lots of fixes + started major refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-10-13 22:14:36 +03:00
parent 53ad572369
commit 4690bc0770
3 changed files with 289 additions and 112 deletions

View File

@ -54,13 +54,23 @@
display: block; display: block;
position: relative; position: relative;
/* XXX do a better calculation... */
width: calc(100% - var(--button-size) - var(--outline-padding) * 2); width: calc(100% - var(--button-size) - var(--outline-padding) * 2);
padding-left: var(--outline-padding); padding: 1em var(--outline-padding);
padding-right: 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 { .editor .outline .block {
position: relative; position: relative;
outline: none; outline: none;
@ -110,6 +120,7 @@
pointer-events: none; pointer-events: none;
} }
/* block hover... */ /* block hover... */
.editor .outline:empty:hover:after,
.editor .outline .block:hover>.view { .editor .outline .block:hover>.view {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
@ -127,6 +138,7 @@
} }
/* clickable things in view */ /* clickable things in view */
.editor .outline .block>.view a, .editor .outline .block>.view a,
.editor .outline .block>.view pre,
.editor .outline .block>.view input { .editor .outline .block>.view input {
pointer-events: auto; pointer-events: auto;
} }
@ -138,7 +150,7 @@ editor .outline .block:focus {
outline: none; outline: none;
} }
.editor .outline .block:focus>.text { .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 { .editor .outline .block.focused:not(:focus)>.text {
background: rgba(0,0,0,0.01); background: rgba(0,0,0,0.01);
@ -464,11 +476,13 @@ editor .outline .block:focus {
font-family: monospace; font-family: monospace;
background: rgba(0,0,0,0.07); background: rgba(0,0,0,0.07);
border-radius: 0.2em; border-radius: 0.2em;
outline: none;
} }
.editor .outline .block>.view pre>code { .editor .outline .block>.view pre>code {
display: block; display: block;
padding: 0.6em 0.6em; padding: 0.6em 0.6em;
padding-bottom: 0.8em; padding-bottom: 0.8em;
outline: none;
} }

View File

@ -25,6 +25,56 @@ var atLine = function(elem, index){
//---------------------------------------------------------------------
var codeBlock = {
// can be used in:
// <string>.replace(codeBlock.pattern, codeBlock.handler)
// or:
// codeBlock
pattern: /(?<!\\)```(.*\s*\n)((\n|.)*?)\h*(?<!\\)```/g,
handler: function(_, language, code){
var quote = this?.quote
|| codeBlock.quote
language = language.trim()
language = language ?
'language-'+language
: language
return `<pre>`
+`<code contenteditable="true" class="${language}">${
quote ?
quote(code)
: code
}</code>`
+`</pre>` },
quote: function(text){
return text
.replace(/(?<!\\)&/g, '&amp;')
.replace(/(?<!\\)</g, '&lt;')
.replace(/(?<!\\)>/g, '&gt;')
.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... // XXX experiment with a concatinative model...
@ -101,12 +151,13 @@ var Outline = {
// groups defaulting to .focused as base... // groups defaulting to .focused as base...
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){ if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
return this.get('focused', node) } return this.get('focused', node) }
// helpers... // helpers...
var parent = function(node){ var parent = function(node){
return node === outline ? return node === outline ?
node outline
: node?.parentElement?.parentElement } : node.parentElement === outline ?
outline
: node.parentElement.parentElement }
var children = function(node){ var children = function(node){
return node === outline ? return node === outline ?
[...node.children] [...node.children]
@ -337,11 +388,6 @@ var Outline = {
.replace(/\\(?!`)/g, '\\\\') } .replace(/\\(?!`)/g, '\\\\') }
var quote = function(_, code){ var quote = function(_, code){
return `<code>${quoteText(code)}</code>` } return `<code>${quoteText(code)}</code>` }
var pre = function(_, language, code){
language = language ?
'language-'+language
: language
return `<pre><code class="${language}">${ quoteText(code) }</code></pre>` }
var table = function(_, body){ var table = function(_, body){
return `<table><tr><td>${ return `<table><tr><td>${
body body
@ -349,7 +395,8 @@ var Outline = {
.replace(/\s*\|\s*/gm, '</td><td>') .replace(/\s*\|\s*/gm, '</td><td>')
}</td></td></table>` } }</td></td></table>` }
elem.text = code var preParse = function(text){
return text
// hidden attributes... // hidden attributes...
// XXX make this generic... // XXX make this generic...
// collapsed... // collapsed...
@ -361,7 +408,9 @@ var Outline = {
.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/, .replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,
function(_, value){ function(_, value){
elem.id = value.trim() elem.id = value.trim()
return '' }) return '' }) }
var blockParse = function(text){
return text
// markdown... // markdown...
// style: headings... // style: headings...
.replace(/^(?<!\\)######\s+(.*)$/m, style('heading-6')) .replace(/^(?<!\\)######\s+(.*)$/m, style('heading-6'))
@ -374,12 +423,21 @@ var Outline = {
//.replace(/^(?<!\\)[-\*]\s+(.*)$/m, style('list-item')) //.replace(/^(?<!\\)[-\*]\s+(.*)$/m, style('list-item'))
.replace(/^\s*(.*)(?<!\\):\s*$/m, style('list')) .replace(/^\s*(.*)(?<!\\):\s*$/m, style('list'))
.replace(/^\s*(.*)(?<!\\)#\s*$/m, style('numbered-list')) .replace(/^\s*(.*)(?<!\\)#\s*$/m, style('numbered-list'))
// style: misc... // style: misc...
.replace(/^\s*(?<!\\)>\s+(.*)$/m, style('quote')) .replace(/^\s*(?<!\\)>\s+(.*)$/m, style('quote'))
.replace(/^\s*(?<!\\)((\/\/|;)\s+.*)$/m, style('comment')) .replace(/^\s*(?<!\\)((\/\/|;)\s+.*)$/m, style('comment'))
.replace(/^\s*(?<!\\)NOTE:?\s*(.*)$/m, style('NOTE')) .replace(/^\s*(?<!\\)NOTE:?\s*(.*)$/m, style('NOTE'))
.replace(/^\s*(?<!\\)XXX\s+(.*)$/m, style('XXX')) .replace(/^\s*(?<!\\)XXX\s+(.*)$/m, style('XXX'))
.replace(/^(.*)\s*(?<!\\)XXX$/m, style('XXX')) .replace(/^(.*)\s*(?<!\\)XXX$/m, style('XXX')) }
var quoteParse = function(text){
return text
.replace(codeBlock.pattern, codeBlock.handler)
.replace(/(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, quote) }
var inlineParse = function(text){
return text
.replace(/(\s*)(?<!\\)(FEATURE:|Q:|Question:|Note:)(\s*)/gm,
'$1<b class="$2">$2</b>$3')
.replace(/(\s*)(?<!\\)(ASAP|BUG|FIX|HACK|STUB|WARNING|CAUTION)(\s*)/gm, .replace(/(\s*)(?<!\\)(ASAP|BUG|FIX|HACK|STUB|WARNING|CAUTION)(\s*)/gm,
'$1<span class="highlight $2">$2</span>$3') '$1<span class="highlight $2">$2</span>$3')
// elements... // elements...
@ -404,8 +462,7 @@ var Outline = {
.replace(/(?<!\\)~(?=[^\s~])(([^~]|\\~)*[^\s~])(?<!\\)~/gm, '<s>$1</s>') .replace(/(?<!\\)~(?=[^\s~])(([^~]|\\~)*[^\s~])(?<!\\)~/gm, '<s>$1</s>')
.replace(/(?<!\\)_(?=[^\s_])(([^_]|\\_)*[^\s_])(?<!\\)_/gm, '<i>$1</i>') .replace(/(?<!\\)_(?=[^\s_])(([^_]|\\_)*[^\s_])(?<!\\)_/gm, '<i>$1</i>')
// code/quoting... // code/quoting...
.replace(/(?<!\\)```(.*)\s*\n((\n|.)*)\h*(?<!\\)```\s*/g, pre) //.replace(/(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, quote)
.replace(/(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, quote)
// XXX support "\==" in mark... // XXX support "\==" in mark...
.replace(/(?<!\\)==(?=[^\s])(.*[^\s])(?<!\\)==/gm, '<mark>$1</mark>') .replace(/(?<!\\)==(?=[^\s])(.*[^\s])(?<!\\)==/gm, '<mark>$1</mark>')
// links... // links...
@ -417,10 +474,44 @@ var Outline = {
.replace(/(?<!\\)\(c\)/gm, '©') .replace(/(?<!\\)\(c\)/gm, '©')
.replace(/(?<!\\)\/!\\/gm, '⚠') .replace(/(?<!\\)\/!\\/gm, '⚠')
.replace(/(?<!\\)---(?!-)/gm, '&mdash;') .replace(/(?<!\\)---(?!-)/gm, '&mdash;')
.replace(/(?<!\\)--(?!-)/gm, '&ndash;') .replace(/(?<!\\)--(?!-)/gm, '&ndash;') }
var postParse = function(text){
return text
// quoting... // quoting...
// NOTE: this must be last... // NOTE: this must be last...
.replace(/(?<!\\)\\(.)/gm, '$1') .replace(/(?<!\\)\\(.)/gm, '$1') }
var parse = function(text){
// split text into parsable and non-parsable sections...
// split fomat:
// [ text <match> <type> <body>, ... ]
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 }, return elem },
// XXX essentially here we need to remove service stuff like some // XXX essentially here we need to remove service stuff like some
// attributes (collapsed, id, ...)... // attributes (collapsed, id, ...)...
@ -482,7 +573,7 @@ var Outline = {
text = text text = text
.replace(/^\s*\n/, '') .replace(/^\s*\n/, '')
text = ('\n' + text) text = ('\n' + text)
.split(/\n(\s*)- /g) .split(/\n(\s*)(?:- |-\s*$)/gm)
.slice(1) .slice(1)
var tab = ' '.repeat(this.tab_size || 8) var tab = ' '.repeat(this.tab_size || 8)
var level = function(lst, prev_sep=undefined, parent=[]){ var level = function(lst, prev_sep=undefined, parent=[]){
@ -747,6 +838,15 @@ var Outline = {
if(elem.classList.contains('children')){ if(elem.classList.contains('children')){
return } 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 // expand/collapse
if(elem.classList.contains('view') if(elem.classList.contains('view')
&& elem.parentElement.getAttribute('tabindex')){ && elem.parentElement.getAttribute('tabindex')){
@ -783,10 +883,14 @@ var Outline = {
to to
: m } : m }
text.value = text.value.replace(/\[[Xx_]\]/g, toggle) } }) text.value = text.value.replace(/\[[Xx_]\]/g, toggle) } })
// heboard handling... // keyboard handling...
outline.addEventListener('keydown', outline.addEventListener('keydown',
function(evt){ function(evt){
var elem = evt.target var elem = evt.target
// code editing...
if(elem.nodeName == 'CODE'
&& elem.getAttribute('contenteditable') == 'true'){
return }
// update element state... // update element state...
if(elem.nodeName == 'TEXTAREA'){ if(elem.nodeName == 'TEXTAREA'){
setTimeout(function(){ setTimeout(function(){
@ -795,6 +899,28 @@ var Outline = {
// handle keyboard... // handle keyboard...
evt.key in that.keyboard evt.key in that.keyboard
&& that.keyboard[evt.key].call(that, evt) }) && 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... // toggle view/code of nodes...
outline.addEventListener('focusin', outline.addEventListener('focusin',
function(evt){ function(evt){
@ -829,7 +955,8 @@ var Outline = {
// XXX do a plugin... // XXX do a plugin...
window.hljs window.hljs
&& hljs.highlightAll() }) && hljs.highlightAll()
})
// update .code... // update .code...
var update_code_timeout var update_code_timeout
outline.addEventListener('change', outline.addEventListener('change',
@ -870,6 +997,10 @@ var Outline = {
.replace(/&gt;/g, '>')) .replace(/&gt;/g, '>'))
console.log(`Parse: ${Date.now() - t}ms`)} console.log(`Parse: ${Date.now() - t}ms`)}
// XXX do a plugin...
window.hljs
&& hljs.highlightAll()
return this }, return this },
} }

View File

@ -26,11 +26,7 @@ var setup = function(){
window.editor = { window.editor = {
__proto__: Outline, __proto__: Outline,
}.setup( }.setup(
document.querySelector('.editor')) document.querySelector('.editor')) }
// XXX make this a plugin...
window.hljs
&& hljs.highlightAll() }
</script> </script>
</head> </head>
@ -56,19 +52,41 @@ var setup = function(){
an we'll not get here... an we'll not get here...
- -
- ## ToDo: - ## 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: pressing enter in text edit mode should split text into two blocks
- ASAP: editor: shifting nodes up/down - ASAP: editor: shifting nodes up/down
- ASAP: use \\t for indent...
- ASAP: scroll into view is bad... - ASAP: scroll into view is bad...
- on item click, place the cursor where it was clicked before the code expanded... - ASAP: need to reach checkboxes via keyboard
- ~editor: semi-live update styles~ - FEATURE: "percentage complete" in parent blocks with todo's nested
- need to reach checkboxes via keyboard - _...use `[%]` (preferred), `%%`, or something similar..._
- persistent empty first/last node (a button to create a new node) - FEATURE: read-only mode
- add completion percentage to blocks with todo's nested - FEATURE: `collapse-children:: true` block option -- when loading collapse all immediate children
- _...use `[%]`, `%%`, or something similar..._ - FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)`
- read-only mode - Code blocks and bullets:
- should bulets be on the same level as nodes or offset?? - ```
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: - A) justified to bullet:
* list item * list item
* list item * list item
@ -78,8 +96,10 @@ var setup = function(){
* list item * list item
block text block text
- NOTE: this is only a problem if making list-items manually -- disable??? - 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(..)` - ~Q: can we edit code in a code block directly? (a-la Logseq)~
- Nerd fonts (options?) - empty item height is a bit off...
- ~`.editor .outline:empty` view and behavior...~
- ~editor: semi-live update styles~
- ~do a better expand/collapse icons~ - ~do a better expand/collapse icons~
- ~loading from DOM -- fill textarea~ - ~loading from DOM -- fill textarea~
- ~focus management~ - ~focus management~
@ -89,17 +109,29 @@ var setup = function(){
- ~shift subtree up/down~ - ~shift subtree up/down~
- ~create node~ - ~create node~
- ~edit 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~ - ~serialize/deserialize~
- ~add optional text styling to nodes~ - ~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??
- `<block>.get() -> <block>`
-
- ## TEST - ## TEST
- ### Formatting: - ### Formatting:
- Styles - Styles