Compare commits

...

2 Commits

Author SHA1 Message Date
e01d5b54cb moved to textarea + added support for code/view processing (currently html <-> text, not FF compatible)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-09-27 00:56:26 +03:00
2172b83e93 tweaking...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-09-26 14:50:42 +03:00

View File

@ -7,12 +7,42 @@
font-size: 5mm;
}
.editor div div {
.editor [tabindex] {
position: relative;
}
.editor div [tabindex] {
margin-left: 2em;
}
.editor div span {
.editor [tabindex]>span,
.editor [tabindex]>textarea {
--padding: 0.2em;
display: block;
padding: 0.2em;
width: 100%;
padding: var(--padding);
margin: 0;
font-family: sans-serif;
font-size: 5mm;
white-space: pre;
outline: none;
border: none;
}
.editor [tabindex]>textarea {
height: calc(2 * var(--padding) + 1em);
overflow: hidden;
resize: none;
}
/* show/hide node's view/code... */
.editor [tabindex]>span+textarea:not(:focus),
/* XXX not sure how to do this without :has(..)... */
.editor [tabindex]:has(>span+textarea:focus)>span:has(+textarea),
.editor [tabindex]:focus>span+textarea {
position: absolute;
opacity: 0;
top: 0;
}
.editor div[collapsed] {
@ -26,21 +56,29 @@
/*outline: solid 0.2em silver;*/
outline: none;
}
.editor div:focus>span {
.editor div:focus>span,
.editor div:focus>textarea {
background: silver;
}
</style>
<script>
var updateTextareaSize = function(elem){
elem.style.height = ''
elem.style.height = elem.scrollHeight + 'px'
return elem }
var getFocused = function(offset=0){
var focused = document.querySelector('.editor :focus')
var getFocused = function(offset=0, selector='[tabindex]'){
var focused = document.querySelector(`.editor ${selector}:focus`)
|| (selector != 'textarea' ?
getEditable()?.parentElement
: null)
if(offset == 0){
return focused }
if(offset == 'parent'){
if(!focused){
return document.querySelector('.editor [tabindex]') }
return document.querySelector(`.editor ${selector}`) }
var elem = focused.parentElement
return elem.classList.contains('editor') ?
undefined
@ -48,7 +86,7 @@ var getFocused = function(offset=0){
if(offset == 'child'){
if(!focused){
return document.querySelector('.editor [tabindex]') }
return document.querySelector(`.editor ${selector}`) }
return focused.querySelector('div') }
if(offset == 'children'){
@ -65,7 +103,7 @@ var getFocused = function(offset=0){
.filter(function(elem){
return elem.getAttribute('tabindex') }) }
var focusable = [...document.querySelectorAll('.editor [tabindex]')]
var focusable = [...document.querySelectorAll(`.editor ${selector}`)]
.filter(function(e){
return e.offsetParent != null })
if(offset == 'all'){
@ -83,8 +121,13 @@ var getFocused = function(offset=0){
} else {
return focusable[offset > 0 ? 0 : focusable.length-1] } }
// XXX would also be nice to make the move only if at first/last line/char
// XXX would be nice to keep the cursor at roughly the same left offset...
var getEditable = function(offset){
return getFocused(offset, 'textarea') }
var indentNode = function(indent=true){
var cur = getFocused()
var cur = getFocused()
if(!cur){
return }
var siblings = getFocused('siblings')
@ -108,35 +151,94 @@ var toggleCollapse = function(node, state='next'){
return getFocused('all')
.map(function(node){
return toggleCollapse(node, state) }) }
// state passed directly...
// toggleCollapse(<state>)
if(!(node instanceof HTMLElement) && node != null){
state = node
node = null }
node ??= getFocused()
if(!node){
if(!node
// only nodes with children can be collapsed...
|| !node.querySelector('[tabindex]')){
return }
state = state == 'next' ?
!node.getAttribute('collapsed')
: state
state ?
if(state){
node.setAttribute('collapsed', '')
: node.removeAttribute('collapsed')
} else {
node.removeAttribute('collapsed')
for(var elem of [...node.querySelectorAll('textarea')]){
updateTextareaSize(elem) }
}
return node }
// XXX add reference node...
var createBlock = function(place=none){
var block = document.createElement('div')
block.setAttribute('tabindex', '0')
block.innerHTML = `<span></span><textarea></textarea>`
var cur = getFocused()
|| getEditable()?.parentElement
place && cur
&& cur[place](block)
return block }
// XXX do a caret api...
// XXX this works only on the current text node...
// XXX only for text areas...
var atLine = function(index){
// XXX add support for range...
var elem = getEditable()
var text = elem.value
var lines = text.split(/\n/g).length
var offset = elem.selectionStart
var line = text.slice(0, offset).split(/\n/g).length
//console.log('---', line, 'of', lines, '---', offset, sel)
// XXX STUB index handling...
if(index == -1 && line == lines){
return true
} else if(index == 0 && line == 1){
return true
}
return false
}
var LEFT_COLLAPSE = false
var RIGHT_EXPAND = true
// XXX add scrollIntoView(..) to nav...
var keyboard = {
// vertical navigation...
ArrowDown: function(evt, offset=1){
getFocused(1)?.focus() },
ArrowUp: function(evt){
getFocused(-1)?.focus() },
var action = getFocused
var edited = document.querySelector('.editor textarea:focus')
if(edited){
if(!atLine(0)){
return }
action = getEditable }
evt.preventDefault()
action(-1)?.focus() },
ArrowDown: function(evt, offset=1){
var action = getFocused
var edited = document.querySelector('.editor textarea:focus')
if(edited){
if(!atLine(-1)){
return }
//window.getSelection()
action = getEditable }
evt.preventDefault()
action(1)?.focus() },
// horizontal navigation / collapse...
// XXX if at start/end of element move to prev/next...
ArrowLeft: function(evt){
if(document.querySelector('.editor textarea:focus')){
// XXX if at end of element move to next...
return }
if(LEFT_COLLAPSE){
toggleCollapse(true)
getFocused('parent')?.focus()
@ -145,6 +247,9 @@ var keyboard = {
toggleCollapse(true)
: getFocused('parent')?.focus() } },
ArrowRight: function(evt){
if(document.querySelector('.editor textarea:focus')){
// XXX if at end of element move to next...
return }
if(RIGHT_EXPAND){
toggleCollapse(false)
var child = getFocused('child')
@ -159,34 +264,88 @@ var keyboard = {
// indent...
Tab: function(evt){
evt.preventDefault()
indentNode(!evt.shiftKey)?.focus() },
var editable = getEditable()
var node = indentNode(!evt.shiftKey)
;(editable ?
editable
: node)?.focus() },
// edit mode...
O: function(evt){
if(evt.target.nodeName != 'TEXTAREA'){
evt.preventDefault()
createBlock('before')?.querySelector('textarea')?.focus() } },
o: function(evt){
if(evt.target.nodeName != 'TEXTAREA'){
evt.preventDefault()
createBlock('after')?.querySelector('textarea')?.focus() } },
Enter: function(evt){
console.log('---', evt)
},
/*if(evt.target.isContentEditable){
// XXX create new node...
return }
//*/
if(evt.ctrlKey
|| evt.shiftKey){
return }
evt.preventDefault()
evt.target.nodeName == 'TEXTAREA' ?
createBlock('after')?.querySelector('textarea')?.focus()
: getFocused()?.querySelector('textarea')?.focus() },
Escape: function(evt){
console.log('---', evt)
},
document.querySelector('textarea:focus')?.parentElement?.focus() },
Delete: function(evt){
if(evt.target.isContentEditable){
return }
var next = getFocused(1)
getFocused()?.remove()
next?.focus() },
}
document.addEventListener('keydown',
function(evt){
evt.key in keyboard
&& keyboard[evt.key](evt) })
document.addEventListener('input',
function(evt){
updateTextareaSize(evt.target) })
// XXX add support for markup handlers...
document.addEventListener('focusin',
function(evt){
var node = evt.target
if(node.nodeName == 'TEXTAREA'
&& node?.previousElementSibling?.nodeName == 'SPAN'){
node.value = node.previousElementSibling.innerHTML
updateTextareaSize(node) } })
document.addEventListener('focusout',
function(evt){
var node = evt.target
if(node.nodeName == 'TEXTAREA'
&& node?.previousElementSibling?.nodeName == 'SPAN'){
node.previousElementSibling.innerHTML = node.value } })
var setup = function(){
for(var elem of [...document.querySelectorAll('.editor textarea')]){
updateTextareaSize(elem) } }
</script>
</head>
<body>
<body onload="setup()">
<pre>
TODO:
- <s>navigation</s>
- <s>expand/collapse subtree</s>
- <s>shift subtree up/down</s>
- edit node
- create node
- <s>create node</s>
- <s>edit node</s>
- undo delete node
- copy/paste nodes/trees
- shifting nodes up/down
- multiple node selection
- mouse controls
- touch controls
- serialize/deserialize
- add optional styling to nodes
Controls:
up - focus node above
@ -197,24 +356,36 @@ Controls:
s-tab - deindent node
s-left - collapse node
s-right - expand node
enter - normal mode: edit node
- edit mode: create node below
esc - exit edit mode
</pre>
<hr>
<div class="editor">
<div tabindex=0><span>root</span>
<div tabindex=0 collapsed><span>A</span>
<div tabindex=0><span>a</span>
<div tabindex=0>
<span><i>root</i></span><textarea></textarea>
<div tabindex=0 collapsed>
<span>A</span><textarea></textarea>
<div tabindex=0><span>a</span><textarea></textarea>
</div>
<div tabindex=0><span>b</span>
<div tabindex=0><span>b</span><textarea></textarea>
</div>
<div tabindex=0><span>c</span>
<div tabindex=0><span>c</span><textarea></textarea>
</div>
</div>
<div tabindex=0><span>B</span>
<div tabindex=0><span>d</span>
<div tabindex=0><span>B</span><textarea></textarea>
<div tabindex=0><span>d</span><textarea></textarea>
</div>
<div tabindex=0><span>e</span>
<div tabindex=0><span>e</span><textarea></textarea>
</div>
</div>
<div tabindex=0><span>C</span><textarea></textarea>
<div tabindex=0><span>This is a line of text</span><textarea></textarea>
</div>
<div tabindex=0><span>This is a set
text lines</span><textarea></textarea>
</div>
</div>
</div>