diff --git a/experiments/outline-editor/editor.js b/experiments/outline-editor/editor.js index 64850e0..139ced0 100755 --- a/experiments/outline-editor/editor.js +++ b/experiments/outline-editor/editor.js @@ -138,23 +138,25 @@ var plugin = { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// XXX style attributes... var attributes = { __proto__: plugin, - __pre_parse__: function(text, editor, elem){ + __parse__: function(text, editor, elem){ + var skip = new Set([ + 'text', + 'focused', + 'collapsed', + 'id', + 'children', + 'style', + ]) return text - // hidden attributes... - // XXX make this generic... - // collapsed... - .replace(/(\n|^)\s*collapsed::\s*(.*)\s*(\n|$)/, - function(_, value){ - elem.collapsed = value.trim() == 'true' - return '' }) - // id... - .replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/, - function(_, value){ - elem.id = value.trim() - return '' }) }, + + Object.entries(elem) + .reduce(function(res, [key, value]){ + return skip.has(key) ? + res + : res + `
${key}: ${value}` }, '') }, } @@ -614,7 +616,6 @@ var Outline = { // - parsing // - event dropping plugins: [ - attributes, blocks, quoted, @@ -622,6 +623,8 @@ var Outline = { // treating '[_] ... [_]' as italic... tasks, styling, + // XXX + //attributes, tables, symbols, //syntax, @@ -838,26 +841,28 @@ var Outline = { update: function(node='focused', data){ var node = this.get(node) data ??= this.data(node, false) - for(var [attr, value] of Object.entries(data)){ - if(attr == 'children'){ - continue } - if(attr == 'text'){ - var text = node.querySelector('textarea') - var html = node.querySelector('span') - if(this.__code2html__){ - // NOTE: we are ignoring the .collapsed attr here - var parsed = this.__code2html__(data.text) - html.innerHTML = parsed.text - // heading... - node.classList.remove(...this.__styles) - parsed.style - && node.classList.add(...parsed.style) - } else { - html.innerHTML = data.text } - text.value = data.text - // XXX this does not seem to work until we click in the textarea... - text.autoUpdateSize() + var parsed = {} + if('text' in data){ + var text = node.querySelector('textarea') + var html = node.querySelector('span') + if(this.__code2html__){ + // NOTE: we are ignoring the .collapsed attr here + parsed = this.__code2html__(data.text, {...data}) + html.innerHTML = parsed.text + // heading... + node.classList.remove(...this.__styles) + parsed.style + && node.classList.add(...parsed.style) + delete parsed.style + } else { + html.innerHTML = data.text } + text.value = data.text + // XXX this does not seem to work until we click in the textarea... + text.autoUpdateSize() } + + for(var [attr, value] of Object.entries({...data, ...parsed})){ + if(attr == 'children' || attr == 'text'){ continue } var type = this.__block_attrs__[attr] @@ -872,8 +877,7 @@ var Outline = { (value ? node.setAttribute(attr, '') : node.removeAttribute(attr)) - : (attr in data - && value != null) ? + : value != null ? node.setAttribute(attr, value) : node.removeAttribute(attr) } } this.__change__() @@ -988,16 +992,13 @@ var Outline = { this.__change__() return this }, - // block serialization... + // block render... // XXX need a way to filter input text... // use-case: hidden attributes... // NOTE: this is auto-populated by .__code2html__(..) __styles: undefined, - __code2html__: function(code){ + __code2html__: function(code, elem={}){ var that = this - var elem = { - collapsed: false, - } // only whitespace -> keep element blank... if(code.trim() == ''){ @@ -1013,6 +1014,9 @@ var Outline = { }[stage] return that.threadPlugins(meth, text, that, elem) } + elem = this.parseBlockAttrs(code, elem) + code = elem.text + // stage: pre... var text = run('pre', // pre-sanitize... @@ -1115,6 +1119,54 @@ var Outline = { .flat() .join('\n') }, + // + // Parse attrs... + // .parseBlockAttrs([, ]) + // -> + // + // Parse attrs keeping non-system attrs in .text... + // .parseBlockAttrs(, true[, ]) + // -> + // + // Parse attrs keeping all attrs in .text... + // .parseBlockAttrs(, 'all'[, ]) + // -> + // + 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 @@ -1134,29 +1186,14 @@ var Outline = { // same level... if(sep.length == prev_sep.length){ var [_, block] = lst.splice(0, 2) - var attrs = { + 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, - } - block = block - // XXX generalize attr processing... - .replace(/\n\s*id::\s*(.*)\s*$/, - function(_, value){ - attrs.id = value - return '' }) - .replace(/\n\s*focused::\s*(.*)\s*$/, - function(_, value){ - attrs.focused = value == 'true' - return '' }) - .replace(/\n\s*collapsed::\s*(.*)\s*$/, - function(_, value){ - attrs.collapsed = value == 'true' - return '' }) - parent.push({ - text: that.__text2code__(block - // normalize indent... - .split(new RegExp('\n'+sep+' ', 'g')) - .join('\n')), ...attrs, children: [], }) @@ -1190,6 +1227,7 @@ var Outline = { children.classList.add('children') children.setAttribute('tabindex', '-1') block.append(code, html, children) + this.update(block, data) // place... @@ -1519,7 +1557,7 @@ var Outline = { // update element state... if(elem.classList.contains('code')){ setTimeout(function(){ - that.update(elem.parentElement) + that.update(elem.parentElement) elem.updateSize() }, 0) } // handle keyboard... evt.key in that.keyboard @@ -1561,7 +1599,9 @@ var Outline = { var elem = evt.target if(elem.classList.contains('code')){ var block = elem.parentElement - that.update(block, { text: elem.value }) + // clean out attrs... + elem.value = that.parseBlockAttrs(elem.value).text + that.update(block) // give the browser a chance to update the DOM... // XXX revise... setTimeout(function(){