Compare commits

...

9 Commits

Author SHA1 Message Date
43b342c04a notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-19 00:24:36 +03:00
021062315b bugfix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-19 00:08:21 +03:00
044df2e013 more tuning...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 23:50:01 +03:00
a2913d7731 more fixes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 23:30:15 +03:00
02b3be01ec cleanup...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 20:08:01 +03:00
2ab71cf49c bugfix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 20:04:39 +03:00
6939f96098 added DONE toggle...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 19:48:00 +03:00
f1d7dd2ff0 some refactoring + now status correctly updating...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 18:44:24 +03:00
ffbc4d5031 added checlbox navigation + refactoring...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-18 18:40:04 +03:00
3 changed files with 268 additions and 48 deletions

View File

@ -462,6 +462,14 @@ editor .outline .block:focus {
/* NOTE: this appears to be needed for the em sizes above to work correctly */
font-size: 1em;
}
.editor .outline .block.check.focused>.view input[type=checkbox].selected,
.editor .outline .block.todo.focused>.view input[type=checkbox].selected {
outline: solid 0.2em silver;
}
.editor .outline .block.check:focus>.view input[type=checkbox].selected,
.editor .outline .block.todo:focus>.view input[type=checkbox].selected {
outline: solid 0.2em gray;
}
.editor .outline .block.todo>.view input[type=checkbox]:first-child {
margin-left: calc(
-1 * var(--checkbox-size)

View File

@ -38,6 +38,65 @@ function clickPoint(x,y){
//*/
// Get the character offset at coordinates...
//
// This is done by moving a range down the element until its bounding
// box corresponds the to desired coordinates. This accounts for nested
// elements.
//
// XXX HACK -- is there a better way to do this???
var getCharOffset = function(elem, x, y, c){
c = c ?? 0
var r = document.createRange()
for(var e of [...elem.childNodes]){
if(e instanceof Text){
for(var i=0; i < e.length; i++){
r.setStart(e, i)
r.setEnd(e, i)
var b = r.getBoundingClientRect()
// found target...
if(b.x >= x
&& b.y <= y
&& b.bottom >= y){
return c + i } }
c += i
} else {
var res = getCharOffset(e, x, y, c)
if(!(res instanceof Array)){
return res }
;[c, res] = res } }
return arguments.length > 3 ?
[c, null]
: null }
// Get offset in markdown relative to the resulting text...
//
// v <----- position
// text: 'Hea|ding'
// |
// +-+ <--- offset in markdown
// |
// markdown: '# Hea|ding'
//
var getMarkdownOffset = function(markdown, text, i){
i = i ?? text.length
var m = 0
// walk both strings skipping/counting non-matching stuff...
for(var n=0; n < i; n++, m++){
var c = text[n]
var p = m
// walk to next match...
while(c != markdown[m] && m < markdown.length){
m++ }
// reached something unrepresentable directly in markdown (html
// entity, symbol, ...)
if(m >= markdown.length){
m = p } }
return m - n }
//---------------------------------------------------------------------
// Plugins...
@ -111,8 +170,6 @@ var blocks = {
.replace(/^\s*(?<!\\)>\s+(.*)$/m, this.style(editor, elem, 'quote'))
.replace(/^\s*(?<!\\)((\/\/|;)\s+.*)$/m, this.style(editor, elem, 'comment'))
.replace(/^\s*(?<!\\)NOTE:?\s*(.*)$/m, this.style(editor, elem, 'NOTE'))
.replace(/^\s*(?<!\\)DONE\s+(.*)$/m, this.style(editor, elem, 'DONE'))
.replace(/^(.*)\s*(?<!\\)DONE\s*$/m, this.style(editor, elem, 'DONE'))
.replace(/^\s*(?<!\\)XXX\s+(.*)$/m, this.style(editor, elem, 'XXX'))
.replace(/^(.*)\s*(?<!\\)XXX$/m, this.style(editor, elem, 'XXX')) } ,
}
@ -199,10 +256,15 @@ var quoted = {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// XXX add actions...
var tasks = {
__proto__: plugin,
done_patterns: [
/^\s*(?<!\\)DONE\s+(.*)$/m,
/^(.*)\s*(?<!\\)DONE\s*$/m,
],
// State...
updateStatus: function(editor, node){
node = editor.get(node)
if(node == null){
@ -219,7 +281,7 @@ var tasks = {
!isNaN(c)
&& state.setAttribute('completion', c +'%') }
return this },
updateBranch: function(editor, node){
updateBranchStatus: function(editor, node){
if(!node){
return this }
var outline = editor.outline
@ -228,13 +290,127 @@ var tasks = {
this.updateStatus(editor, p)
p = editor.get(p, 'parent') }
return this },
updateAll: function(editor){
updateAllStatus: function(editor){
for(var e of [...editor.outline.querySelectorAll('.block>.view .completion')]){
this.updateStatus(editor, e) }
return this },
// Checkboxes...
getCheckbox: function(editor, elem, offset=0){
elem = elem
?? editor.get()
if(elem == null
|| (offset == 0
&& elem.type == 'checkbox')){
return elem }
var node = editor.get(elem)
var view = node.querySelector('.view')
var cur = view.querySelector('input[type=checkbox].selected')
?? view.querySelector('input[type=checkbox]')
if(offset == 0 && cur == null){
return}
var checkboxes = [...editor.outline.querySelectorAll('.view input[type=checkbox]')]
if(checkboxes.length == 0){
return }
// no checkbox in node -> get closest to cur in offset direction...
if(cur == null){
var nodes = [...editor.outline.querySelectorAll('.block')]
var checkbox_nodes = checkboxes
.map(function(e){
return editor.get(e) })
var i = nodes.indexOf(node)
var p, n
for(var c of checkbox_nodes){
p = n
var j = nodes.indexOf(c)
if(j >= i){
n = j
break } }
cur = offset < 0 ?
nodes[p]
: nodes[n] }
var elem = cur == null ?
checkboxes.at(
offset > 0 ?
offset -1
: offset)
: checkboxes.at(
(checkboxes.indexOf(cur) + offset) % checkboxes.length)
return elem },
updateCheckboxes: function(editor, elem){
elem = this.getCheckbox(editor, elem)
var node = editor.get(elem)
var text = node.querySelector('.code')
// get the checkbox order...
var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem)
var to = elem.checked ?
'[X]'
: '[_]'
var toggle = function(m){
return i-- == 0 ?
to
: m }
text.value = text.value.replace(/\[[Xx_]\]/g, toggle)
return elem },
toggleCheckbox: function(editor, checkbox, offset){
checkbox = this.getCheckbox(editor, checkbox, offset)
if(checkbox){
checkbox.checked = !checkbox.checked
this.updateCheckboxes(editor, checkbox)
this.updateBranchStatus(editor, checkbox) }
return checkbox },
selectCheckbox: function(editor, checkbox, offset){
checkbox = this.getCheckbox(editor, checkbox, offset)
if(checkbox == null){
return }
var checkboxes = editor.get(checkbox)
.querySelector('.view')
.querySelectorAll('input[type=checkbox]')
if(checkboxes.length == 0){
return }
for(var c of checkboxes){
c.classList.remove('selected') }
checkbox.classList.add('selected')
editor.show(checkbox)
return checkbox },
nextCheckbox: function(editor, node='focused', offset=1){
node = this.selectCheckbox(editor, node, offset)
editor.focus(node)
return node },
prevCheckbox: function(editor, node='focused', offset=-1){
return this.nextCheckbox(editor, node, offset) },
// DONE...
toggleDone: function(editor, elem){
var node = editor.get(elem)
if(node == null){
return }
var text = node.querySelector('.code')
var value = text.value
var s = text.selectionStart
var e = text.selectionEnd
var l = text.value.length
if(this.done_patterns
.reduce(function(res, p){
return res
|| p.test(text.value) } , false)){
for(var p of this.done_patterns){
value = value.replace(p, '$1') }
} else {
value = 'DONE ' + value }
text.value = value
text.selectionStart = s + (value.length - l)
text.selectionEnd = e + (value.length - l)
editor.update(node)
return node },
__setup__: function(editor){
return this.updateAll(editor) },
return this.updateAllStatus(editor) },
__pre_parse__: function(text, editor, elem){
// handle done..
var handler = this.style(editor, elem, 'DONE')
for(var p of this.done_patterns){
text = text
.replace(p, handler) }
return text },
__parse__: function(text, editor, elem){
return text
// block checkboxes...
@ -252,25 +428,20 @@ var tasks = {
// completion...
// XXX add support for being like a todo checkbox...
.replace(/(?<!\\)\[[%]\]/gm, '<span class="completion"></span>') },
__editedcode__: function(evt, editor, node){
return this.updateBranch(editor, node) },
__focusin__: function(evt, editor, elem){
elem.classList.contains('block')
&& this.selectCheckbox(editor, elem) },
__editedcode__: function(evt, editor, elem){
this.updateBranchStatus(editor, elem)
this.selectCheckbox(editor, elem) },
__click__: function(evt, editor, elem){
// toggle checkbox...
if(elem.type == 'checkbox'){
var node = editor.get(elem)
var text = node.querySelector('.code')
// get the checkbox order...
var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem)
var to = elem.checked ?
'[X]'
: '[_]'
var toggle = function(m){
return i-- == 0 ?
to
: m }
text.value = text.value.replace(/\[[Xx_]\]/g, toggle)
// update status...
this.updateBranch(editor, node) }
this.updateCheckboxes(editor, elem)
this.updateBranchStatus(editor, node)
this.selectCheckbox(editor, elem)
node.focus() }
return this },
}
@ -992,6 +1163,10 @@ var Outline = {
edited.selectionStart =
edited.selectionEnd = edited.value.length + 1 }
return }
if(evt.ctrlKey){
evt.preventDefault()
tasks.prevCheckbox(this)
return }
;((this.left_key_collapses
|| evt.shiftKey)
&& this.get().getAttribute('collapsed') == null
@ -1009,6 +1184,10 @@ var Outline = {
edited.selectionStart =
edited.selectionEnd = 0 }
return }
if(evt.ctrlKey){
evt.preventDefault()
tasks.nextCheckbox(this)
return }
if(this.right_key_expands){
this.toggleCollapse(false)
this.focus('next')
@ -1118,18 +1297,18 @@ var Outline = {
this.remove(edited)
return } },
// select...
// XXX add:
// ctrl-A
// ctrl-D
d: function(evt){
// toggle done...
if(evt.ctrlKey){
evt.preventDefault()
tasks.toggleDone(this) } },
// toggle checkbox...
' ': function(evt){
if(this.get('edited') != null){
return }
evt.preventDefault()
var focused = this.get()
focused.getAttribute('selected') != null ?
focused.removeAttribute('selected')
: focused.setAttribute('selected', '') },
tasks.toggleCheckbox(this) },
},
setup: function(dom){
@ -1142,6 +1321,26 @@ var Outline = {
for(var elem of [...outline.querySelectorAll('textarea')]){
elem.autoUpdateSize() }
// click...
// XXX revise...
// XXX tap support...
outline.addEventListener('mousedown',
function(evt){
var elem = evt.target
// place the cursor where the user clicked in code/text...
if(elem.classList.contains('code')
&& document.activeElement !== elem){
evt.preventDefault()
var view = that.get(elem).querySelector('.view')
var c = getCharOffset(view, evt.clientX, evt.clientY)
if(c == null){
elem.focus()
elem.selectionStart = elem.value.length
elem.selectionEnd = elem.value.length
} else {
var m = getMarkdownOffset(elem.value, view.innerText, c)
elem.focus()
elem.selectionStart = c + m
elem.selectionEnd = c + m } } })
outline.addEventListener('click',
function(evt){
var elem = evt.target
@ -1175,7 +1374,7 @@ var Outline = {
} }
// edit of focus...
// NOTE: this is usefull if element text is hidden but the
// NOTE: this is useful if element text is hidden but the
// frame is still visible...
if(elem.classList.contains('block')){
elem.querySelector('.code').focus() }

View File

@ -44,17 +44,14 @@ var setup = function(){
- // Seems that I unintentionally implemented quite a chunk of the markdown spec ;)
-
- ## Bugs:
- BUG: editor: FF seems to update the style every other key press -- should be live...
- BUG: last node seems to get trash tags added to it's end...
- BUG: quote block bullet is off in edit mode:
- > quoted text
-
- ## ToDo:
- ASAP: checkbox navigation via `alt-<arrow>`
- _might be a good idea to include also TODO/DONE navigation -- not yet sure how to mark undone blocks (i.e. the ones marked with TODO in Logseg)..._
- toggle with `space`
- navigation auto-selects first checkbox
- ASAP: scroll into view is bad...
- ASAP: mobile browsers behave quite chaotically ignoring parts of the styling...
- ASAP: need to reach checkboxes via keyboard
_...not sure how to do this with tab taken..._
- FEATURE: read-only mode
- export html
- embed css
@ -62,6 +59,7 @@ var setup = function(){
- generate ideomatic html (???)
- 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(..)`
- table inline editing a-la code editing -- click cell and edit directly...
- a way to make a block monospace (???)
- codeblock as a block
_...if only whitespace before/after clear it and style whole block..._
@ -89,14 +87,7 @@ var setup = function(){
- `<editable/>` -- field marker
- each child node will copy the template and allow editing of only fields
- not clear how to handle template changes...
- 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 list bullets be on the same level as nodes or offset??
collapsed:: true
@ -110,6 +101,21 @@ var setup = function(){
block text
- NOTE: this is only a problem if making list-items manually -- disable???
- empty item height is a bit off...
- DONE Q: can we get the caret line in a textarea???
collapsed:: true
- _...this will fix a lot of issues with moving between blocks in edit mode..._
- DONE 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..._
- DONE click to select/edit node must retain click position in text...
- _...need a bit of tuning -- where in the char a click is made and where the cursor is placed..._
- DONE checkbox navigation via `alt-<arrow>`
collapsed:: true
- _might be a good idea to include also TODO/DONE navigation -- not yet sure how to mark undone blocks (i.e. the ones marked with TODO in Logseg)..._
- toggle with `space`
- navigation auto-selects first checkbox
- DONE editor: backsapce/del at start/end of a block should join it with prev/next
- DONE editor: pressing enter in text edit mode should split text into two blocks
- DONE editor: shifting nodes up/down
@ -132,8 +138,8 @@ var setup = function(){
- ## Refactoring:
- Plugin architecture
- DONE basic structure
- plugin handler sequencing (see: `.setup(..)`)
- plugin handler canceling
- plugin handler sequencing (see: `<editor>.setup(..)`)
- DONE plugin handler canceling (see: `<editor>.runPlugins(..)`)
- DONE Item parser (`.__code2html__(..)`)
- DONE split out
- DONE define a way to extend/stack parsers
@ -152,7 +158,7 @@ var setup = function(){
- `<block>.get() -> <block>`
- Docs
-
- ## TEST
- ## Docs
- ### Controls
- ASAP: these need updating...
- | Key | Action |
@ -166,11 +172,17 @@ var setup = function(){
| s-pgdown | shift node down |
| s-left | collapse node |
| s-right | expand node |
| c-left | prev checkbox |
| c-right | next checkbox |
| space | toggle current checkbox |
| c-d | toggle current element DONE |
| enter | normal mode: edit node |
| | edit mode: create node below |
| esc | exit edit mode |
- ### Formatting:
- Styles
- ### Formatting
- The formatting mostly adheres to the markdown spec with a few minor differences
-
- Styles:
- # Heading 1
- ## Heading 2
- ### Heading 3
@ -207,6 +219,7 @@ var setup = function(){
- z
- c
- > quote
- ASAP edit mode bullet is off...
- Notes
- NOTE: a note text
- NOTE: