mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-29 18:10:09 +00:00
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:
parent
69f294cec3
commit
21c386f3dc
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-size: 5mm;
|
--font-size: 5mm;
|
||||||
|
--item-padding: 0.2em;
|
||||||
--button-size: 2em;
|
--button-size: 2em;
|
||||||
|
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@ -36,14 +37,16 @@
|
|||||||
}
|
}
|
||||||
.editor .outline [tabindex]>span,
|
.editor .outline [tabindex]>span,
|
||||||
.editor .outline [tabindex]>textarea {
|
.editor .outline [tabindex]>textarea {
|
||||||
--padding: 0.2em;
|
|
||||||
|
|
||||||
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 textarea's height here is off too... */
|
||||||
min-height: 1em;
|
min-height: 1em;
|
||||||
padding: var(--padding);
|
padding-top: var(--item-padding);
|
||||||
|
padding-bottom: var(--item-padding);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
@ -53,7 +56,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.editor .outline [tabindex]>textarea {
|
.editor .outline [tabindex]>textarea {
|
||||||
height: calc(2 * var(--padding) + 1em);
|
height: calc(2 * var(--item-padding) + 1em);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
@ -130,6 +133,15 @@
|
|||||||
.editor .outline .heading-6>textarea {
|
.editor .outline .heading-6>textarea {
|
||||||
font-weight: bold;
|
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>span,
|
||||||
.editor .outline .heading-1>textarea {
|
.editor .outline .heading-1>textarea {
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
@ -155,16 +167,20 @@
|
|||||||
font-size: 1em;
|
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-item:before,
|
||||||
.editor .outline .list>[tabindex]:before {
|
.editor .outline .list>[tabindex]>span:before {
|
||||||
|
--size: 0.5rem;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
top: 0.6em;
|
top: calc(0.6em + var(--item-padding));
|
||||||
left: -0.8em;
|
left: calc(var(--size) * -2);
|
||||||
width: 0.5em;
|
width: var(--size);
|
||||||
height: 0.5em;
|
height: var(--size);
|
||||||
|
margin-top: calc(var(--size) / -2);
|
||||||
|
|
||||||
background: silver;
|
background: silver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,15 +188,31 @@
|
|||||||
background: yellow;
|
background: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor.hide-comments .outline .comment {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.editor .outline .comment>span {
|
.editor .outline .comment>span {
|
||||||
color: silver;
|
color: silver;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor .outline [tabindex]>span>input[type=checkbox] {
|
.editor .outline [tabindex]>span>input[type=checkbox].check,
|
||||||
--width: 3em;
|
.editor .outline [tabindex]>span>input[type=checkbox].todo {
|
||||||
|
--size: 1.5rem;
|
||||||
|
--height: calc(var(--font-size) + 2 * var(--item-padding));
|
||||||
|
|
||||||
height: 1em;
|
top: calc(0.6em + var(--item-padding));
|
||||||
width: var(--width);
|
height: var(--size);
|
||||||
margin-left: calc(-1 * var(--width));
|
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,46 +27,8 @@ var atLine = function(elem, index){
|
|||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
var Node = {
|
// XXX experiment with a concatinative model...
|
||||||
dom: undefined,
|
// .get(..) -> Outline (view)
|
||||||
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...
|
|
||||||
var Outline = {
|
var Outline = {
|
||||||
dom: undefined,
|
dom: undefined,
|
||||||
|
|
||||||
@ -219,9 +181,10 @@ var Outline = {
|
|||||||
// XXX should this handle children???
|
// XXX should this handle children???
|
||||||
update: function(node='focused', data){
|
update: function(node='focused', data){
|
||||||
var node = this.get(node)
|
var node = this.get(node)
|
||||||
data.collapsed ?
|
typeof(data.collapsed) == 'boolean'
|
||||||
node.setAttribute('collapsed', '')
|
&& (data.collapsed ?
|
||||||
: node.removeAttribute('collapsed')
|
node.setAttribute('collapsed', '')
|
||||||
|
: node.removeAttribute('collapsed'))
|
||||||
if(data.text){
|
if(data.text){
|
||||||
var text = node.querySelector('textarea')
|
var text = node.querySelector('textarea')
|
||||||
var html = node.querySelector('span')
|
var html = node.querySelector('span')
|
||||||
@ -231,7 +194,7 @@ var Outline = {
|
|||||||
html.innerHTML = parsed.text
|
html.innerHTML = parsed.text
|
||||||
// heading...
|
// heading...
|
||||||
parsed.style ?
|
parsed.style ?
|
||||||
node.classList.add(parsed.style)
|
node.classList.add(...parsed.style)
|
||||||
: node.classList.remove(...this.__styles__)
|
: node.classList.remove(...this.__styles__)
|
||||||
} else {
|
} else {
|
||||||
html.innerHTML = data.text }
|
html.innerHTML = data.text }
|
||||||
@ -306,9 +269,10 @@ var Outline = {
|
|||||||
return this },
|
return this },
|
||||||
|
|
||||||
// block serialization...
|
// block serialization...
|
||||||
// XXX STUB...
|
|
||||||
// XXX shouild we support headings + other formatting per block???
|
// 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__: [
|
__styles__: [
|
||||||
'heading-1',
|
'heading-1',
|
||||||
'heading-2',
|
'heading-2',
|
||||||
@ -324,11 +288,13 @@ var Outline = {
|
|||||||
}
|
}
|
||||||
var heading = function(level){
|
var heading = function(level){
|
||||||
return function(_, text){
|
return function(_, text){
|
||||||
elem.style = 'heading-'+level
|
elem.style ??= []
|
||||||
|
elem.style.push('heading-'+level)
|
||||||
return text } }
|
return text } }
|
||||||
var style = function(style){
|
var style = function(style){
|
||||||
return function(_, text){
|
return function(_, text){
|
||||||
elem.style = style
|
elem.style ??= []
|
||||||
|
elem.style.push(style)
|
||||||
return text } }
|
return text } }
|
||||||
elem.text = code
|
elem.text = code
|
||||||
// hidden attributes...
|
// hidden attributes...
|
||||||
@ -342,33 +308,37 @@ var Outline = {
|
|||||||
// id...
|
// id...
|
||||||
.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,
|
.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,
|
||||||
function(_, value){
|
function(_, value){
|
||||||
elem.collapsed = value.trim() == 'true'
|
elem.id = value.trim()
|
||||||
return '' })
|
return '' })
|
||||||
// markdown...
|
// markdown...
|
||||||
// ToDo...
|
|
||||||
.replace(/^TODO\s*(.*)$/, '<input type="checkbox"> $1')
|
|
||||||
.replace(/^DONE\s*(.*)$/, '<input type="checkbox" checked> $1')
|
|
||||||
// style: headings...
|
// style: headings...
|
||||||
.replace(/^######\s*(.*)$/, style('heading-6'))
|
.replace(/^######\s*(.*)$/m, style('heading-6'))
|
||||||
.replace(/^#####\s*(.*)$/, style('heading-5'))
|
.replace(/^#####\s*(.*)$/m, style('heading-5'))
|
||||||
.replace(/^####\s*(.*)$/, style('heading-4'))
|
.replace(/^####\s*(.*)$/m, style('heading-4'))
|
||||||
.replace(/^###\s*(.*)$/, style('heading-3'))
|
.replace(/^###\s*(.*)$/m, style('heading-3'))
|
||||||
.replace(/^##\s*(.*)$/, style('heading-2'))
|
.replace(/^##\s*(.*)$/m, style('heading-2'))
|
||||||
.replace(/^#\s*(.*)$/, style('heading-1'))
|
.replace(/^#\s*(.*)$/m, style('heading-1'))
|
||||||
// style: list...
|
// style: list...
|
||||||
.replace(/^[-\*]\s+(.*)$/, style('list-item'))
|
.replace(/^[-\*]\s+(.*)$/m, style('list-item'))
|
||||||
.replace(/^\s*(.*):\s*$/, style('list'))
|
.replace(/^\s*(.*):\s*$/m, style('list'))
|
||||||
// style: misc...
|
// style: misc...
|
||||||
.replace(/^((\/\/|;)\s+.*)$/, style('comment'))
|
.replace(/^((\/\/|;)\s+.*)$/m, style('comment'))
|
||||||
.replace(/^XXX\s+(.*)$/, style('XXX'))
|
.replace(/^XXX\s+(.*)$/m, style('XXX'))
|
||||||
.replace(/^(.*)\s*XXX$/, style('XXX'))
|
.replace(/^(.*)\s*XXX$/m, style('XXX'))
|
||||||
// basic styling...
|
// basic styling...
|
||||||
// XXX these are quite naive...
|
// XXX these are quite naive...
|
||||||
.replace(/\*(.*)\*/g, '<b>$1</b>')
|
.replace(/\*(.*)\*/gm, '<b>$1</b>')
|
||||||
.replace(/~([^~]*)~/g, '<s>$1</s>')
|
.replace(/~([^~]*)~/gm, '<s>$1</s>')
|
||||||
.replace(/_([^_]*)_/g, '<i>$1</i>')
|
.replace(/_([^_]*)_/gm, '<i>$1</i>')
|
||||||
// elements...
|
// 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 },
|
return elem },
|
||||||
|
|
||||||
// serialization...
|
// serialization...
|
||||||
@ -460,9 +430,19 @@ var Outline = {
|
|||||||
var html = document.createElement('span')
|
var html = document.createElement('span')
|
||||||
block.append(text, html)
|
block.append(text, html)
|
||||||
this.update(block, data)
|
this.update(block, data)
|
||||||
|
// place...
|
||||||
var cur = this.get()
|
var cur = this.get()
|
||||||
place && cur
|
if(place && cur){
|
||||||
&& cur[place](block)
|
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 },
|
return block },
|
||||||
load: function(data){
|
load: function(data){
|
||||||
var that = this
|
var that = this
|
||||||
@ -481,6 +461,9 @@ var Outline = {
|
|||||||
.clear()
|
.clear()
|
||||||
.outline
|
.outline
|
||||||
.append(...level(data))
|
.append(...level(data))
|
||||||
|
// update sizes of all the textareas (transparent)...
|
||||||
|
for(var e of [...this.outline.querySelectorAll('textarea')]){
|
||||||
|
e.updateSize() }
|
||||||
return this },
|
return this },
|
||||||
|
|
||||||
sync: function(){
|
sync: function(){
|
||||||
@ -569,25 +552,31 @@ var Outline = {
|
|||||||
O: function(evt){
|
O: function(evt){
|
||||||
if(evt.target.nodeName != 'TEXTAREA'){
|
if(evt.target.nodeName != 'TEXTAREA'){
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
this.Block('before')?.querySelector('textarea')?.focus() } },
|
this.Block('before')
|
||||||
|
?.querySelector('textarea')
|
||||||
|
?.focus() } },
|
||||||
o: function(evt){
|
o: function(evt){
|
||||||
if(evt.target.nodeName != 'TEXTAREA'){
|
if(evt.target.nodeName != 'TEXTAREA'){
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
this.Block('after')?.querySelector('textarea')?.focus() } },
|
this.Block('next')
|
||||||
|
?.querySelector('textarea')
|
||||||
|
?.focus() } },
|
||||||
Enter: function(evt){
|
Enter: function(evt){
|
||||||
/*if(evt.target.isContentEditable){
|
|
||||||
// XXX create new node...
|
|
||||||
return }
|
|
||||||
//*/
|
|
||||||
if(evt.ctrlKey
|
if(evt.ctrlKey
|
||||||
|| evt.shiftKey){
|
|| evt.shiftKey){
|
||||||
return }
|
return }
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
evt.target.nodeName == 'TEXTAREA' ?
|
evt.target.nodeName == 'TEXTAREA' ?
|
||||||
this.Block('after')?.querySelector('textarea')?.focus()
|
this.Block('next')
|
||||||
: this.get()?.querySelector('textarea')?.focus() },
|
?.querySelector('textarea')
|
||||||
|
?.focus()
|
||||||
|
: this.get()
|
||||||
|
?.querySelector('textarea')
|
||||||
|
?.focus() },
|
||||||
Escape: function(evt){
|
Escape: function(evt){
|
||||||
this.outline.querySelector('textarea:focus')?.parentElement?.focus() },
|
this.outline.querySelector('textarea:focus')
|
||||||
|
?.parentElement
|
||||||
|
?.focus() },
|
||||||
Delete: function(evt){
|
Delete: function(evt){
|
||||||
if(this.get('edited')){
|
if(this.get('edited')){
|
||||||
return }
|
return }
|
||||||
@ -620,14 +609,27 @@ var Outline = {
|
|||||||
outline.addEventListener('click',
|
outline.addEventListener('click',
|
||||||
function(evt){
|
function(evt){
|
||||||
var elem = evt.target
|
var elem = evt.target
|
||||||
// toggle checkbox...
|
// todo: toggle checkbox...
|
||||||
if(elem.nodeName == 'INPUT' && elem.type == 'checkbox'){
|
if(elem.classList.contains('todo')){
|
||||||
var node = elem.parentElement.parentElement
|
var node = elem.parentElement.parentElement
|
||||||
var text = node.querySelector('textarea')
|
var text = node.querySelector('textarea')
|
||||||
text.value =
|
text.value =
|
||||||
elem.checked ?
|
elem.checked ?
|
||||||
text.value.replace(/^\s*TODO(\s*)/, 'DONE$1')
|
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...
|
// heboard handling...
|
||||||
outline.addEventListener('keydown',
|
outline.addEventListener('keydown',
|
||||||
function(evt){
|
function(evt){
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link href="editor.css" rel="stylesheet"/>
|
<link href="editor.css" rel="stylesheet"/>
|
||||||
@ -27,8 +28,7 @@ var setup = function(){
|
|||||||
<!-- code -->
|
<!-- code -->
|
||||||
<div class="code">
|
<div class="code">
|
||||||
- # Outline editor prototype
|
- # Outline editor prototype
|
||||||
- ## TODO
|
- ## ToDo
|
||||||
- editor: enter on an expanded parent node should create child (currently next sibling)
|
|
||||||
- editor: bksapce/del at start/end of a block should join it with prev/next
|
- 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: pressing enter in text edit mode should split text into two blocks
|
||||||
- editor: caret
|
- editor: caret
|
||||||
@ -51,6 +51,7 @@ var setup = function(){
|
|||||||
- indent/deindent
|
- indent/deindent
|
||||||
- edit node
|
- edit node
|
||||||
- copy/paste nodes/trees
|
- copy/paste nodes/trees
|
||||||
|
- markdown tables
|
||||||
- ~serialize~/deserialize
|
- ~serialize~/deserialize
|
||||||
- ~add optional text styling to nodes~
|
- ~add optional text styling to nodes~
|
||||||
-
|
-
|
||||||
@ -71,12 +72,14 @@ var setup = function(){
|
|||||||
- // C-style comment
|
- // C-style comment
|
||||||
- ; ASM-style comment
|
- ; ASM-style comment
|
||||||
- XXX Highlight
|
- XXX Highlight
|
||||||
- line
|
- Line
|
||||||
- ---
|
- ---
|
||||||
- basic inline *bold*, _italic_ and ~striked~
|
- Basic inline *bold*, _italic_ and ~striked~
|
||||||
- To do items
|
- To do items
|
||||||
- TODO undone item
|
- TODO undone item
|
||||||
- DONE done item
|
- DONE done item
|
||||||
|
_(clicking the checkbox updates the item)_
|
||||||
|
- Inline [X] checkboxes [ ]
|
||||||
- A
|
- A
|
||||||
collapsed:: true
|
collapsed:: true
|
||||||
- a
|
- a
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user