diff --git a/experiments/outline-editor/editor.css b/experiments/outline-editor/editor.css index 0631aae..f035604 100755 --- a/experiments/outline-editor/editor.css +++ b/experiments/outline-editor/editor.css @@ -54,13 +54,23 @@ display: block; position: relative; - /* XXX do a better calculation... */ width: calc(100% - var(--button-size) - var(--outline-padding) * 2); - padding-left: var(--outline-padding); - padding-right: var(--outline-padding); + padding: 1em var(--outline-padding); + padding-bottom: 1.2em } +/* virtual empty block... */ +.editor .outline:empty:after { + content: "Empty"; + display: block; + font-style: italic; + color: rgba(0,0,0,0.2); +} +.editor .outline:empty:hover:after { +} + + .editor .outline .block { position: relative; outline: none; @@ -110,6 +120,7 @@ pointer-events: none; } /* block hover... */ +.editor .outline:empty:hover:after, .editor .outline .block:hover>.view { background: linear-gradient( 90deg, @@ -127,6 +138,7 @@ } /* clickable things in view */ .editor .outline .block>.view a, +.editor .outline .block>.view pre, .editor .outline .block>.view input { pointer-events: auto; } @@ -138,7 +150,7 @@ editor .outline .block:focus { outline: none; } .editor .outline .block:focus>.text { - background: rgba(0,0,0,0.1); + background: rgba(0,0,0,0.07); } .editor .outline .block.focused:not(:focus)>.text { background: rgba(0,0,0,0.01); @@ -464,11 +476,13 @@ editor .outline .block:focus { font-family: monospace; background: rgba(0,0,0,0.07); border-radius: 0.2em; + outline: none; } .editor .outline .block>.view pre>code { display: block; padding: 0.6em 0.6em; padding-bottom: 0.8em; + outline: none; } diff --git a/experiments/outline-editor/editor.js b/experiments/outline-editor/editor.js index 26757a1..893b3ce 100755 --- a/experiments/outline-editor/editor.js +++ b/experiments/outline-editor/editor.js @@ -25,6 +25,56 @@ var atLine = function(elem, index){ +//--------------------------------------------------------------------- + +var codeBlock = { + // can be used in: + // .replace(codeBlock.pattern, codeBlock.handler) + // or: + // codeBlock + pattern: /(?` + +`${ + quote ? + quote(code) + : code + }` + +`` }, + + quote: function(text){ + return text + .replace(/(?/g, '>') + .replace(/\\(?!`)/g, '\\\\') }, + + map: function(text, func){ + return text.replace(this.pattern, func) }, + + replace: function(text, index, updated){ + return this.map(text, + function(match, language, code){ + return index-- != 0 ? + match + : ('```'+language + + (typeof(updated) == 'function' ? + updated(code) + : updated) + +'```') }) }, + + toHTML: function(text){ + return this.map(text, this.handler) }, +} + + + //--------------------------------------------------------------------- // XXX experiment with a concatinative model... @@ -101,12 +151,13 @@ var Outline = { // groups defaulting to .focused as base... if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){ return this.get('focused', node) } - // helpers... var parent = function(node){ return node === outline ? - node - : node?.parentElement?.parentElement } + outline + : node.parentElement === outline ? + outline + : node.parentElement.parentElement } var children = function(node){ return node === outline ? [...node.children] @@ -337,11 +388,6 @@ var Outline = { .replace(/\\(?!`)/g, '\\\\') } var quote = function(_, code){ return `${quoteText(code)}` } - var pre = function(_, language, code){ - language = language ? - 'language-'+language - : language - return `
${ quoteText(code) }
` } var table = function(_, body){ return `
${ body @@ -349,78 +395,123 @@ var Outline = { .replace(/\s*\|\s*/gm, '') }
` } - elem.text = code - // 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 '' }) - // markdown... - // style: headings... - .replace(/^(?\s+(.*)$/m, style('quote')) - .replace(/^\s*(?$2$3') - // elements... - .replace(/(\n|^)(?') - // ToDo... - // NOTE: these are separate as we need to align block text - // to leading chekbox... - .replace(/^\s*(?')) - .replace(/^\s*(?')) - // inline checkboxes... - .replace(/\s*(?')) - .replace(/\s*(?')) - // tables... - .replace(/^\s*(?$1') - .replace(/(?$1') - .replace(/(?$1') - // code/quoting... - .replace(/(?$1') - // links... - .replace(/(?$1') - .replace(/((?:https?:|ftps?:)[^\s]*)(\s*)/g, '$1$2') - // characters... - // XXX use ligatures for these??? - .replace(/(?\s+(.*)$/m, style('quote')) + .replace(/^\s*(?$2$3') + .replace(/(\s*)(?$2$3') + // elements... + .replace(/(\n|^)(?') + // ToDo... + // NOTE: these are separate as we need to align block text + // to leading chekbox... + .replace(/^\s*(?')) + .replace(/^\s*(?')) + // inline checkboxes... + .replace(/\s*(?')) + .replace(/\s*(?')) + // tables... + .replace(/^\s*(?$1') + .replace(/(?$1') + .replace(/(?$1') + // code/quoting... + //.replace(/(?$1') + // links... + .replace(/(?$1') + .replace(/((?:https?:|ftps?:)[^\s]*)(\s*)/g, '$1$2') + // characters... + // XXX use ligatures for these??? + .replace(/(? , ... ] + var pattern = /(<(pre|code)(?:|\s[^>]*)>((?:\n|.)*)<\/\2>)/g + var sections = + quoteParse( + blockParse( + preParse(text + .replace(/\x00/g, '')))) + .split(pattern) + // 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) } + // parse only the parsable sections... + return postParse( + inlineParse( + parsable + .join('\x00')) + .split(/\x00/g) + .map(function(section){ + return [section, quoted.shift() ?? ''] }) + .flat() + .join('')) } + + elem.text = parse(code) + return elem }, // XXX essentially here we need to remove service stuff like some // attributes (collapsed, id, ...)... @@ -482,7 +573,7 @@ var Outline = { text = text .replace(/^\s*\n/, '') text = ('\n' + text) - .split(/\n(\s*)- /g) + .split(/\n(\s*)(?:- |-\s*$)/gm) .slice(1) var tab = ' '.repeat(this.tab_size || 8) var level = function(lst, prev_sep=undefined, parent=[]){ @@ -747,6 +838,15 @@ var Outline = { if(elem.classList.contains('children')){ return } + // empty outline -> create new eleemnt... + if(elem.classList.contains('outline') + && elem.children.length == 0){ + // create new eleemnt and edit it... + var block = that.Block() + that.outline.append(block) + that.edit(block) + return } + // expand/collapse if(elem.classList.contains('view') && elem.parentElement.getAttribute('tabindex')){ @@ -783,10 +883,14 @@ var Outline = { to : m } text.value = text.value.replace(/\[[Xx_]\]/g, toggle) } }) - // heboard handling... + // keyboard handling... outline.addEventListener('keydown', function(evt){ var elem = evt.target + // code editing... + if(elem.nodeName == 'CODE' + && elem.getAttribute('contenteditable') == 'true'){ + return } // update element state... if(elem.nodeName == 'TEXTAREA'){ setTimeout(function(){ @@ -795,6 +899,28 @@ var Outline = { // handle keyboard... evt.key in that.keyboard && that.keyboard[evt.key].call(that, evt) }) + // update code block... + outline.addEventListener('keyup', + function(evt){ + var elem = evt.target + // editable code... + if(elem.nodeName == 'CODE' + && elem.getAttribute('contenteditable') == 'true'){ + // XXX should we clear the syntax??? + // XXX do this only if things changed... + delete elem.dataset.highlighted + + var block = that.get(elem) + var code = block.querySelector('.code') + + var update = elem.innerText + var i = [...block + .querySelectorAll('.view code[contenteditable=true]')] + .indexOf(elem) + // update element content... + code.value = codeBlock.replace(code.value, i, update) + + return } }) // toggle view/code of nodes... outline.addEventListener('focusin', function(evt){ @@ -829,7 +955,8 @@ var Outline = { // XXX do a plugin... window.hljs - && hljs.highlightAll() }) + && hljs.highlightAll() + }) // update .code... var update_code_timeout outline.addEventListener('change', @@ -870,6 +997,10 @@ var Outline = { .replace(/>/g, '>')) console.log(`Parse: ${Date.now() - t}ms`)} + // XXX do a plugin... + window.hljs + && hljs.highlightAll() + return this }, } diff --git a/experiments/outline-editor/index.html b/experiments/outline-editor/index.html index d5044f5..bc16cfa 100755 --- a/experiments/outline-editor/index.html +++ b/experiments/outline-editor/index.html @@ -26,11 +26,7 @@ window.editor = { __proto__: Outline, }.setup( - document.querySelector('.editor')) - - // XXX make this a plugin... - window.hljs - && hljs.highlightAll() } + document.querySelector('.editor')) } @@ -56,19 +52,41 @@ an we'll not get here... - - ## ToDo: - - ASAP: editor: bksapce/del at start/end of a block should join it with prev/next + - ASAP: editor: backsapce/del at start/end of a block should join it with prev/next - ASAP: editor: pressing enter in text edit mode should split text into two blocks - ASAP: editor: shifting nodes up/down - - ASAP: use \\t for indent... - ASAP: scroll into view is bad... - - on item click, place the cursor where it was clicked before the code expanded... - - ~editor: semi-live update styles~ - - need to reach checkboxes via keyboard - - persistent empty first/last node (a button to create a new node) - - add completion percentage to blocks with todo's nested - - _...use `[%]`, `%%`, or something similar..._ - - read-only mode - - should bulets be on the same level as nodes or offset?? + - ASAP: need to reach checkboxes via keyboard + - FEATURE: "percentage complete" in parent blocks with todo's nested + - _...use `[%]` (preferred), `%%`, or something similar..._ + - FEATURE: read-only mode + - FEATURE: `collapse-children:: true` block option -- when loading collapse all immediate children + - FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)` + - Code blocks and bullets: + - ``` + code + ``` + - _bullet should be either in the middle of the block or at the first line of code (preferred)..._ + - custom element... + - Nerd fonts (option???) + - multiple node selection + - copy/paste nodes/trees + - undo + collapsed:: true + - delete node + - indent/deindent + - edit node + - Q: can we get the caret line in a textarea??? + - _...this will fix a lot of issues with moving between blocks in edit mode..._ + - Q: do we use \\t for indent? (option???) + - Q: can we place the cursor on item click where it was clicked before before the code expanded? + collapsed:: true + - for example + - #### Click in this line and see where the cursor goes + - _not sure how..._ + - Q: persistent empty first/last node (a button to create a new node)? + - Q: should bullets be on the same level as nodes or offset?? + collapsed:: true - A) justified to bullet: * list item * list item @@ -78,8 +96,10 @@ * list item block text - NOTE: this is only a problem if making list-items manually -- disable??? - - FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)` - - Nerd fonts (options?) + - ~Q: can we edit code in a code block directly? (a-la Logseq)~ + - empty item height is a bit off... + - ~`.editor .outline:empty` view and behavior...~ + - ~editor: semi-live update styles~ - ~do a better expand/collapse icons~ - ~loading from DOM -- fill textarea~ - ~focus management~ @@ -89,17 +109,29 @@ - ~shift subtree up/down~ - ~create node~ - ~edit node~ - - multiple node selection - - copy/paste nodes/trees - - undo - collapsed:: true - - delete node - - indent/deindent - - edit node - - empty item height is a bit off... - ~serialize/deserialize~ - ~add optional text styling to nodes~ - +- ## Refactoring: + - Item parser (`.__code2html__(..)`) + - split out + - define api + - define a way to extend/stack parsers + _...add wikiwords, ..._ + - Format parser/generator + - split out + - define api + - experiment with clean markdown as format + - CSS + - separate out theming + - separate out settings + - Actions -- move user actions (code in `.keyboard`) into methods + - Move to `keyboard.js` + - Plugin architecture + - Q: do we need `features.js` and/or `actions.js` + - Q: do we need a concatenative API?? + - `.get() -> ` + - - ## TEST - ### Formatting: - Styles