mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-29 18:10:09 +00:00
refactoring...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
7a5663e044
commit
1e2c4b01c2
@ -688,6 +688,64 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* XXX EXPERIMENTAL...
|
||||||
|
* - need to handle nested blocks somehow...
|
||||||
|
*/
|
||||||
|
.editor .outline .block.table-2 {
|
||||||
|
|
||||||
|
&>.view {
|
||||||
|
font-size: small;
|
||||||
|
color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
background: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background: rgba(0,0,0,0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.children {
|
||||||
|
display: table !important;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&>.block {
|
||||||
|
display: table-row;
|
||||||
|
|
||||||
|
&:first-child>.view td {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: solid 0.1rem silver;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
&:not(:first-child) {
|
||||||
|
&>.view td {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
background: rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background: rgba(0,0,0,0.07);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block>.children,
|
||||||
|
.view,
|
||||||
|
table,
|
||||||
|
tbody,
|
||||||
|
tr {
|
||||||
|
display: contents !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/********************************************************* Testing ***/
|
/********************************************************* Testing ***/
|
||||||
|
|
||||||
:host.show-click-zones .outline .block,
|
:host.show-click-zones .outline .block,
|
||||||
@ -710,5 +768,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* vim:set ts=4 sw=4 : */
|
* vim:set ts=4 sw=4 : */
|
||||||
|
|||||||
@ -102,9 +102,16 @@ var getCharOffset = function(elem, x, y, data){
|
|||||||
return data.c - 1 }
|
return data.c - 1 }
|
||||||
|
|
||||||
// count "virtual" newlines between text and block elements...
|
// count "virtual" newlines between text and block elements...
|
||||||
var block = ['block', 'table', 'flex', 'grid']
|
var type = getComputedStyle(e).display
|
||||||
.includes(
|
var block = [
|
||||||
getComputedStyle(e).display)
|
'block',
|
||||||
|
// XXX these do not add up yet...
|
||||||
|
//'table',
|
||||||
|
//'table-row',
|
||||||
|
//'table-cell',
|
||||||
|
'flex',
|
||||||
|
'grid',
|
||||||
|
].includes(type)
|
||||||
if(block
|
if(block
|
||||||
&& data.prev_elem
|
&& data.prev_elem
|
||||||
&& data.prev_elem != 'block'){
|
&& data.prev_elem != 'block'){
|
||||||
@ -115,6 +122,13 @@ var getCharOffset = function(elem, x, y, data){
|
|||||||
|
|
||||||
// handle the node...
|
// handle the node...
|
||||||
data = getCharOffset(e, x, y, data)
|
data = getCharOffset(e, x, y, data)
|
||||||
|
|
||||||
|
// compensate for table stuff...
|
||||||
|
if(type == 'table-row'){
|
||||||
|
data.c -= 1 }
|
||||||
|
if(type == 'table-cell'){
|
||||||
|
data.c += 1 }
|
||||||
|
|
||||||
if(typeof(data) != 'object'){
|
if(typeof(data) != 'object'){
|
||||||
return data } } }
|
return data } } }
|
||||||
return arguments.length > 3 ?
|
return arguments.length > 3 ?
|
||||||
@ -200,8 +214,11 @@ var plugin = {
|
|||||||
return function(_, text){
|
return function(_, text){
|
||||||
elem.style ??= []
|
elem.style ??= []
|
||||||
elem.style.push(...style)
|
elem.style.push(...style)
|
||||||
return code
|
return typeof(code) == 'function' ?
|
||||||
?? text } },
|
code(...arguments)
|
||||||
|
: code != null ?
|
||||||
|
code
|
||||||
|
: text } },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -632,15 +649,23 @@ var syntax = {
|
|||||||
var tables = {
|
var tables = {
|
||||||
__proto__: plugin,
|
__proto__: plugin,
|
||||||
|
|
||||||
|
// XXX EXPERIMENTAL
|
||||||
|
__pre_parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
.replace(/^(--table--)$/m, this.style(editor, elem, 'table-2')) },
|
||||||
|
|
||||||
__parse__: function(text, editor, elem){
|
__parse__: function(text, editor, elem){
|
||||||
return text
|
return text
|
||||||
.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/,
|
.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/,
|
||||||
function(_, body){
|
this.style(editor, elem,
|
||||||
return `<table><tr><td>${
|
'table',
|
||||||
body
|
function(_, body){
|
||||||
.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>')
|
return `<table><tr><td>${
|
||||||
.replace(/\s*\|\s*/gm, '</td><td>')
|
body
|
||||||
}</td></td></table>` }) },
|
.trim()
|
||||||
|
.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>')
|
||||||
|
.replace(/\s*\|\s*/gm, '</td><td>')
|
||||||
|
}</td></td></table>` })) },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -732,17 +757,6 @@ var escaping = {
|
|||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
var JSONOutline = {
|
var JSONOutline = {
|
||||||
// Format:
|
|
||||||
// <json> ::= [
|
|
||||||
// {
|
|
||||||
// text: <text>,
|
|
||||||
// children: <json>,
|
|
||||||
// ...
|
|
||||||
// },
|
|
||||||
// ...
|
|
||||||
// ]
|
|
||||||
json: undefined,
|
|
||||||
|
|
||||||
// format:
|
// format:
|
||||||
// {
|
// {
|
||||||
// <id>: <node>,
|
// <id>: <node>,
|
||||||
@ -777,7 +791,7 @@ var JSONOutline = {
|
|||||||
var i = 0
|
var i = 0
|
||||||
// all nodes..
|
// all nodes..
|
||||||
if(node == null || node == 'all' || node == 'visible'){
|
if(node == null || node == 'all' || node == 'visible'){
|
||||||
for(var e of this.json){
|
for(var e of this.json()){
|
||||||
yield* this.__iter(e, [i++], node) }
|
yield* this.__iter(e, [i++], node) }
|
||||||
// single node...
|
// single node...
|
||||||
} else {
|
} else {
|
||||||
@ -789,7 +803,7 @@ var JSONOutline = {
|
|||||||
this.get(...args),
|
this.get(...args),
|
||||||
mode) } },
|
mode) } },
|
||||||
[Symbol.iterator]: function*(mode='all'){
|
[Symbol.iterator]: function*(mode='all'){
|
||||||
for(var node of this.json){
|
for(var node of this.json()){
|
||||||
for(var [_, n] of this.__iter(node, mode)){
|
for(var [_, n] of this.__iter(node, mode)){
|
||||||
yield n } } },
|
yield n } } },
|
||||||
iter: function*(node, mode){
|
iter: function*(node, mode){
|
||||||
@ -817,12 +831,285 @@ var JSONOutline = {
|
|||||||
crop: function(){},
|
crop: function(){},
|
||||||
uncrop: function(){},
|
uncrop: function(){},
|
||||||
|
|
||||||
parseBlockAttrs: function(){},
|
// NOTE: this is auto-populated by plugin.style(..)...
|
||||||
parse: function(){},
|
__styles: undefined,
|
||||||
|
|
||||||
|
// block render...
|
||||||
|
__code2html__: function(code, elem={}){
|
||||||
|
var that = this
|
||||||
|
|
||||||
|
// only whitespace -> keep element blank...
|
||||||
|
if(code.trim() == ''){
|
||||||
|
elem.text = code
|
||||||
|
return elem }
|
||||||
|
|
||||||
|
// helpers...
|
||||||
|
var run = function(stage, text){
|
||||||
|
var meth = {
|
||||||
|
pre: '__pre_parse__',
|
||||||
|
main: '__parse__',
|
||||||
|
post: '__post_parse__',
|
||||||
|
}[stage]
|
||||||
|
return that.threadPlugins(meth, text, that, elem) }
|
||||||
|
|
||||||
|
elem = this.parseBlockAttrs(code, elem)
|
||||||
|
code = elem.text
|
||||||
|
|
||||||
|
// stage: pre...
|
||||||
|
var text = run('pre',
|
||||||
|
// pre-sanitize...
|
||||||
|
code.replace(/\x00/g, ''))
|
||||||
|
// split text into parsable and non-parsable sections...
|
||||||
|
var sections = text
|
||||||
|
// split fomat:
|
||||||
|
// [ text <match> <type> <body>, ... ]
|
||||||
|
.split(/(<(pre|code)(?:|\s[^>]*)>((?:\n|.)*)<\/\2>)/g)
|
||||||
|
// sort out the sections...
|
||||||
|
var parsable = []
|
||||||
|
var quoted = []
|
||||||
|
while(sections.length > 0){
|
||||||
|
var [section, match] = sections.splice(0, 4)
|
||||||
|
parsable.push(section)
|
||||||
|
quoted.push(match) }
|
||||||
|
// stage: main...
|
||||||
|
text = run('main',
|
||||||
|
// parse only the parsable sections...
|
||||||
|
parsable.join('\x00'))
|
||||||
|
.split(/\x00/g)
|
||||||
|
// merge the quoted sections back in...
|
||||||
|
.map(function(section){
|
||||||
|
return [section, quoted.shift() ?? ''] })
|
||||||
|
.flat()
|
||||||
|
.join('')
|
||||||
|
// stage: post...
|
||||||
|
elem.text = run('post', text)
|
||||||
|
|
||||||
|
return elem },
|
||||||
|
|
||||||
|
// output format...
|
||||||
|
__code2text__: function(code){
|
||||||
|
return code
|
||||||
|
.replace(/(\n\s*)-/g, '$1\\-') },
|
||||||
|
__text2code__: function(text){
|
||||||
|
text = text
|
||||||
|
.replace(/(\n\s*)\\-/g, '$1-')
|
||||||
|
return this.trim_block_text ?
|
||||||
|
text.trim()
|
||||||
|
: text },
|
||||||
|
|
||||||
|
__block_attrs__: {
|
||||||
|
id: 'attr',
|
||||||
|
collapsed: 'attr',
|
||||||
|
focused: 'cls',
|
||||||
|
},
|
||||||
|
//
|
||||||
|
// Parse attrs...
|
||||||
|
// .parseBlockAttrs(<text>[, <elem>])
|
||||||
|
// -> <elem>
|
||||||
|
//
|
||||||
|
// Parse attrs keeping non-system attrs in .text...
|
||||||
|
// .parseBlockAttrs(<text>, true[, <elem>])
|
||||||
|
// -> <elem>
|
||||||
|
//
|
||||||
|
// Parse attrs keeping all attrs in .text...
|
||||||
|
// .parseBlockAttrs(<text>, 'all'[, <elem>])
|
||||||
|
// -> <elem>
|
||||||
|
//
|
||||||
|
parseBlockAttrs: function(text, keep=false, elem={}){
|
||||||
|
if(typeof(keep) == 'object'){
|
||||||
|
elem = keep
|
||||||
|
keep = typeof(elem) == 'boolean' ?
|
||||||
|
elem
|
||||||
|
: false }
|
||||||
|
var system = this.__block_attrs__
|
||||||
|
var clean = text
|
||||||
|
// XXX for some reason changing the first group into (?<= .. )
|
||||||
|
// still eats up the whitespace...
|
||||||
|
// ...putting the same pattern in a normal group and
|
||||||
|
// returning it works fine...
|
||||||
|
//.replace(/(?<=[\n\h]*)(?:(?:\n|^)\s*\w*\s*::\s*[^\n]*\s*)*$/,
|
||||||
|
.replace(/([\n\t ]*)(?:(?:\n|^)[\t ]*\w+[\t ]*::[\t ]*[^\n]+[\t ]*)+$/,
|
||||||
|
function(match, ws){
|
||||||
|
var attrs = match
|
||||||
|
.trim()
|
||||||
|
.split(/(?:[\t ]*::[\t ]*|[\t ]*\n[\t ]*)/g)
|
||||||
|
while(attrs.length > 0){
|
||||||
|
var [name, val] = attrs.splice(0, 2)
|
||||||
|
elem[name] =
|
||||||
|
val == 'true' ?
|
||||||
|
true
|
||||||
|
: val == 'false' ?
|
||||||
|
false
|
||||||
|
: val
|
||||||
|
// keep non-system attrs...
|
||||||
|
if(keep
|
||||||
|
&& !(name in system)){
|
||||||
|
ws += `\n${name}::${val}` } }
|
||||||
|
return ws })
|
||||||
|
elem.text = keep == 'all' ?
|
||||||
|
text
|
||||||
|
: clean
|
||||||
|
return elem },
|
||||||
|
parse: function(text){
|
||||||
|
var that = this
|
||||||
|
text = text
|
||||||
|
.replace(/^[ \t]*\n/, '')
|
||||||
|
text = ('\n' + text)
|
||||||
|
.split(/\n([ \t]*)(?:- |-\s*$)/gm)
|
||||||
|
.slice(1)
|
||||||
|
var tab = ' '.repeat(this.tab_size || 8)
|
||||||
|
var level = function(lst, prev_sep=undefined, parent=[]){
|
||||||
|
while(lst.length > 0){
|
||||||
|
sep = lst[0].replace(/\t/gm, tab)
|
||||||
|
// deindent...
|
||||||
|
if(prev_sep != null
|
||||||
|
&& sep.length < prev_sep.length){
|
||||||
|
break }
|
||||||
|
prev_sep ??= sep
|
||||||
|
// same level...
|
||||||
|
if(sep.length == prev_sep.length){
|
||||||
|
var [_, block] = lst.splice(0, 2)
|
||||||
|
var attrs = that.parseBlockAttrs(block)
|
||||||
|
attrs.text = that.__text2code__(attrs.text
|
||||||
|
// normalize indent...
|
||||||
|
.split(new RegExp('\n'+sep+' ', 'g'))
|
||||||
|
.join('\n'))
|
||||||
|
parent.push({
|
||||||
|
collapsed: false,
|
||||||
|
focused: false,
|
||||||
|
...attrs,
|
||||||
|
children: [],
|
||||||
|
})
|
||||||
|
// indent...
|
||||||
|
} else {
|
||||||
|
parent.at(-1).children = level(lst, sep) } }
|
||||||
|
return parent }
|
||||||
|
return level(text) },
|
||||||
|
|
||||||
data: function(){},
|
data: function(){},
|
||||||
load: function(){},
|
load: function(){},
|
||||||
text: function(){},
|
|
||||||
|
// Format:
|
||||||
|
// <json> ::= [
|
||||||
|
// {
|
||||||
|
// text: <text>,
|
||||||
|
// children: <json>,
|
||||||
|
// ...
|
||||||
|
// },
|
||||||
|
// ...
|
||||||
|
// ]
|
||||||
|
// XXX
|
||||||
|
json: function(){},
|
||||||
|
|
||||||
|
// XXX add option to customize indent size...
|
||||||
|
text: function(node, indent, level){
|
||||||
|
// .text(<indent>, <level>)
|
||||||
|
if(typeof(node) == 'string'){
|
||||||
|
;[node, indent=' ', level=''] = [undefined, ...arguments] }
|
||||||
|
node ??= this.json(node)
|
||||||
|
indent ??= ' '
|
||||||
|
level ??= ''
|
||||||
|
var text = []
|
||||||
|
for(var elem of node){
|
||||||
|
text.push(
|
||||||
|
level +'- '
|
||||||
|
+ this.__code2text__(elem.text)
|
||||||
|
.replace(/\n/g, '\n'+ level +' ')
|
||||||
|
// attrs...
|
||||||
|
+ (Object.keys(elem)
|
||||||
|
.reduce(function(res, attr){
|
||||||
|
return (attr == 'text'
|
||||||
|
|| attr == 'children') ?
|
||||||
|
res
|
||||||
|
: res
|
||||||
|
+ (elem[attr] ?
|
||||||
|
'\n'+level+' ' + `${ attr }:: ${ elem[attr] }`
|
||||||
|
: '') }, '')),
|
||||||
|
(elem.children
|
||||||
|
&& elem.children.length > 0) ?
|
||||||
|
this.text(elem.children || [], indent, level+indent)
|
||||||
|
: [] ) }
|
||||||
|
return text
|
||||||
|
.flat()
|
||||||
|
.join('\n') },
|
||||||
|
|
||||||
|
// XXX add read-only option...
|
||||||
|
htmlBlock: function(data, options={}){
|
||||||
|
var that = this
|
||||||
|
|
||||||
|
var parsed = this.__code2html__(data.text, {...data})
|
||||||
|
|
||||||
|
var cls = parsed.style ?? []
|
||||||
|
delete parsed.style
|
||||||
|
|
||||||
|
var attrs = []
|
||||||
|
|
||||||
|
for(var [attr, value] of Object.entries({...data, ...parsed})){
|
||||||
|
if(attr == 'children' || attr == 'text'){
|
||||||
|
continue }
|
||||||
|
var i
|
||||||
|
var type = this.__block_attrs__[attr]
|
||||||
|
if(type == 'cls'){
|
||||||
|
value ?
|
||||||
|
cls.push(attr)
|
||||||
|
: (i = cls.indexOf(attr)) >= 0 ?
|
||||||
|
cls.splice(i, 1)
|
||||||
|
: undefined
|
||||||
|
} else if(type == 'attr'
|
||||||
|
|| type == undefined){
|
||||||
|
typeof(value) == 'boolean'?
|
||||||
|
(value ?
|
||||||
|
attrs.push(attr)
|
||||||
|
: (i = attrs.indexOf(attr)) >= 0 ?
|
||||||
|
attrs.splice(i, 1)
|
||||||
|
: undefined)
|
||||||
|
: value != null ?
|
||||||
|
attrs.push(`${attr}="${value}"`)
|
||||||
|
: (i = attrs.indexOf(attr)) >= 0 ?
|
||||||
|
attrs.splice(i, 1)
|
||||||
|
: undefined } }
|
||||||
|
|
||||||
|
var children = data.children
|
||||||
|
.map(function(data){
|
||||||
|
return that.htmlBlock(data) })
|
||||||
|
.join('')
|
||||||
|
return (
|
||||||
|
`<div class="block ${ cls.join(' ') }" tabindex="0" ${ attrs.join(' ') }>\
|
||||||
|
<textarea class="code text" value="${ data.text }"></textarea>\
|
||||||
|
<span class="view text">${ parsed.text }</span>\
|
||||||
|
<div class="children">${ children }</div>\
|
||||||
|
</div>`) },
|
||||||
|
html: function(data, options=false){
|
||||||
|
var that = this
|
||||||
|
if(typeof(data) == 'boolean'){
|
||||||
|
options = data
|
||||||
|
data = undefined }
|
||||||
|
data = data == null ?
|
||||||
|
this.json()
|
||||||
|
: typeof(data) == 'string' ?
|
||||||
|
this.parse(data)
|
||||||
|
: data instanceof Array ?
|
||||||
|
data
|
||||||
|
: [data]
|
||||||
|
options =
|
||||||
|
typeof(options) == 'boolean' ?
|
||||||
|
{full: options}
|
||||||
|
: (options
|
||||||
|
?? {})
|
||||||
|
|
||||||
|
var nodes = data
|
||||||
|
.map(function(data){
|
||||||
|
return that.htmlBlock(data) })
|
||||||
|
.join('')
|
||||||
|
|
||||||
|
return !options.full ?
|
||||||
|
nodes
|
||||||
|
: (
|
||||||
|
`<div class="editor" autofocus>\
|
||||||
|
<div class="header"></div>\
|
||||||
|
<textarea class="code"></textarea>\
|
||||||
|
<div class="outline" tabindex="0">${ nodes }</div>\
|
||||||
|
</div>`) },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -830,6 +1117,8 @@ var JSONOutline = {
|
|||||||
// XXX experiment with a concatinative model...
|
// XXX experiment with a concatinative model...
|
||||||
// .get(..) -> Outline (view)
|
// .get(..) -> Outline (view)
|
||||||
var Outline = {
|
var Outline = {
|
||||||
|
__proto__: JSONOutline,
|
||||||
|
|
||||||
dom: undefined,
|
dom: undefined,
|
||||||
|
|
||||||
// config...
|
// config...
|
||||||
@ -1039,18 +1328,22 @@ var Outline = {
|
|||||||
: offset == 'visible' ?
|
: offset == 'visible' ?
|
||||||
[...node.querySelectorAll('.block')]
|
[...node.querySelectorAll('.block')]
|
||||||
.filter(function(e){
|
.filter(function(e){
|
||||||
return e.querySelector('.view').offsetParent != null })
|
//return e.querySelector('.view').offsetParent != null })
|
||||||
|
return e.offsetParent != null })
|
||||||
: offset == 'viewport' ?
|
: offset == 'viewport' ?
|
||||||
[...node.querySelectorAll('.block')]
|
[...node.querySelectorAll('.block')]
|
||||||
.filter(function(e){
|
.filter(function(e){
|
||||||
return e.querySelector('.view').offsetParent != null
|
//return e.querySelector('.view').offsetParent != null
|
||||||
&& e.querySelector('.code').visibleInViewport() })
|
// && e.querySelector('.code').visibleInViewport() })
|
||||||
|
return e.offsetParent != null
|
||||||
|
&& e.visibleInViewport() })
|
||||||
: offset == 'editable' ?
|
: offset == 'editable' ?
|
||||||
[...node.querySelectorAll('.block>.code')]
|
[...node.querySelectorAll('.block>.code')]
|
||||||
: offset == 'selected' ?
|
: offset == 'selected' ?
|
||||||
[...node.querySelectorAll('.block[selected]')]
|
[...node.querySelectorAll('.block[selected]')]
|
||||||
.filter(function(e){
|
.filter(function(e){
|
||||||
return e.querySelector('.view').offsetParent != null })
|
//return e.querySelector('.view').offsetParent != null })
|
||||||
|
return e.offsetParent != null })
|
||||||
: offset == 'children' ?
|
: offset == 'children' ?
|
||||||
children(node)
|
children(node)
|
||||||
: offset == 'siblings' ?
|
: offset == 'siblings' ?
|
||||||
@ -1141,13 +1434,15 @@ var Outline = {
|
|||||||
},
|
},
|
||||||
// NOTE: this does not internally handle undo as it would be too
|
// NOTE: this does not internally handle undo as it would be too
|
||||||
// granular...
|
// granular...
|
||||||
|
_updateTextareaSize: function(elem){
|
||||||
|
elem.style.height = getComputedStyle(elem.nextSibling).height },
|
||||||
update: function(node='focused', data){
|
update: function(node='focused', data){
|
||||||
var node = this.get(node)
|
var node = this.get(node)
|
||||||
data ??= this.data(node, false)
|
data ??= this.data(node, false)
|
||||||
|
|
||||||
var parsed = {}
|
var parsed = {}
|
||||||
if('text' in data){
|
if('text' in data){
|
||||||
var text = node.querySelector('.code')
|
var code = node.querySelector('.code')
|
||||||
var html = node.querySelector('.view')
|
var html = node.querySelector('.view')
|
||||||
if(this.__code2html__){
|
if(this.__code2html__){
|
||||||
// NOTE: we are ignoring the .collapsed attr here
|
// NOTE: we are ignoring the .collapsed attr here
|
||||||
@ -1171,8 +1466,10 @@ var Outline = {
|
|||||||
// NOTE: adding a space here is done to prevent the browser
|
// NOTE: adding a space here is done to prevent the browser
|
||||||
// from hiding the last newline...
|
// from hiding the last newline...
|
||||||
: data.text + ' ' }
|
: data.text + ' ' }
|
||||||
text.value = data.text
|
code.value = data.text
|
||||||
text.updateSize() }
|
code.updateSize()
|
||||||
|
// NOTE: this will have no effect if the element is not attached...
|
||||||
|
this._updateTextareaSize(code) }
|
||||||
|
|
||||||
for(var [attr, value] of Object.entries({...data, ...parsed})){
|
for(var [attr, value] of Object.entries({...data, ...parsed})){
|
||||||
if(attr == 'children' || attr == 'text'){
|
if(attr == 'children' || attr == 'text'){
|
||||||
@ -1425,70 +1722,6 @@ var Outline = {
|
|||||||
;[this.__redo_stack] = this.__undo(this.__redo_stack)
|
;[this.__redo_stack] = this.__undo(this.__redo_stack)
|
||||||
return this },
|
return this },
|
||||||
|
|
||||||
// block render...
|
|
||||||
// NOTE: this is auto-populated by .__code2html__(..)
|
|
||||||
__styles: undefined,
|
|
||||||
__code2html__: function(code, elem={}){
|
|
||||||
var that = this
|
|
||||||
|
|
||||||
// only whitespace -> keep element blank...
|
|
||||||
if(code.trim() == ''){
|
|
||||||
elem.text = code
|
|
||||||
return elem }
|
|
||||||
|
|
||||||
// helpers...
|
|
||||||
var run = function(stage, text){
|
|
||||||
var meth = {
|
|
||||||
pre: '__pre_parse__',
|
|
||||||
main: '__parse__',
|
|
||||||
post: '__post_parse__',
|
|
||||||
}[stage]
|
|
||||||
return that.threadPlugins(meth, text, that, elem) }
|
|
||||||
|
|
||||||
elem = this.parseBlockAttrs(code, elem)
|
|
||||||
code = elem.text
|
|
||||||
|
|
||||||
// stage: pre...
|
|
||||||
var text = run('pre',
|
|
||||||
// pre-sanitize...
|
|
||||||
code.replace(/\x00/g, ''))
|
|
||||||
// split text into parsable and non-parsable sections...
|
|
||||||
var sections = text
|
|
||||||
// split fomat:
|
|
||||||
// [ text <match> <type> <body>, ... ]
|
|
||||||
.split(/(<(pre|code)(?:|\s[^>]*)>((?:\n|.)*)<\/\2>)/g)
|
|
||||||
// sort out the sections...
|
|
||||||
var parsable = []
|
|
||||||
var quoted = []
|
|
||||||
while(sections.length > 0){
|
|
||||||
var [section, match] = sections.splice(0, 4)
|
|
||||||
parsable.push(section)
|
|
||||||
quoted.push(match) }
|
|
||||||
// stage: main...
|
|
||||||
text = run('main',
|
|
||||||
// parse only the parsable sections...
|
|
||||||
parsable.join('\x00'))
|
|
||||||
.split(/\x00/g)
|
|
||||||
// merge the quoted sections back in...
|
|
||||||
.map(function(section){
|
|
||||||
return [section, quoted.shift() ?? ''] })
|
|
||||||
.flat()
|
|
||||||
.join('')
|
|
||||||
// stage: post...
|
|
||||||
elem.text = run('post', text)
|
|
||||||
|
|
||||||
return elem },
|
|
||||||
// output format...
|
|
||||||
__code2text__: function(code){
|
|
||||||
return code
|
|
||||||
.replace(/(\n\s*)-/g, '$1\\-') },
|
|
||||||
__text2code__: function(text){
|
|
||||||
text = text
|
|
||||||
.replace(/(\n\s*)\\-/g, '$1-')
|
|
||||||
return this.trim_block_text ?
|
|
||||||
text.trim()
|
|
||||||
: text },
|
|
||||||
|
|
||||||
// serialization...
|
// serialization...
|
||||||
data: function(elem, deep=true){
|
data: function(elem, deep=true){
|
||||||
elem = this.get(elem)
|
elem = this.get(elem)
|
||||||
@ -1521,121 +1754,6 @@ var Outline = {
|
|||||||
return children
|
return children
|
||||||
.map(function(elem){
|
.map(function(elem){
|
||||||
return that.data(elem) }) },
|
return that.data(elem) }) },
|
||||||
// XXX add option to customize indent size...
|
|
||||||
text: function(node, indent, level){
|
|
||||||
// .text(<indent>, <level>)
|
|
||||||
if(typeof(node) == 'string'){
|
|
||||||
;[node, indent=' ', level=''] = [undefined, ...arguments] }
|
|
||||||
node ??= this.json(node)
|
|
||||||
indent ??= ' '
|
|
||||||
level ??= ''
|
|
||||||
var text = []
|
|
||||||
for(var elem of node){
|
|
||||||
text.push(
|
|
||||||
level +'- '
|
|
||||||
+ this.__code2text__(elem.text)
|
|
||||||
.replace(/\n/g, '\n'+ level +' ')
|
|
||||||
// attrs...
|
|
||||||
+ (Object.keys(elem)
|
|
||||||
.reduce(function(res, attr){
|
|
||||||
return (attr == 'text'
|
|
||||||
|| attr == 'children') ?
|
|
||||||
res
|
|
||||||
: res
|
|
||||||
+ (elem[attr] ?
|
|
||||||
'\n'+level+' ' + `${ attr }:: ${ elem[attr] }`
|
|
||||||
: '') }, '')),
|
|
||||||
(elem.children
|
|
||||||
&& elem.children.length > 0) ?
|
|
||||||
this.text(elem.children || [], indent, level+indent)
|
|
||||||
: [] ) }
|
|
||||||
return text
|
|
||||||
.flat()
|
|
||||||
.join('\n') },
|
|
||||||
|
|
||||||
//
|
|
||||||
// Parse attrs...
|
|
||||||
// .parseBlockAttrs(<text>[, <elem>])
|
|
||||||
// -> <elem>
|
|
||||||
//
|
|
||||||
// Parse attrs keeping non-system attrs in .text...
|
|
||||||
// .parseBlockAttrs(<text>, true[, <elem>])
|
|
||||||
// -> <elem>
|
|
||||||
//
|
|
||||||
// Parse attrs keeping all attrs in .text...
|
|
||||||
// .parseBlockAttrs(<text>, 'all'[, <elem>])
|
|
||||||
// -> <elem>
|
|
||||||
//
|
|
||||||
parseBlockAttrs: function(text, keep=false, elem={}){
|
|
||||||
if(typeof(keep) == 'object'){
|
|
||||||
elem = keep
|
|
||||||
keep = typeof(elem) == 'boolean' ?
|
|
||||||
elem
|
|
||||||
: false }
|
|
||||||
var system = this.__block_attrs__
|
|
||||||
var clean = text
|
|
||||||
// XXX for some reason changing the first group into (?<= .. )
|
|
||||||
// still eats up the whitespace...
|
|
||||||
// ...putting the same pattern in a normal group and
|
|
||||||
// returning it works fine...
|
|
||||||
//.replace(/(?<=[\n\h]*)(?:(?:\n|^)\s*\w*\s*::\s*[^\n]*\s*)*$/,
|
|
||||||
.replace(/([\n\t ]*)(?:(?:\n|^)[\t ]*\w+[\t ]*::[\t ]*[^\n]+[\t ]*)+$/,
|
|
||||||
function(match, ws){
|
|
||||||
var attrs = match
|
|
||||||
.trim()
|
|
||||||
.split(/(?:[\t ]*::[\t ]*|[\t ]*\n[\t ]*)/g)
|
|
||||||
while(attrs.length > 0){
|
|
||||||
var [name, val] = attrs.splice(0, 2)
|
|
||||||
elem[name] =
|
|
||||||
val == 'true' ?
|
|
||||||
true
|
|
||||||
: val == 'false' ?
|
|
||||||
false
|
|
||||||
: val
|
|
||||||
// keep non-system attrs...
|
|
||||||
if(keep
|
|
||||||
&& !(name in system)){
|
|
||||||
ws += `\n${name}::${val}` } }
|
|
||||||
return ws })
|
|
||||||
elem.text = keep == 'all' ?
|
|
||||||
text
|
|
||||||
: clean
|
|
||||||
return elem },
|
|
||||||
parse: function(text){
|
|
||||||
var that = this
|
|
||||||
text = text
|
|
||||||
.replace(/^[ \t]*\n/, '')
|
|
||||||
text = ('\n' + text)
|
|
||||||
.split(/\n([ \t]*)(?:- |-\s*$)/gm)
|
|
||||||
.slice(1)
|
|
||||||
var tab = ' '.repeat(this.tab_size || 8)
|
|
||||||
var level = function(lst, prev_sep=undefined, parent=[]){
|
|
||||||
while(lst.length > 0){
|
|
||||||
sep = lst[0].replace(/\t/gm, tab)
|
|
||||||
// deindent...
|
|
||||||
if(prev_sep != null
|
|
||||||
&& sep.length < prev_sep.length){
|
|
||||||
break }
|
|
||||||
prev_sep ??= sep
|
|
||||||
// same level...
|
|
||||||
if(sep.length == prev_sep.length){
|
|
||||||
var [_, block] = lst.splice(0, 2)
|
|
||||||
var attrs = that.parseBlockAttrs(block)
|
|
||||||
attrs.text = that.__text2code__(attrs.text
|
|
||||||
// normalize indent...
|
|
||||||
.split(new RegExp('\n'+sep+' ', 'g'))
|
|
||||||
.join('\n'))
|
|
||||||
parent.push({
|
|
||||||
collapsed: false,
|
|
||||||
focused: false,
|
|
||||||
...attrs,
|
|
||||||
children: [],
|
|
||||||
})
|
|
||||||
// indent...
|
|
||||||
} else {
|
|
||||||
parent.at(-1).children = level(lst, sep) } }
|
|
||||||
return parent }
|
|
||||||
return level(text) },
|
|
||||||
|
|
||||||
// XXX should this handle children???
|
// XXX should this handle children???
|
||||||
// XXX revise name...
|
// XXX revise name...
|
||||||
@ -1699,6 +1817,8 @@ var Outline = {
|
|||||||
cur[place](block)
|
cur[place](block)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
this._updateTextareaSize(code)
|
||||||
|
|
||||||
this.setUndo(this.path(cur), 'remove', [this.path(block)]) }
|
this.setUndo(this.path(cur), 'remove', [this.path(block)]) }
|
||||||
return block },
|
return block },
|
||||||
// XXX see inside...
|
// XXX see inside...
|
||||||
@ -1728,8 +1848,9 @@ var Outline = {
|
|||||||
// ...this is done by expanding the textarea to the element
|
// ...this is done by expanding the textarea to the element
|
||||||
// size and enabling it to intercept clicks correctly...
|
// size and enabling it to intercept clicks correctly...
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
|
var f = that._updateTextareaSize
|
||||||
for(var e of [...that.outline.querySelectorAll('textarea')]){
|
for(var e of [...that.outline.querySelectorAll('textarea')]){
|
||||||
e.updateSize() } }, 0)
|
f(e) } }, 0)
|
||||||
// restore focus...
|
// restore focus...
|
||||||
this.focus()
|
this.focus()
|
||||||
return this },
|
return this },
|
||||||
|
|||||||
@ -70,6 +70,10 @@ var setup = function(){
|
|||||||
(BUG also the above line is not italic -- can't reproduce)
|
(BUG also the above line is not italic -- can't reproduce)
|
||||||
- clicking right of the last line places cursor wrong
|
- clicking right of the last line places cursor wrong
|
||||||
- _this is a problem with the new version of `getMarkdownOffset(..)`_
|
- _this is a problem with the new version of `getMarkdownOffset(..)`_
|
||||||
|
- DONE text text text
|
||||||
|
- DONE text text text
|
||||||
|
text text text
|
||||||
|
text text text
|
||||||
- DONE M
|
- DONE M
|
||||||
M can't place cursor before first char
|
M can't place cursor before first char
|
||||||
M
|
M
|
||||||
@ -87,6 +91,9 @@ var setup = function(){
|
|||||||
text text text
|
text text text
|
||||||
```
|
```
|
||||||
text text text
|
text text text
|
||||||
|
- DONE |text|text|text|
|
||||||
|
|text|text|text|
|
||||||
|
|text|text|text|
|
||||||
- BUG: parser: code blocks do not ignore single back-quotes...
|
- BUG: parser: code blocks do not ignore single back-quotes...
|
||||||
- ```
|
- ```
|
||||||
x = `moo`
|
x = `moo`
|
||||||
@ -103,8 +110,14 @@ var setup = function(){
|
|||||||
- side margins are a bit too large (account for toolbat to the right)
|
- side margins are a bit too large (account for toolbat to the right)
|
||||||
-
|
-
|
||||||
- ## ToDo:
|
- ## ToDo:
|
||||||
|
- Q: should we use `HTMLTextAreaElement.autoUpdateSize(..)` or handle it globally in setup???
|
||||||
|
- _...I'm leaning towards the later..._
|
||||||
- Q: can we place a cursor in a table correctly???
|
- Q: can we place a cursor in a table correctly???
|
||||||
- Q: should tables be text-based markdown or higher-level?
|
- Q: should tables be text-based markdown or higher-level?
|
||||||
|
- for reference a normal table
|
||||||
|
- | col 1 | col 2 | col 3 |
|
||||||
|
| moo | foo | boo |
|
||||||
|
| 1 | 2 | 3 |
|
||||||
- block-based -- adjacent blocks in table format (a-la markdown) are treated as rows of one table...
|
- block-based -- adjacent blocks in table format (a-la markdown) are treated as rows of one table...
|
||||||
- here is an example
|
- here is an example
|
||||||
- | col 1 | col 2 | col 3 |
|
- | col 1 | col 2 | col 3 |
|
||||||
@ -114,17 +127,18 @@ var setup = function(){
|
|||||||
- not yet sure how are we going to allign columns (CSS preffered)
|
- not yet sure how are we going to allign columns (CSS preffered)
|
||||||
- block-children -- similar to how lists are done now
|
- block-children -- similar to how lists are done now
|
||||||
- a demo
|
- a demo
|
||||||
- table:
|
- --table--
|
||||||
- | A | B | B |
|
- | A | B | C |
|
||||||
- | 1 | 2 | 3 |
|
- | 1 | 2 | 3 |
|
||||||
- | X | y | Z |
|
- | moo | foo | boo |
|
||||||
-
|
-
|
||||||
- the header may be used as::
|
- the header may be used as::
|
||||||
- header row
|
- header row
|
||||||
- caption text
|
- caption text
|
||||||
- both?
|
- both?
|
||||||
- see CSS grids + 'display: contents' (might help hide non-grid elemnts...
|
- Q: how do we handle indenting a table row?
|
||||||
-
|
- Q: how do we handle unmarked text?
|
||||||
|
-
|
||||||
- might be fun to make the general syntax (with "=" removed) to be compatible with markdown...
|
- might be fun to make the general syntax (with "=" removed) to be compatible with markdown...
|
||||||
- might also be fun to auto-generat (template) new blocks within a table...
|
- might also be fun to auto-generat (template) new blocks within a table...
|
||||||
- this would greatly simplify table navigation and creation
|
- this would greatly simplify table navigation and creation
|
||||||
@ -374,13 +388,13 @@ var setup = function(){
|
|||||||
| a-s | toggle status |
|
| a-s | toggle status |
|
||||||
| a-x | toggle status DONE |
|
| a-x | toggle status DONE |
|
||||||
| a-r | toggle status REJECT |
|
| a-r | toggle status REJECT |
|
||||||
| c-z | normal: undo |
|
| c-z | normal: undo |
|
||||||
| c-s-z | normal: redo |
|
| c-s-z | normal: redo |
|
||||||
| c | normal: crop current node |
|
| c | normal: crop current node |
|
||||||
| enter | normal: edit node |
|
| enter | normal: edit node |
|
||||||
| | edit: create node below |
|
| | edit: create node below |
|
||||||
| esc | crop: exit crop |
|
| esc | crop: exit crop |
|
||||||
| | edit: exit edit mode |
|
| | edit: exit edit mode |
|
||||||
- ### Formatting
|
- ### Formatting
|
||||||
- The formatting mostly adheres to the markdown spec with a few minor differences
|
- The formatting mostly adheres to the markdown spec with a few minor differences
|
||||||
-
|
-
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user