Compare commits

..

No commits in common. "e41a78f5b080c6149212639045a0a4aaa993d305" and "d21d233b07c3199d394aa9f398193cc4e1b947d5" have entirely different histories.

3 changed files with 80 additions and 200 deletions

View File

@ -27,7 +27,7 @@
font-size: var(--font-size); font-size: var(--font-size);
/*text-size-adjust: none;*/ /*text-size-adjust: none;*/
text-size-adjust: 180%; text-size-adjust: 150%;
} }
.editor { .editor {

View File

@ -673,6 +673,7 @@ var JSONOutline = {
at: function(index){}, at: function(index){},
indent: function(){}, indent: function(){},
deindent: function(){},
shift: function(){}, shift: function(){},
show: function(){}, show: function(){},
toggleCollapse: function(){}, toggleCollapse: function(){},
@ -691,7 +692,6 @@ var JSONOutline = {
} }
// XXX experiment with a concatinative model... // XXX experiment with a concatinative model...
// .get(..) -> Outline (view) // .get(..) -> Outline (view)
var Outline = { var Outline = {
@ -1035,18 +1035,17 @@ var Outline = {
return node }, return node },
// edit... // edit...
indent: function(node='focused', indent='in'){ indent: function(node='focused', indent=true){
// .indent(<indent>) // .indent(<indent>)
if(node === 'in' || node === 'out'){ if(node === true || node === false){
indent = node indent = node
node = 'focused' } node = 'focused' }
var cur = this.get(node) var cur = this.get(node)
if(!cur){ if(!cur){
return } return }
var prev = this.path(cur)
var siblings = this.get(node, 'siblings') var siblings = this.get(node, 'siblings')
// deindent... // deindent...
if(indent == 'out'){ if(!indent){
var parent = this.get(node, 'parent') var parent = this.get(node, 'parent')
if(parent != this.outline){ if(parent != this.outline){
var children = siblings var children = siblings
@ -1054,31 +1053,21 @@ var Outline = {
parent.after(cur) parent.after(cur)
children.length > 0 children.length > 0
&& cur.lastChild.append(...children) && cur.lastChild.append(...children)
this.setUndo(
this.path(cur),
'indent',
['in'],
prev)
this.__change__() } this.__change__() }
// indent... // indent...
} else { } else {
var parent = siblings[siblings.indexOf(cur) - 1] var parent = siblings[siblings.indexOf(cur) - 1]
if(parent){ if(parent){
parent.lastChild.append(cur) parent.lastChild.append(cur)
this.setUndo(
this.path(cur),
'indent',
['out'],
prev)
this.__change__()} } this.__change__()} }
return cur }, return cur },
deindent: function(node='focused', indent=false){
return this.indent(node, indent) },
shift: function(node='focused', direction){ shift: function(node='focused', direction){
if(node == 'up' || node == 'down'){ if(node == 'up' || node == 'down'){
direction = node direction = node
node = 'focused' } node = 'focused' }
if(direction == null if(direction == null){
|| (direction !== 'up'
&& direction != 'down')){
return } return }
node = this.get(node) node = this.get(node)
var focused = node.classList.contains('focused') var focused = node.classList.contains('focused')
@ -1087,51 +1076,29 @@ var Outline = {
if(direction == 'up' if(direction == 'up'
&& i > 0){ && i > 0){
siblings[i-1].before(node) siblings[i-1].before(node)
} else if(direction == 'down'
&& i < siblings.length-1){
siblings[i+1].after(node) }
focused focused
&& this.focus() && this.focus()
this.setUndo( } else if(direction == 'down'
this.path(node), && i < siblings.length-1){
'shift', siblings[i+1].after(node)
[direction == 'up' ? focused
'down' && this.focus() }
: 'up'])
this.__change__() this.__change__()
return this }, return this },
// XXX make undo a bit more refined... show: function(node='focused', offset){
remove: function(node='focused'){ var node = this.get(...arguments)
var elem = this.get(...arguments) var outline = this.outline
// XXX HACK... var parent = node
var data = this.json() var changes = false
var next do{
if(elem.classList.contains('focused')){ parent = parent.parentElement
// XXX need to be able to get the next elem on same level... changes = changes
this.toggleCollapse(elem, true) || parent.getAttribute('collapsed') == ''
next = elem === this.get(-1) ? parent.removeAttribute('collapsed')
this.get(elem, 'prev') } while(parent !== outline)
: this.get(elem, 'next') } changes
elem?.remove() && this.__change__()
next return node },
&& this.focus(next)
// XXX HACK...
this.setUndo(
undefined,
'load',
[data])
this.__change__()
return this },
clear: function(){
this.setUndo(
undefined,
'load',
[this.json()])
this.outline.innerText = ''
this.__change__()
return this },
// expand/collapse...
toggleCollapse: function(node='focused', state='next'){ toggleCollapse: function(node='focused', state='next'){
var that = this var that = this
if(node == 'all'){ if(node == 'all'){
@ -1158,20 +1125,24 @@ var Outline = {
elem.updateSize() } } elem.updateSize() } }
this.__change__() this.__change__()
return node }, return node },
show: function(node='focused', offset){ remove: function(node='focused', offset){
var node = this.get(...arguments) var elem = this.get(...arguments)
var outline = this.outline var next
var parent = node if(elem.classList.contains('focused')){
var changes = false // XXX need to be able to get the next elem on same level...
do{ this.toggleCollapse(elem, true)
parent = parent.parentElement next = elem === this.get(-1) ?
changes = changes this.get(elem, 'prev')
|| parent.getAttribute('collapsed') == '' : this.get(elem, 'next') }
parent.removeAttribute('collapsed') elem?.remove()
} while(parent !== outline) next
changes && this.focus(next)
&& this.__change__() this.__change__()
return node }, return this },
clear: function(){
this.outline.innerText = ''
this.__change__()
return this },
// crop... // crop...
crop: function(node='focused'){ crop: function(node='focused'){
@ -1179,7 +1150,6 @@ var Outline = {
for(var block of [...this.outline.querySelectorAll('[cropped]')]){ for(var block of [...this.outline.querySelectorAll('[cropped]')]){
block.removeAttribute('cropped') } block.removeAttribute('cropped') }
this.get(...arguments).setAttribute('cropped', '') this.get(...arguments).setAttribute('cropped', '')
// build header path...
this.header.innerHTML = this.header.innerHTML =
`<span class="path-item" onclick="editor.uncrop('all')">/</span> ` `<span class="path-item" onclick="editor.uncrop('all')">/</span> `
+ this.path(...arguments, 'text') + this.path(...arguments, 'text')
@ -1188,66 +1158,27 @@ var Outline = {
return `<span class="path-item" onclick="editor.uncrop(${ length-i })">${s}</span> ` }) return `<span class="path-item" onclick="editor.uncrop(${ length-i })">${s}</span> ` })
.join(' / ') .join(' / ')
return this }, return this },
uncrop: function(count=1){ uncrop: function(mode=1){
var outline = this.outline var outline = this.outline
var top = this.get(0) var top = this.get(0)
for(var block of [...this.outline.querySelectorAll('[cropped]')]){ for(var block of [...this.outline.querySelectorAll('[cropped]')]){
block.removeAttribute('cropped') } block.removeAttribute('cropped') }
// crop parent if available... // crop parent if available...
while(count != 'all' while(mode != 'all'
&& count > 0 && mode > 0
&& top !== outline){ && top !== outline){
top = this.get(top, 'parent') top = this.get(top, 'parent')
count-- } mode-- }
if(count == 'all' || top === outline){ if(mode == 'all' || top === outline){
this.dom.classList.remove('crop') this.dom.classList.remove('crop')
this.header.innerHTML = '' this.header.innerHTML = ''
} else { } else {
this.crop(top) } this.crop(top) }
return this }, return this },
// undo...
// NOTE: calling .setUndo(..) will drop the redo stack, but this does
// not happen when calling a method via .undo(..)/.redo(..) as we
// are reassigning the stacks manually.
__undo_stack: undefined,
__redo_stack: undefined,
setUndo: function(path, action, args, next){
;(this.__undo_stack ??= []).push([path, action, args, next])
this.__redo_stack = undefined
return this },
clearUndo: function(){
this.__undo_stack = undefined
this.__redo_stack = undefined
return this },
__undo: function(from, to){
if(from == null
|| from.length == 0){
return [from, to] }
var [path, action, args, next] = from.pop()
var l = from.length
path != null
&& this.focus(path)
this[action](...args)
next != null ?
this.focus(next)
: this.focus()
if(l < from.length){
to ??= []
to.push(
...from.splice(l, from.length)) }
if(from.length == 0){
from = undefined }
return [from, to] },
undo: function(){
;[this.__undo_stack, this.__redo_stack] =
this.__undo(this.__undo_stack, this.__redo_stack)
return this },
redo: function(){
;[this.__redo_stack] = this.__undo(this.__redo_stack)
return this },
// block render... // block render...
// XXX need a way to filter input text...
// use-case: hidden attributes...
// NOTE: this is auto-populated by .__code2html__(..) // NOTE: this is auto-populated by .__code2html__(..)
__styles: undefined, __styles: undefined,
__code2html__: function(code, elem={}){ __code2html__: function(code, elem={}){
@ -1523,7 +1454,7 @@ var Outline = {
.clear() .clear()
.outline .outline
.append(...level(data)) .append(...level(data))
/* XXX do we actually need this??? //* XXX do we actually need this???
// update sizes of all the textareas (transparent)... // update sizes of all the textareas (transparent)...
setTimeout(function(){ setTimeout(function(){
for(var e of [...that.outline.querySelectorAll('textarea')]){ for(var e of [...that.outline.querySelectorAll('textarea')]){
@ -1678,7 +1609,8 @@ var Outline = {
this.focus(-1) }, this.focus(-1) },
PageUp: function(evt){ PageUp: function(evt){
var that = this var that = this
if(this.get('edited')){ var edited = this.get('edited')
if(edited){
return } return }
if(evt.shiftKey if(evt.shiftKey
|| evt.ctrlKey){ || evt.ctrlKey){
@ -1692,7 +1624,8 @@ var Outline = {
viewport[0], 'prev') } }, viewport[0], 'prev') } },
PageDown: function(evt){ PageDown: function(evt){
var that = this var that = this
if(this.get('edited')){ var edited = this.get('edited')
if(edited){
return } return }
if(evt.shiftKey if(evt.shiftKey
|| evt.ctrlKey){ || evt.ctrlKey){
@ -1710,9 +1643,7 @@ var Outline = {
evt.preventDefault() evt.preventDefault()
var edited = this.get('edited') var edited = this.get('edited')
var node = this.show( var node = this.show(
this.indent(evt.shiftKey ? this.indent(!evt.shiftKey))
'out'
: 'in'))
// keep focus in node... // keep focus in node...
;(edited ? ;(edited ?
edited edited
@ -1755,29 +1686,12 @@ var Outline = {
if(this.get('edited')){ if(this.get('edited')){
this.focus() this.focus()
} else { } else {
this.uncrop() } }, evt.shiftKey ?
s_Escape: function(evt){ this.uncrop('all')
if(this.get('edited')){ : this.uncrop() } },
this.focus()
} else {
this.uncrop('all') } },
c: function(evt){ c: function(evt){
if(!this.get('edited')){ if(!this.get('edited')){
this.crop() } }, this.crop() } },
c_z: function(evt){
if(!this.get('edited')){
evt.preventDefault()
this.undo() } },
c_s_z: function(evt){
if(!this.get('edited')){
evt.preventDefault()
this.redo() } },
U: function(evt){
if(!this.get('edited')){
this.redo() } },
u: function(evt){
if(!this.get('edited')){
this.undo() } },
Delete: function(evt){ Delete: function(evt){
var edited = this.get('edited') var edited = this.get('edited')
@ -1817,10 +1731,11 @@ var Outline = {
this.remove(edited) this.remove(edited)
return } }, return } },
c_d: function(evt){ d: function(evt){
// toggle done... // toggle done...
if(evt.ctrlKey){
evt.preventDefault() evt.preventDefault()
tasks.toggleDone(this) }, tasks.toggleDone(this) } },
// toggle checkbox... // toggle checkbox...
' ': function(evt){ ' ': function(evt){
@ -1920,26 +1835,8 @@ var Outline = {
if(that.runPlugins('__keydown__', evt, that, evt.target) !== true){ if(that.runPlugins('__keydown__', evt, that, evt.target) !== true){
return } return }
// handle keyboard... // handle keyboard...
var keys = [] evt.key in that.keyboard
evt.ctrlKey && that.keyboard[evt.key].call(that, evt) })
&& keys.push('c_' + evt.key)
evt.ctrlKey && evt.altKey
&& keys.push('c_a_' + evt.key)
evt.ctrlKey && evt.shiftKey
&& keys.push('c_s_' + evt.key)
evt.altKey && evt.ctrlKey && evt.shiftKey
&& keys.push('c_a_s_' + evt.key)
evt.altKey
&& keys.push('a_' + evt.key)
evt.altKey && evt.shiftKey
&& keys.push('a_s_' + evt.key)
evt.shiftKey
&& keys.push('s_' + evt.key)
keys.push(evt.key)
for(var k of keys){
if(k in that.keyboard){
that.keyboard[k].call(that, evt)
break } } })
// update code block... // update code block...
outline.addEventListener('keyup', outline.addEventListener('keyup',
function(evt){ function(evt){
@ -1969,7 +1866,6 @@ var Outline = {
that.get('focused')?.classList?.add('focused') } that.get('focused')?.classList?.add('focused') }
// textarea... // textarea...
if(elem.classList.contains('code')){ if(elem.classList.contains('code')){
elem.dataset.original = elem.value
elem.updateSize() } elem.updateSize() }
// XXX do we need this??? // XXX do we need this???
@ -1979,20 +1875,10 @@ var Outline = {
var elem = evt.target var elem = evt.target
// update code... // update code...
if(elem.classList.contains('code')){ if(elem.classList.contains('code')){
var block = that.get(elem) var block = elem.parentElement
// clean out attrs... // clean out attrs...
elem.value = that.parseBlockAttrs(elem.value).text elem.value = that.parseBlockAttrs(elem.value).text
that.update(block) that.update(block)
// undo...
if(elem.value != elem.dataset.original){
that.setUndo(
that.path(elem),
'update',
[that.path(elem), {
...that.data(elem),
text: elem.dataset.original,
}])
delete elem.dataset.original }
// give the browser a chance to update the DOM... // give the browser a chance to update the DOM...
// XXX revise... // XXX revise...
setTimeout(function(){ setTimeout(function(){
@ -2031,7 +1917,6 @@ var Outline = {
.replace(/&lt;/g, '<') .replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')) .replace(/&gt;/g, '>'))
console.log(`Parse: ${Date.now() - t}ms`) } console.log(`Parse: ${Date.now() - t}ms`) }
this.clearUndo()
this.runPlugins('__setup__', this) this.runPlugins('__setup__', this)

View File

@ -48,18 +48,12 @@ var setup = function(){
- -
- ## Bugs: - ## Bugs:
focused:: true focused:: true
- BUG: crop root indent is off...
- a
- b
- c
- crop this
- BUG: mobile browsers behave quite chaotically ignoring parts of the styling... - BUG: mobile browsers behave quite chaotically ignoring parts of the styling...
- -
- ## ToDo: - ## ToDo:
- undo: checkboxes and DONE?? - undo
_...this should be triggered by text change -- move current implementation to .__editedcode__()??..._ collapsed:: true
- copy/paste nodes/trees - edit stack (position, action, ...)
- FEATURE: read-only mode
- auto-shift done blocks to the end of siblings... (option?) - auto-shift done blocks to the end of siblings... (option?)
- ...or should this be `sort:: done` -- i.e. sort children by done status?? - ...or should this be `sort:: done` -- i.e. sort children by done status??
- codeblock as a block - codeblock as a block
@ -70,6 +64,8 @@ var setup = function(){
code code
``` ```
- _bullet should be either in the middle of the block or at the first line of code (preferred)..._ - _bullet should be either in the middle of the block or at the first line of code (preferred)..._
- copy/paste nodes/trees
- FEATURE: read-only mode
- export html - export html
- embed css - embed css
- cleanup html - cleanup html
@ -94,6 +90,10 @@ var setup = function(){
- each child node will copy the template and allow editing of only fields - each child node will copy the template and allow editing of only fields
- not clear how to handle template changes... - not clear how to handle template changes...
- JSON API - JSON API
- `.path(..)`
- `.get(..)`
- `.at(..)`
- ...
- cli - cli
- Q: do we use \\t for indent? (option???) - Q: do we use \\t for indent? (option???)
- Q: persistent empty first/last node (a button to create a new node)? - Q: persistent empty first/last node (a button to create a new node)?
@ -111,7 +111,6 @@ var setup = function(){
- empty item height is a bit off... - empty item height is a bit off...
- search? - search?
- _...not sure if search should be internal or external yet..._ - _...not sure if search should be internal or external yet..._
- DONE undo
- DONE crop: make path clickable - DONE crop: make path clickable
- DONE Q: crop: should we control crop via "crop-in"/"crop-out" instead of crop/uncrop?? - DONE Q: crop: should we control crop via "crop-in"/"crop-out" instead of crop/uncrop??
- _crop-in/crop-out seems more natural..._ - _crop-in/crop-out seems more natural..._
@ -216,13 +215,9 @@ var setup = function(){
| c-right | next checkbox | | c-right | next checkbox |
| space | toggle current checkbox | | space | toggle current checkbox |
| c-d | toggle current element DONE | | c-d | toggle current element DONE |
| c-z | normal: undo | | enter | normal mode: edit node |
| c-s-z | normal: redo | | | edit mode: create node below |
| c | normal: crop current node | | esc | exit edit mode |
| enter | normal: edit node |
| | edit: create node below |
| esc | crop: exit crop |
| | 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
- -