refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-10-12 19:45:12 +03:00
parent 71cb3f1f97
commit 9eddf696c4
3 changed files with 315 additions and 148 deletions

View File

@ -29,7 +29,7 @@
/*text-size-adjust: none;*/ /*text-size-adjust: none;*/
text-size-adjust: 150%; text-size-adjust: 150%;
/*scroll-behavior: smooth;*/
} }
.editor { .editor {
@ -49,6 +49,9 @@
display: none; display: none;
} }
.editor .children {
}
.editor .outline { .editor .outline {
display: block; display: block;
position: relative; position: relative;
@ -60,19 +63,18 @@
padding-right: var(--outline-padding); padding-right: var(--outline-padding);
} }
.editor .outline [tabindex] { .editor .outline .block {
position: relative; position: relative;
outline: none; outline: none;
border: none; border: none;
} }
.editor .outline [tabindex] [tabindex] { .editor .outline .block .block {
margin-left: var(--item-indent); margin-left: var(--item-indent);
} }
.editor .outline [tabindex]>span, .editor .outline .block>.text {
.editor .outline [tabindex]>textarea {
display: block; display: block;
width: 100%; width: 100%;
/* XXX this is a tiny bit off and using textarea's height here is off too... */ /* XXX this is a tiny bit off and using .code's height here is off too... */
min-height: calc(1em + var(--item-padding) * 2); min-height: calc(1em + var(--item-padding) * 2);
padding-top: var(--item-padding); padding-top: var(--item-padding);
padding-bottom: var(--item-padding); padding-bottom: var(--item-padding);
@ -89,60 +91,58 @@
border: none; border: none;
} }
/* show/hide node's view/code... */ /* show/hide node's view/code... */
/*.editor .outline [tabindex]>textarea:focus+span,*/ /*.editor .outline .block>.code:focus+.view,*/
.editor .outline [tabindex]>textarea:not(:focus) { .editor .outline .block>.code:not(:focus) {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
} }
/* hide span content but show before/after -- keep bulets and touch zones... */ /* hide .view content but show before/after -- keep bulets and touch zones... */
.editor .outline [tabindex]>textarea:focus+span { .editor .outline .block>.code:focus+.view {
position: absolute; position: absolute;
top: 0; top: 0;
visibility: hidden; visibility: hidden;
} }
.editor .outline [tabindex]>textarea:focus+span:before, .editor .outline .block>.code:focus+.view:before,
.editor .outline [tabindex]>textarea:focus+span:after { .editor .outline .block>.code:focus+.view:after {
visibility: visible; visibility: visible;
} }
/* click through the span text to the textarea */ /* click through the .view text to the .code */
.editor .outline [tabindex]>span { .editor .outline .block>.view {
position: relative; position: relative;
pointer-events: none; pointer-events: none;
} }
/* block hover... */ /* block hover... */
.editor .outline [tabindex]:hover>span { .editor .outline .block:hover>.view {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
rgba(0,0,0,0.01) 0%, rgba(0,0,0,0.01) 0%,
rgba(0,0,0,0.01) 80%, rgba(0,0,0,0.01) 80%,
rgba(0,0,0,0.03) 100%); rgba(0,0,0,0.03) 100%);
} }
.editor .outline [tabindex]>span:blank { .editor .outline .block>.view:blank {
content: " "; content: " ";
} }
.editor .outline [tabindex]>textarea { .editor .outline .block>.code {
height: calc(2 * var(--item-padding) + 1em); height: calc(2 * var(--item-padding) + 1em);
overflow: hidden; overflow: hidden;
resize: none; resize: none;
} }
/* clickable things in view */ /* clickable things in view */
.editor .outline [tabindex]>span a, .editor .outline .block>.view a,
.editor .outline [tabindex]>span input { .editor .outline .block>.view input {
pointer-events: auto; pointer-events: auto;
} }
/* focus... */ /* focus... */
editor .outline [tabindex]:focus { editor .outline .block:focus {
/*outline: solid 0.2em silver;*/ /*outline: solid 0.2em silver;*/
outline: none; outline: none;
} }
.editor .outline [tabindex]:focus>span, .editor .outline .block:focus>.text {
.editor .outline [tabindex]:focus>textarea {
background: rgba(0,0,0,0.1); background: rgba(0,0,0,0.1);
} }
.editor .outline [tabindex].focused:not(:focus)>span, .editor .outline .block.focused:not(:focus)>.text {
.editor .outline [tabindex].focused:not(:focus)>textarea {
background: rgba(0,0,0,0.01); background: rgba(0,0,0,0.01);
} }
@ -151,8 +151,8 @@ editor .outline [tabindex]:focus {
} }
/* click/tap zones for expand button... */ /* click/tap zones for expand button... */
.editor .outline [tabindex]>span:before, .editor .outline .block>.view:before,
.editor .outline [tabindex]>span:after { .editor .outline .block>.view:after {
--size: 3rem; --size: 3rem;
content: ""; content: "";
@ -185,23 +185,23 @@ editor .outline [tabindex]:focus {
background: transparent; background: transparent;
} }
/* left indicator */ /* left indicator */
.editor .outline [tabindex]>span:before { .editor .outline .block>.view:before {
justify-content: right; justify-content: right;
left: calc(-1 * var(--size)); left: calc(-1 * var(--size));
} }
/* right indicator (collapse/expand) */ /* right indicator (collapse/expand) */
.editor .outline [tabindex]>span:after { .editor .outline .block>.view:after {
color: silver; color: silver;
} }
.editor .outline [tabindex]:has([tabindex])>span:after { .editor .outline .block:has(.block)>.view:after {
content: "○"; content: "○";
} }
.editor .outline [tabindex][collapsed]>span:after { .editor .outline .block[collapsed]>.view:after {
content: "●"; content: "●";
} }
/* collapse -- hide children... */ /* collapse -- hide children... */
.editor .outline [tabindex][collapsed] [tabindex] { .editor .outline .block[collapsed] .block {
display: none; display: none;
} }
@ -242,58 +242,42 @@ editor .outline [tabindex]:focus {
.editor .outline .heading-6 { .editor .outline .heading-6 {
margin-top: 1rem; margin-top: 1rem;
} }
.editor .outline .heading-1>span, .editor .outline .heading-1>.text,
.editor .outline .heading-1>textarea, .editor .outline .heading-2>.text,
.editor .outline .heading-2>span, .editor .outline .heading-3>.text,
.editor .outline .heading-2>textarea, .editor .outline .heading-4>.text,
.editor .outline .heading-3>span, .editor .outline .heading-5>.text,
.editor .outline .heading-3>textarea, .editor .outline .heading-6>.text {
.editor .outline .heading-4>span,
.editor .outline .heading-4>textarea,
.editor .outline .heading-5>span,
.editor .outline .heading-5>textarea,
.editor .outline .heading-6>span,
.editor .outline .heading-6>textarea {
font-weight: bold; font-weight: bold;
} }
.editor .outline .heading-1>span, .editor .outline .heading-1>.text,
.editor .outline .heading-1>textarea, .editor .outline .heading-2>.text,
.editor .outline .heading-2>span, .editor .outline .heading-3>.text {
.editor .outline .heading-2>textarea,
.editor .outline .heading-3>span,
.editor .outline .heading-3>textarea {
border-bottom: solid 1px rgba(0,0,0,0.1); border-bottom: solid 1px rgba(0,0,0,0.1);
} }
.editor .outline .heading-1>span, .editor .outline .heading-1>.text {
.editor .outline .heading-1>textarea {
--font-size: 2.5em; --font-size: 2.5em;
} }
.editor .outline .heading-2>span, .editor .outline .heading-2>.text {
.editor .outline .heading-2>textarea {
--font-size: 1.9em; --font-size: 1.9em;
} }
.editor .outline .heading-3>span, .editor .outline .heading-3>.text {
.editor .outline .heading-3>textarea {
--font-size: 1.5em; --font-size: 1.5em;
} }
.editor .outline .heading-4>span, .editor .outline .heading-4>.text {
.editor .outline .heading-4>textarea {
--font-size: 1.3em; --font-size: 1.3em;
} }
.editor .outline .heading-5>span, .editor .outline .heading-5>.text {
.editor .outline .heading-5>textarea {
--font-size: 1.1em; --font-size: 1.1em;
} }
.editor .outline .heading-6>span, .editor .outline .heading-6>.text {
.editor .outline .heading-6>textarea {
--font-size: 1em; --font-size: 1em;
} }
/* Quote... */ /* Quote... */
.editor .outline .quote>span, .editor .outline .quote>.text {
.editor .outline .quote>textarea {
--indent: 1rem; --indent: 1rem;
--margin: 0.7rem; --margin: 0.7rem;
--item-padding-ratio: 0.7; --item-padding-ratio: 0.7;
@ -315,33 +299,66 @@ editor .outline [tabindex]:focus {
/* List... */ /* List... */
/* XXX needs to be in the middle of the first span but with universal size... */ /* XXX needs to be in the middle of the first .view but with universal size... */
.editor .outline .list-item>span:before, .editor .outline .list-item>.view:before,
.editor .outline .list>[tabindex]>span:not(:empty):before { .editor .outline .list>.children>.block>.view:not(:empty):before {
content: "◼"; content: "◼";
color: gray; color: gray;
} }
.editor .outline .list>.list>[tabindex]>span:not(:empty):before { .editor .outline
.list>.children
>.list>.children>.block>.view:not(:empty):before {
content: "●"; content: "●";
} }
.editor .outline .list>.list>.list>[tabindex]>span:not(:empty):before { .editor .outline
.list>.children
>.list>.children
>.list>.children>.block>.view:not(:empty):before {
content: "○"; content: "○";
} }
.editor .outline .list>.list>.list>.list>[tabindex]>span:not(:empty):before { .editor .outline
.list>.children
>.list>.children
>.list>.children
>.list>.children>.block>.view:not(:empty):before {
content: "▪"; content: "▪";
} }
/* List... */ /* List... */
/* XXX nested lists are broken -- seems that I need a container for the children... */ /* XXX nested lists are broken -- seems that I need a container for the children... */
.editor .outline .numbered-list { .editor .outline .numbered-list>.children {
counter-reset: numbered-list; counter-reset: numbered-list;
} }
.editor .outline .numbered-list>[tabindex]>span:not(:empty):before { .editor .outline .numbered-list>.children>.block>.view:not(:empty):before {
counter-increment: numbered-list; counter-increment: numbered-list;
content: counter(numbered-list) "."; content: counter(numbered-list) ".";
color: gray; color: gray;
} }
.editor .outline
.numbered-list>.children
>.numbered-list>.children>.block>.view:not(:empty):before {
counter-increment: numbered-list;
content: counter(numbered-list, lower-alpha) ".";
color: gray;
}
.editor .outline
.numbered-list>.children
>.numbered-list>.children
>.numbered-list>.children>.block>.view:not(:empty):before {
counter-increment: numbered-list;
content: counter(numbered-list, lower-roman) ".";
color: gray;
}
.editor .outline
.numbered-list>.children
>.numbered-list>.children
>.numbered-list>.children
>.numbered-list>.children>.block>.view:not(:empty):before {
counter-increment: numbered-list;
content: counters(numbered-list, ".") ".";
color: gray;
}
/* Notes... */ /* Notes... */
@ -356,20 +373,20 @@ editor .outline [tabindex]:focus {
background: rgba(0,0,0,0.05); background: rgba(0,0,0,0.05);
} }
/* XXX this prevents it from being accesible via click/tap... */ /* XXX this prevents it from being accesible via click/tap... */
.editor .outline .NOTE>span:empty { .editor .outline .NOTE>.view:empty {
display: none; display: none;
} }
.editor .outline .NOTE>span:empty ~ [tabindex] { .editor .outline .NOTE>.view:empty ~ .block {
/* XXX calculate this... */ /* XXX calculate this... */
margin-left: 1em; margin-left: 1em;
} }
.editor .outline .NOTE>span:before { .editor .outline .NOTE>.view:before {
content: "" !important; content: "" !important;
} }
/* correct the right click zone... */ /* correct the right click zone... */
/* XXX need to account for nesting... (???) */ /* XXX need to account for nesting... (???) */
.editor .outline [tabindex].NOTE>span:after, .editor .outline .block.NOTE>.view:after,
.editor .outline [tabindex].NOTE [tabindex]>span:after { .editor .outline .block.NOTE .block>.view:after {
margin-right: calc(-1 * var(--padding-h)); margin-right: calc(-1 * var(--padding-h));
} }
@ -379,7 +396,7 @@ editor .outline [tabindex]:focus {
font-weight: bold; font-weight: bold;
background: yellow; background: yellow;
} }
.editor .outline .XXX>span { .editor .outline .XXX>.view {
background: yellow; background: yellow;
} }
@ -388,13 +405,13 @@ editor .outline [tabindex]:focus {
.editor.hide-comments .outline .comment { .editor.hide-comments .outline .comment {
display: none; display: none;
} }
.editor .outline .comment>span { .editor .outline .comment>.view {
color: silver; color: silver;
} }
/* Checkboxes... */ /* Checkboxes... */
.editor .outline [tabindex].todo>span { .editor .outline .block.todo>.view {
width: calc( width: calc(
100% 100%
- var(--checkbox-size) - var(--checkbox-size)
@ -403,8 +420,8 @@ editor .outline [tabindex]:focus {
var(--checkbox-size) var(--checkbox-size)
+ var(--checkbox-margin)); + var(--checkbox-margin));
} }
.editor .outline [tabindex].check>span input[type=checkbox], .editor .outline .block.check>.view input[type=checkbox],
.editor .outline [tabindex].todo>span input[type=checkbox] { .editor .outline .block.todo>.view input[type=checkbox] {
height: var(--checkbox-size); height: var(--checkbox-size);
width: var(--checkbox-size); width: var(--checkbox-size);
@ -417,13 +434,13 @@ editor .outline [tabindex]:focus {
/* NOTE: this appears to be needed for the em sizes above to work correctly */ /* NOTE: this appears to be needed for the em sizes above to work correctly */
font-size: 1em; font-size: 1em;
} }
.editor .outline [tabindex].todo>span input[type=checkbox]:first-child { .editor .outline .block.todo>.view input[type=checkbox]:first-child {
margin-left: calc( margin-left: calc(
-1 * var(--checkbox-size) -1 * var(--checkbox-size)
- var(--checkbox-margin)); - var(--checkbox-margin));
} }
/* correct the left click zone... */ /* correct the left click zone... */
.editor .outline [tabindex].todo>span:before { .editor .outline .block.todo>.view:before {
margin-left: calc( margin-left: calc(
-1 * var(--checkbox-size) -1 * var(--checkbox-size)
- var(--checkbox-margin)); - var(--checkbox-margin));
@ -431,7 +448,7 @@ editor .outline [tabindex]:focus {
/* code... */ /* code... */
.editor .outline [tabindex]>span code { .editor .outline .block>.view code {
padding: 0.1em 0.3em; padding: 0.1em 0.3em;
font-family: monospace; font-family: monospace;
background: rgba(0,0,0,0.07); background: rgba(0,0,0,0.07);
@ -440,19 +457,19 @@ editor .outline [tabindex]:focus {
/* Tables... */ /* Tables... */
.editor .outline [tabindex]>span>table { .editor .outline .block>.view>table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
.editor .outline [tabindex]>span>table tr:nth-child(odd) { .editor .outline .block>.view>table tr:nth-child(odd) {
background: rgba(0,0,0,0.03); background: rgba(0,0,0,0.03);
} }
.editor .outline [tabindex]>span>table tr:first-child { .editor .outline .block>.view>table tr:first-child {
font-weight: bold; font-weight: bold;
border-bottom: solid 0.1rem silver; border-bottom: solid 0.1rem silver;
background: transparent; background: transparent;
} }
.editor .outline [tabindex]>span>table td { .editor .outline .block>.view>table td {
height: calc(1em + var(--item-padding) * 2); height: calc(1em + var(--item-padding) * 2);
padding: 0 1em; padding: 0 1em;
/*text-align: center;*/ /*text-align: center;*/
@ -461,13 +478,13 @@ editor .outline [tabindex]:focus {
/********************************************************* Testing ***/ /********************************************************* Testing ***/
.editor.show-click-zones .outline [tabindex]>span:before, .editor.show-click-zones .outline .block>.view:before,
.editor.show-click-zones .outline [tabindex]>span:after { .editor.show-click-zones .outline .block>.view:after {
background: rgba(0,0,0,0.03); background: rgba(0,0,0,0.03);
border: solid 1px silver; border: solid 1px silver;
} }
.editor.show-click-zones .outline [tabindex]:hover>span:before, .editor.show-click-zones .outline .block:hover>.view:before,
.editor.show-click-zones .outline [tabindex]:hover>span:after { .editor.show-click-zones .outline .block:hover>.view:after {
background: rgba(0,0,0,0.1); background: rgba(0,0,0,0.1);
} }

View File

@ -82,6 +82,111 @@ var Outline = {
// //
// XXX add support for node ID... // XXX add support for node ID...
// XXX need to be able to get the next elem on same level... // XXX need to be able to get the next elem on same level...
get: function(node='focused', offset){
var that = this
offset =
offset == 'next' ?
1
: offset == 'prev' ?
-1
: offset
var outline = this.outline
// root nodes...
if(node == 'top'){
return [...outline.children] }
// groups defaulting to .outline as base...
if(['all', 'visible', 'editable', 'selected'].includes(node)){
return this.get(outline, node) }
// 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 }
var children = function(node){
return node === outline ?
[...node.children]
: [...node?.lastChild?.children] }
// single base node...
var edited
;[node, edited] =
typeof(node) == 'number' ?
[this.get('visible').at(node),
edited]
: (node == 'outline' || node == 'root') ?
[outline, edited]
: node == 'focused' ?
[outline.querySelector(`.block:focus`)
|| outline.querySelector(`.code:focus`)
|| outline.querySelector('.block.focused'),
edited]
: node == 'edited' ?
[outline.querySelector(`.code:focus`),
outline.querySelector(`.code:focus`)]
: [node , edited]
// get the .block...
if(node instanceof HTMLElement){
while(node !== outline
&& !node.classList.contains('block')){
node = node.parentElement } }
// no reference node...
if(node == null
|| typeof(node) == 'string'){
return undefined }
// parent...
if(offset == 'parent'){
return edited ?
parent(node).querySelector('.code')
: parent(node) }
// node groups...
var nodes =
typeof(offset) == 'number' ?
this.get('visible')
: offset == 'all' ?
[...node.querySelectorAll('.block')]
: offset == 'visible' ?
[...node.querySelectorAll('.block')]
.filter(function(e){
return e.offsetParent != null })
: offset == 'editable' ?
[...node.querySelectorAll('.block>.code')]
: offset == 'selected' ?
[...node.querySelectorAll('.block[selected]')]
.filter(function(e){
return e.offsetParent != null })
: offset == 'children' ?
children(node)
: offset == 'siblings' ?
children(parent(node))
: undefined
// get node by offset...
if(typeof(offset) == 'number'){
node = nodes.at(nodes.indexOf(node) + offset)
?? nodes[0]
edited = edited ?
node.querySelector('.code')
: edited
nodes = undefined }
return nodes !== undefined ?
edited ?
nodes
.map(function(){
return node.querySelector('.code') })
: nodes
: (edited
?? node) },
/*/
get: function(node='focused', offset){ get: function(node='focused', offset){
var that = this var that = this
@ -93,10 +198,15 @@ var Outline = {
var outline = this.outline var outline = this.outline
var parent = function(node){
return node?.parentElement?.parentElement }
var children = function(node){
return [...node?.lastChild?.children] }
// get parent node... // get parent node...
if(node instanceof HTMLElement){ if(node instanceof HTMLElement){
while(!node.getAttribute('tabindex')){ while(!node.classList.contains('block')){
node = node.parentElement node = node.parentElement
if(node === this.outline){ if(node === this.outline){
return undefined } } } return undefined } } }
@ -104,15 +214,15 @@ var Outline = {
var NO_NODES = {} var NO_NODES = {}
var nodes = var nodes =
node == 'all' ? node == 'all' ?
[...outline.querySelectorAll('[tabindex]')] [...outline.querySelectorAll('.block')]
: node == 'visible' ? : node == 'visible' ?
[...outline.querySelectorAll('[tabindex]')] [...outline.querySelectorAll('.block')]
.filter(function(e){ .filter(function(e){
return e.offsetParent != null }) return e.offsetParent != null })
: node == 'editable' ? : node == 'editable' ?
[...outline.querySelectorAll('[tabindex]>textarea')] [...outline.querySelectorAll('.block>textarea')]
: node == 'selected' ? : node == 'selected' ?
[...outline.querySelectorAll('[tabindex][selected]')] [...outline.querySelectorAll('.block[selected]')]
: node == 'top' ? : node == 'top' ?
[...outline.children] [...outline.children]
.filter(function(elem){ .filter(function(elem){
@ -136,11 +246,11 @@ var Outline = {
typeof(node) == 'number' ? typeof(node) == 'number' ?
this.at(node) this.at(node)
: node == 'focused' ? : node == 'focused' ?
(outline.querySelector(`[tabindex]:focus`) (outline.querySelector(`.block:focus`)
|| outline.querySelector(`textarea:focus`)?.parentElement || outline.querySelector(`textarea:focus`)?.parentElement
|| outline.querySelector('[tabindex].focused')) || outline.querySelector('.block.focused'))
: node == 'parent' ? : node == 'parent' ?
this.get('focused')?.parentElement parent(this.get('focused'))
: node : node
var edited var edited
if(node == 'edited'){ if(node == 'edited'){
@ -152,15 +262,11 @@ var Outline = {
// children... // children...
if(offset == 'children'){ if(offset == 'children'){
return [...node.children] return children(node) }
.filter(function(elem){
return elem.getAttribute('tabindex') != null }) }
// siblings... // siblings...
if(offset == 'siblings'){ if(offset == 'siblings'){
return [...node.parentElement.children] return children(parent(node)) }
.filter(function(elem){
return elem.getAttribute('tabindex') != null }) }
// offset... // offset...
offset = offset =
@ -177,9 +283,10 @@ var Outline = {
: i % nodes.length : i % nodes.length
node = nodes[i] node = nodes[i]
edited = edited edited = edited
&& node.querySelector('textarea') } && node.querySelector('.code') }
return edited return edited
|| node }, || node },
//*/
at: function(index, nodes='visible'){ at: function(index, nodes='visible'){
return this.get(nodes).at(index) }, return this.get(nodes).at(index) },
focus: function(node='focused', offset){ focus: function(node='focused', offset){
@ -229,20 +336,30 @@ var Outline = {
var siblings = this.get(node, 'siblings') var siblings = this.get(node, 'siblings')
// deindent... // deindent...
if(!indent){ if(!indent){
var parent = cur.parentElement var parent = this.get(node, 'parent')
if(!parent.classList.contains('.outline')){ if(!parent.classList.contains('.outline')){
var children = siblings.slice(siblings.indexOf(cur)+1) var children = siblings
.slice(siblings.indexOf(cur)+1)
parent.after(cur) parent.after(cur)
children.length > 0 children.length > 0
&& cur.append(...children) } && cur.lastChild.append(...children) }
// indent... // indent...
} else { } else {
var parent = siblings[siblings.indexOf(cur) - 1] var parent = siblings[siblings.indexOf(cur) - 1]
if(parent){ if(parent){
parent.append(cur) } } parent.lastChild.append(cur) } }
return cur }, return cur },
deindent: function(node='focused', indent=false){ deindent: function(node='focused', indent=false){
return this.indent(node, indent) }, return this.indent(node, indent) },
show: function(node='focused', offset){
var node = this.get(...arguments)
var outline = this.outline
var parent = node
do{
parent = parent.parentElement
parent.removeAttribute('collapsed')
} while(parent !== outline)
return node },
toggleCollapse: function(node='focused', state='next'){ toggleCollapse: function(node='focused', state='next'){
var that = this var that = this
if(node == 'all'){ if(node == 'all'){
@ -256,7 +373,7 @@ var Outline = {
node = this.get(node) node = this.get(node)
if(!node if(!node
// only nodes with children can be collapsed... // only nodes with children can be collapsed...
|| !node.querySelector('[tabindex]')){ || !node.querySelector('.block')){
return } return }
state = state == 'next' ? state = state == 'next' ?
node.getAttribute('collapsed') != '' node.getAttribute('collapsed') != ''
@ -417,12 +534,9 @@ var Outline = {
json: function(node){ json: function(node){
var that = this var that = this
node ??= this.outline node ??= this.outline
return [...node.children] return [...node.lastChild.children]
.map(function(elem){ .map(function(elem){
return elem.nodeName != 'DIV' ? return that.data(elem) }) },
[]
: [that.data(elem)] })
.flat() },
// XXX add option to customize indent size... // XXX add option to customize indent size...
text: function(node, indent, level){ text: function(node, indent, level){
// .text(<indent>, <level>) // .text(<indent>, <level>)
@ -489,16 +603,29 @@ var Outline = {
// XXX should this handle children??? // XXX should this handle children???
// XXX revise name... // XXX revise name...
Block: function(data={}, place=null){ Block: function(data={}, place=null){
var that = this
if(typeof(data) != 'object'){ if(typeof(data) != 'object'){
place = data place = data
data = {} } data = {} }
// block...
var block = document.createElement('div') var block = document.createElement('div')
block.classList.add('block')
block.setAttribute('tabindex', '0') block.setAttribute('tabindex', '0')
var text = document.createElement('textarea') // code...
var code = document.createElement('textarea')
.autoUpdateSize() .autoUpdateSize()
code.classList.add('code', 'text')
// view...
var html = document.createElement('span') var html = document.createElement('span')
block.append(text, html) html.classList.add('view', 'text')
// children...
var children = document.createElement('div')
children.classList.add('children')
children.setAttribute('tabindex', '-1')
block.append(code, html, children)
this.update(block, data) this.update(block, data)
// place... // place...
var cur = this.get() var cur = this.get()
if(place && cur){ if(place && cur){
@ -506,7 +633,7 @@ var Outline = {
'before' 'before'
: place : place
;(place == 'next' ;(place == 'next'
&& (cur.querySelector('[tabindex]') && (cur.querySelector('.block')
|| cur.nextElementSibling)) ? || cur.nextElementSibling)) ?
this.get(place).before(block) this.get(place).before(block)
: (place == 'next' : (place == 'next'
@ -527,7 +654,8 @@ var Outline = {
.map(function(data){ .map(function(data){
var elem = that.Block(data) var elem = that.Block(data)
if((data.children || []).length > 0){ if((data.children || []).length > 0){
elem.append(...level(data.children)) } elem.lastChild
.append(...level(data.children)) }
return elem }) } return elem }) }
this this
.clear() .clear()
@ -616,19 +744,19 @@ var Outline = {
return } return }
if(this.right_key_expands){ if(this.right_key_expands){
this.toggleCollapse(false) this.toggleCollapse(false)
var child = this.focus('children')[0] this.focus('next')
if(!child){
this.focus('next') }
} else { } else {
evt.shiftKey ? evt.shiftKey ?
this.toggleCollapse(false) this.toggleCollapse(false)
: this.get('children')[0]?.focus() } }, : this.focus('next') } },
// indent... // indent...
Tab: function(evt){ Tab: function(evt){
evt.preventDefault() evt.preventDefault()
var edited = this.get('edited') var edited = this.get('edited')
var node = this.indent(!evt.shiftKey) var node = this.show(
this.indent(!evt.shiftKey))
// keep focus in node...
;(edited ? ;(edited ?
edited edited
: node)?.focus() }, : node)?.focus() },
@ -698,8 +826,11 @@ var Outline = {
function(evt){ function(evt){
var elem = evt.target var elem = evt.target
if(elem.classList.contains('children')){
return }
// expand/collapse // expand/collapse
if(elem.nodeName == 'SPAN' if(elem.classList.contains('view')
&& elem.parentElement.getAttribute('tabindex')){ && elem.parentElement.getAttribute('tabindex')){
// click: left of elem (outside) // click: left of elem (outside)
if(evt.offsetX < 0){ if(evt.offsetX < 0){
@ -718,12 +849,12 @@ var Outline = {
// NOTE: this is usefull if element text is hidden but the // NOTE: this is usefull if element text is hidden but the
// frame is still visible... // frame is still visible...
if(elem.getAttribute('tabindex')){ if(elem.getAttribute('tabindex')){
elem.querySelector('textarea').focus() } elem.querySelector('.code').focus() }
// toggle checkbox... // toggle checkbox...
if(elem.type == 'checkbox'){ if(elem.type == 'checkbox'){
var node = that.get(elem) var node = that.get(elem)
var text = node.querySelector('textarea') var text = node.querySelector('.code')
// get the checkbox order... // get the checkbox order...
var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem) var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem)
var to = elem.checked ? var to = elem.checked ?
@ -749,25 +880,27 @@ var Outline = {
// toggle view/code of nodes... // toggle view/code of nodes...
outline.addEventListener('focusin', outline.addEventListener('focusin',
function(evt){ function(evt){
var node = evt.target var elem = evt.target
// scroll...
// XXX a bit odd still and not smooth... if(elem.classList.contains('children')){
;((node.nodeName == 'SPAN' return }
|| node.nodeName == 'TEXTAREA') ?
node
: node.querySelector('textarea+span'))
?.scrollIntoView({
block: 'nearest',
behavior: 'smooth',
})
// handle focus... // handle focus...
for(var e of [...that.dom.querySelectorAll('.focused')]){ for(var e of [...that.dom.querySelectorAll('.focused')]){
e.classList.remove('focused') } e.classList.remove('focused') }
that.get('focused')?.classList?.add('focused') that.get('focused')?.classList?.add('focused')
// textarea... // textarea...
if(node.nodeName == 'TEXTAREA' if(elem.classList.contains('code')){
&& node?.nextElementSibling?.nodeName == 'SPAN'){ elem.updateSize() }
node.updateSize() } })
/*/ scroll...
that.get(node).querySelector('view')
?.scrollIntoView({
block: 'nearest',
behavior: 'smooth',
})
//*/
})
outline.addEventListener('focusout', outline.addEventListener('focusout',
function(evt){ function(evt){
var node = evt.target var node = evt.target
@ -809,9 +942,11 @@ var Outline = {
// code... // code...
var code = this.code var code = this.code
if(code){ if(code){
var t = Date.now()
this.load(code.innerHTML this.load(code.innerHTML
.replace(/&lt;/g, '<') .replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')) } .replace(/&gt;/g, '>'))
console.log(`Parse: ${Date.now() - t}ms`)}
return this }, return this },
} }

View File

@ -36,7 +36,6 @@ var setup = function(){
- Bonsai - Bonsai
- -
- ## ToDo: - ## ToDo:
- BUG? odd/random focus jumps on refocusing page (can't reporduce yet)
- BUG? pressing down from a longer line will jump over a shorter line - BUG? pressing down from a longer line will jump over a shorter line
- here is the line to jump from, for example from here - here is the line to jump from, for example from here
an we'll not get here... an we'll not get here...
@ -100,13 +99,29 @@ var setup = function(){
- Text - Text
- Lists:: - Lists::
- bullet: - bullet:
- a - a:
collapsed:: true
- bullets:
- in:
- very:
- deep:
- list:
- of:
- items:
- b - b
- c - c
- numbered# - numbered#
- a - a
- b# - b#
- x - x#
collapsed:: true
- bullets#
- in#
- very#
- deep#
- list#
- of#
- items#
- y - y
- z - z
- c - c