added inline checkboxes (request by XYZ) + lots of tweaks and fixes...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-10-09 01:32:46 +03:00
parent 69f294cec3
commit 21c386f3dc
3 changed files with 137 additions and 100 deletions

View File

@ -1,6 +1,7 @@
:root {
--font-size: 5mm;
--item-padding: 0.2em;
--button-size: 2em;
font-family: sans-serif;
@ -36,14 +37,16 @@
}
.editor .outline [tabindex]>span,
.editor .outline [tabindex]>textarea {
--padding: 0.2em;
display: block;
width: 100%;
/* XXX this is a tiny bit off and using textarea's height here is off too... */
min-height: 1em;
padding: var(--padding);
padding-top: var(--item-padding);
padding-bottom: var(--item-padding);
padding-left: 0;
padding-right: 0;
margin: 0;
box-sizing: border-box;
font-family: sans-serif;
font-size: var(--font-size);
@ -53,7 +56,7 @@
border: none;
}
.editor .outline [tabindex]>textarea {
height: calc(2 * var(--padding) + 1em);
height: calc(2 * var(--item-padding) + 1em);
overflow: hidden;
resize: none;
}
@ -130,6 +133,15 @@
.editor .outline .heading-6>textarea {
font-weight: bold;
}
.editor .outline .heading-1>span,
.editor .outline .heading-1>textarea,
.editor .outline .heading-2>span,
.editor .outline .heading-2>textarea,
.editor .outline .heading-3>span,
.editor .outline .heading-3>textarea {
border-bottom: solid 1px rgba(0,0,0,0.05);
}
.editor .outline .heading-1>span,
.editor .outline .heading-1>textarea {
font-size: 2.5em;
@ -155,16 +167,20 @@
font-size: 1em;
}
/* XXX EXPERIMENTAL -- not sure about this... */
/* XXX needs to be in the middle of the first span but with universal size... */
.editor .outline .list-item:before,
.editor .outline .list>[tabindex]:before {
.editor .outline .list>[tabindex]>span:before {
--size: 0.5rem;
display: inline-block;
position: absolute;
content: "";
top: 0.6em;
left: -0.8em;
width: 0.5em;
height: 0.5em;
top: calc(0.6em + var(--item-padding));
left: calc(var(--size) * -2);
width: var(--size);
height: var(--size);
margin-top: calc(var(--size) / -2);
background: silver;
}
@ -172,15 +188,31 @@
background: yellow;
}
.editor.hide-comments .outline .comment {
display: none;
}
.editor .outline .comment>span {
color: silver;
}
.editor .outline [tabindex]>span>input[type=checkbox] {
--width: 3em;
.editor .outline [tabindex]>span>input[type=checkbox].check,
.editor .outline [tabindex]>span>input[type=checkbox].todo {
--size: 1.5rem;
--height: calc(var(--font-size) + 2 * var(--item-padding));
height: 1em;
width: var(--width);
margin-left: calc(-1 * var(--width));
top: calc(0.6em + var(--item-padding));
height: var(--size);
width: var(--size);
margin-top: calc(var(--size) / -2);
/* NOTE: this appears to be needed for the em sizes above to work correctly */
font-size: 1em;
}
.editor .outline [tabindex]>span>input[type=checkbox].todo {
position: absolute;
margin-left: calc(-1 * var(--size) - var(--item-padding));
}
.editor .outline [tabindex]>span>input[type=checkbox].check {
transform: translateY(calc(2 * var(--item-padding)));
}

View File

@ -27,46 +27,8 @@ var atLine = function(elem, index){
//---------------------------------------------------------------------
var Node = {
dom: undefined,
document: undefined,
get: function(){},
get root(){},
get parent(){},
get children(){},
get next(){},
get prev(){},
focus: function(){},
edit: function(){},
indent: function(){ },
deindent: function(){ },
toggleCollapse: function(){ },
remove: function(){},
json: function(){},
text: function(){},
load: function(){},
}
var NodeGroup = {
__proto__: Node,
}
// XXX should this be Page or root??
var Root = {
__proto__: NodeGroup,
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// XXX might be a good idea to do a view-action model...
// XXX experiment with a concatinative model...
// .get(..) -> Outline (view)
var Outline = {
dom: undefined,
@ -219,9 +181,10 @@ var Outline = {
// XXX should this handle children???
update: function(node='focused', data){
var node = this.get(node)
data.collapsed ?
node.setAttribute('collapsed', '')
: node.removeAttribute('collapsed')
typeof(data.collapsed) == 'boolean'
&& (data.collapsed ?
node.setAttribute('collapsed', '')
: node.removeAttribute('collapsed'))
if(data.text){
var text = node.querySelector('textarea')
var html = node.querySelector('span')
@ -231,7 +194,7 @@ var Outline = {
html.innerHTML = parsed.text
// heading...
parsed.style ?
node.classList.add(parsed.style)
node.classList.add(...parsed.style)
: node.classList.remove(...this.__styles__)
} else {
html.innerHTML = data.text }
@ -306,9 +269,10 @@ var Outline = {
return this },
// block serialization...
// XXX STUB...
// XXX shouild we support headings + other formatting per block???
// XXX these should be symetrical -- now one returns text the other an object...
// XXX split this up into a generic handler + plugins...
// XXX need a way to filter input text...
// use-case: hidden attributes...
__styles__: [
'heading-1',
'heading-2',
@ -324,11 +288,13 @@ var Outline = {
}
var heading = function(level){
return function(_, text){
elem.style = 'heading-'+level
elem.style ??= []
elem.style.push('heading-'+level)
return text } }
var style = function(style){
return function(_, text){
elem.style = style
elem.style ??= []
elem.style.push(style)
return text } }
elem.text = code
// hidden attributes...
@ -342,33 +308,37 @@ var Outline = {
// id...
.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,
function(_, value){
elem.collapsed = value.trim() == 'true'
elem.id = value.trim()
return '' })
// markdown...
// ToDo...
.replace(/^TODO\s*(.*)$/, '<input type="checkbox"> $1')
.replace(/^DONE\s*(.*)$/, '<input type="checkbox" checked> $1')
// style: headings...
.replace(/^######\s*(.*)$/, style('heading-6'))
.replace(/^#####\s*(.*)$/, style('heading-5'))
.replace(/^####\s*(.*)$/, style('heading-4'))
.replace(/^###\s*(.*)$/, style('heading-3'))
.replace(/^##\s*(.*)$/, style('heading-2'))
.replace(/^#\s*(.*)$/, style('heading-1'))
.replace(/^######\s*(.*)$/m, style('heading-6'))
.replace(/^#####\s*(.*)$/m, style('heading-5'))
.replace(/^####\s*(.*)$/m, style('heading-4'))
.replace(/^###\s*(.*)$/m, style('heading-3'))
.replace(/^##\s*(.*)$/m, style('heading-2'))
.replace(/^#\s*(.*)$/m, style('heading-1'))
// style: list...
.replace(/^[-\*]\s+(.*)$/, style('list-item'))
.replace(/^\s*(.*):\s*$/, style('list'))
.replace(/^[-\*]\s+(.*)$/m, style('list-item'))
.replace(/^\s*(.*):\s*$/m, style('list'))
// style: misc...
.replace(/^((\/\/|;)\s+.*)$/, style('comment'))
.replace(/^XXX\s+(.*)$/, style('XXX'))
.replace(/^(.*)\s*XXX$/, style('XXX'))
.replace(/^((\/\/|;)\s+.*)$/m, style('comment'))
.replace(/^XXX\s+(.*)$/m, style('XXX'))
.replace(/^(.*)\s*XXX$/m, style('XXX'))
// basic styling...
// XXX these are quite naive...
.replace(/\*(.*)\*/g, '<b>$1</b>')
.replace(/~([^~]*)~/g, '<s>$1</s>')
.replace(/_([^_]*)_/g, '<i>$1</i>')
.replace(/\*(.*)\*/gm, '<b>$1</b>')
.replace(/~([^~]*)~/gm, '<s>$1</s>')
.replace(/_([^_]*)_/gm, '<i>$1</i>')
// elements...
.replace(/(\n|^)---*\h*(\n|$)/, '$1<hr>')
.replace(/(\n|^)---*\h*(\n|$)/m, '$1<hr>')
// ToDo...
.replace(/^TODO\s*(.*)$/m, '<input class="todo" type="checkbox">$1')
.replace(/^DONE\s*(.*)$/m, '<input class="todo" type="checkbox" checked>$1')
// checkboxes...
// XXX these can not be clicked (yet)...
.replace(/\[ \]/gm, '<input class="check" type="checkbox">')
.replace(/\[[X]\]/gm, '<input class="check" type="checkbox" checked>')
return elem },
// serialization...
@ -460,9 +430,19 @@ var Outline = {
var html = document.createElement('span')
block.append(text, html)
this.update(block, data)
// place...
var cur = this.get()
place && cur
&& cur[place](block)
if(place && cur){
place = place == 'prev' ?
'before'
: place
;(place == 'next'
&& (cur.querySelector('[tabindex]')
|| cur.nextElementSibling)) ?
this.get(place).before(block)
: (place == 'before' || place == 'after') ?
cur[place](block)
: undefined }
return block },
load: function(data){
var that = this
@ -481,6 +461,9 @@ var Outline = {
.clear()
.outline
.append(...level(data))
// update sizes of all the textareas (transparent)...
for(var e of [...this.outline.querySelectorAll('textarea')]){
e.updateSize() }
return this },
sync: function(){
@ -569,25 +552,31 @@ var Outline = {
O: function(evt){
if(evt.target.nodeName != 'TEXTAREA'){
evt.preventDefault()
this.Block('before')?.querySelector('textarea')?.focus() } },
this.Block('before')
?.querySelector('textarea')
?.focus() } },
o: function(evt){
if(evt.target.nodeName != 'TEXTAREA'){
evt.preventDefault()
this.Block('after')?.querySelector('textarea')?.focus() } },
this.Block('next')
?.querySelector('textarea')
?.focus() } },
Enter: function(evt){
/*if(evt.target.isContentEditable){
// XXX create new node...
return }
//*/
if(evt.ctrlKey
|| evt.shiftKey){
return }
evt.preventDefault()
evt.target.nodeName == 'TEXTAREA' ?
this.Block('after')?.querySelector('textarea')?.focus()
: this.get()?.querySelector('textarea')?.focus() },
this.Block('next')
?.querySelector('textarea')
?.focus()
: this.get()
?.querySelector('textarea')
?.focus() },
Escape: function(evt){
this.outline.querySelector('textarea:focus')?.parentElement?.focus() },
this.outline.querySelector('textarea:focus')
?.parentElement
?.focus() },
Delete: function(evt){
if(this.get('edited')){
return }
@ -620,14 +609,27 @@ var Outline = {
outline.addEventListener('click',
function(evt){
var elem = evt.target
// toggle checkbox...
if(elem.nodeName == 'INPUT' && elem.type == 'checkbox'){
// todo: toggle checkbox...
if(elem.classList.contains('todo')){
var node = elem.parentElement.parentElement
var text = node.querySelector('textarea')
text.value =
elem.checked ?
text.value.replace(/^\s*TODO(\s*)/, 'DONE$1')
: text.value.replace(/^\s*DONE(\s*)/, 'TODO$1') } })
: text.value.replace(/^\s*DONE(\s*)/, 'TODO$1') }
// check: toggle checkbox...
if(elem.classList.contains('check')){
var node = elem.parentElement.parentElement
var text = node.querySelector('textarea')
var i = [...node.querySelectorAll('.check')].indexOf(elem)
var to = elem.checked ?
'[X]'
: '[ ]'
var toggle = function(m){
return i-- == 0 ?
to
: m }
text.value = text.value.replace(/\[[X ]\]/g, toggle) } })
// heboard handling...
outline.addEventListener('keydown',
function(evt){

View File

@ -1,3 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<link href="editor.css" rel="stylesheet"/>
@ -27,8 +28,7 @@ var setup = function(){
<!-- code -->
<div class="code">
- # Outline editor prototype
- ## TODO
- editor: enter on an expanded parent node should create child (currently next sibling)
- ## ToDo
- editor: bksapce/del at start/end of a block should join it with prev/next
- editor: pressing enter in text edit mode should split text into two blocks
- editor: caret
@ -51,6 +51,7 @@ var setup = function(){
- indent/deindent
- edit node
- copy/paste nodes/trees
- markdown tables
- ~serialize~/deserialize
- ~add optional text styling to nodes~
-
@ -71,12 +72,14 @@ var setup = function(){
- // C-style comment
- ; ASM-style comment
- XXX Highlight
- line
- Line
- ---
- basic inline *bold*, _italic_ and ~striked~
- Basic inline *bold*, _italic_ and ~striked~
- To do items
- TODO undone item
- DONE done item
_(clicking the checkbox updates the item)_
- Inline [X] checkboxes [ ]
- A
collapsed:: true
- a