mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-29 10:00:08 +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 {
|
||||
--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)));
|
||||
}
|
||||
|
||||
|
||||
@ -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){
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user