Compare commits

...

7 Commits

Author SHA1 Message Date
9ecbf4e060 ...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 17:43:25 +03:00
f6fea34821 fixed text positioning...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 17:37:53 +03:00
fbd55abddf bugfix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 16:47:19 +03:00
5c28b8d10e tweak...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 15:34:24 +03:00
0e5d188cad pgup/pgdown now move focus... (some tuning might still be needed)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 14:51:27 +03:00
ecf40bb455 notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 14:41:09 +03:00
b2a3173f8d focus and scroll into view now seems OK, not smooth though...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-23 14:37:14 +03:00
4 changed files with 136 additions and 62 deletions

View File

@ -7,7 +7,7 @@
:root {
--font-size: 5mm;
--outline-padding: 5rem;
--outline-padding: 7rem;
--item-indent: 2rem;
--item-padding-ratio: 0.2;
@ -54,8 +54,6 @@
display: block;
position: relative;
width: calc(100% - var(--button-size) - var(--outline-padding) * 2);
padding: 1em var(--outline-padding);
padding-bottom: 1.2em
}

View File

@ -720,6 +720,7 @@ var Outline = {
// .get('visible')
// .get('editable')
// .get('selected')
// .get('viewport')
// .get('top')
// -> <nodes>
//
@ -739,7 +740,7 @@ var Outline = {
if(node == 'top'){
return [...outline.children] }
// groups defaulting to .outline as base...
if(['all', 'visible', 'editable', 'selected'].includes(node)){
if(['all', 'visible', 'editable', 'selected', 'viewport'].includes(node)){
return this.get(outline, node) }
// groups defaulting to .focused as base...
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
@ -806,6 +807,11 @@ var Outline = {
[...node.querySelectorAll('.block')]
.filter(function(e){
return e.offsetParent != null })
: offset == 'viewport' ?
[...node.querySelectorAll('.block')]
.filter(function(e){
return e.offsetParent != null
&& e.querySelector('.code').visibleInViewport() })
: offset == 'editable' ?
[...node.querySelectorAll('.block>.code')]
: offset == 'selected' ?
@ -840,13 +846,20 @@ var Outline = {
focus: function(node='focused', offset){
var elem = this.get(...arguments)
?? this.get(0)
if(elem){
elem.focus({preventScroll: true})
;(elem.classList.contains('code') ?
elem
&& elem.focus()
: elem.querySelector('.code'))
.scrollIntoView({
block: 'nearest',
//behavior: 'smooth',
}) }
return elem },
edit: function(node='focused', offset){
var elem = this.get(...arguments)
if(elem.nodeName != 'TEXTAREA'){
elem = elem.querySelector('textarea') }
if(!elem.classList.contains('code')){
elem = elem.querySelector('.code') }
elem?.focus()
return elem },
@ -1027,7 +1040,8 @@ var Outline = {
this.get(elem, 'prev')
: this.get(elem, 'next') }
elem?.remove()
next?.focus()
next
&& this.focus(next)
this.__change__()
return this },
clear: function(){
@ -1037,12 +1051,13 @@ var Outline = {
// crop...
// XXX add crop/path indicator...
__crop_stack: undefined,
crop: function(node='focused'){
var stack = this.__crop_stack ??= []
stack.push([this.json(), this.path()])
this.load(this.data())
.focus()
this.dom.classList.add('crop')
return this },
// XXX use JSON API...
uncrop: function(){
@ -1050,7 +1065,8 @@ var Outline = {
return this}
var [state, path] = this.__crop_stack.pop()
if(this.__crop_stack.length == 0){
this.__crop_stack = undefined }
this.__crop_stack = undefined
this.dom.classList.remove('crop') }
// update state...
path
.slice(0, -1)
@ -1058,7 +1074,6 @@ var Outline = {
return res[i].children }, state)
.splice(path.at(-1), 1, ...this.json())
this.load(state)
.focus()
return this },
// block render...
@ -1316,6 +1331,7 @@ var Outline = {
cur[place](block)
: undefined }
return block },
// XXX see inside...
load: function(data){
var that = this
data = typeof(data) == 'string' ?
@ -1336,9 +1352,14 @@ var Outline = {
.clear()
.outline
.append(...level(data))
//* XXX do we actually need this???
// update sizes of all the textareas (transparent)...
for(var e of [...this.outline.querySelectorAll('textarea')]){
e.updateSize() }
setTimeout(function(){
for(var e of [...that.outline.querySelectorAll('textarea')]){
e.updateSize() } }, 0)
//*/
// restore focus...
this.focus()
return this },
sync: function(){
@ -1360,7 +1381,6 @@ var Outline = {
right: function(){},
// XXX move the code here into methods/actions...
// XXX add scrollIntoView(..) to nav...
// XXX use keyboard.js...
keyboard: {
// vertical navigation...
@ -1448,19 +1468,35 @@ var Outline = {
evt.preventDefault()
this.focus(-1) },
PageUp: function(evt){
var that = this
var edited = this.get('edited')
if(!edited
&& (evt.shiftKey
|| evt.ctrlKey)){
if(edited){
return }
if(evt.shiftKey
|| evt.ctrlKey){
evt.preventDefault()
this.shift('up') } },
this.shift('up')
} else {
var viewport = that.get('viewport')
viewport[0] === that.get(0) ?
that.focus(0)
: that.focus(
viewport[0], 'prev') } },
PageDown: function(evt){
var that = this
var edited = this.get('edited')
if(!edited
&& (evt.shiftKey
|| evt.ctrlKey)){
if(edited){
return }
if(evt.shiftKey
|| evt.ctrlKey){
evt.preventDefault()
this.shift('down') } },
this.shift('down')
} else {
var viewport = that.get('viewport')
viewport.at(-1) === that.get(-1) ?
that.focus(-1)
: that.focus(
that.get('viewport').at(-1), 'next') } },
// indent..
Tab: function(evt){
@ -1485,7 +1521,6 @@ var Outline = {
this.edit(
this.Block('next')) } },
Enter: function(evt){
var edited = this.get('edited')
// edit -> split text...
if(edited){
@ -1636,6 +1671,20 @@ var Outline = {
if(elem.classList.contains('block')){
elem.querySelector('.code').focus() }
// focus viewport...
// XXX this does not work because by this point there is
// no focused element...
if(elem === outline){
var cur = that.get()
var viewport = that.get('viewport')
if(!viewport.includes(cur)){
var visible = that.get('visible')
var i = visible.indexOf(cur)
var v = visible.indexOf(viewport[0])
i < v ?
that.focus(viewport[0])
: that.focus(viewport.at(-1)) } }
that.runPlugins('__click__', evt, that, elem) })
// keyboard handling...
outline.addEventListener('keydown',
@ -1666,26 +1715,20 @@ var Outline = {
return }
// handle focus...
if(elem !== that.outline){
for(var e of [...that.dom.querySelectorAll('.focused')]){
e.classList.remove('focused') }
that.get('focused')?.classList?.add('focused')
that.get('focused')?.classList?.add('focused') }
// textarea...
if(elem.classList.contains('code')){
elem.updateSize() }
/*/ scroll...
that.get(node).querySelector('view')
?.scrollIntoView({
block: 'nearest',
behavior: 'smooth',
})
//*/
// XXX do we need this???
that.runPlugins('__focusin__', evt, that, elem) })
outline.addEventListener('focusout',
function(evt){
var elem = evt.target
// update code...
if(elem.classList.contains('code')){
var block = elem.parentElement
// clean out attrs...
@ -1713,7 +1756,7 @@ var Outline = {
focus_textarea = document.activeElement.nodeName == 'TEXTAREA' }
var refocusNode = function(){
focus_textarea ?
editor.get().querySelector('textarea').focus()
editor.get().querySelector('.code').focus()
: editor.focus()
focus_textarea = undefined }
// cache the focused node type before focus changes...

View File

@ -4,6 +4,26 @@
*
**********************************************************************/
Element.prototype.visibleInViewport = function(partial=false){
var { top, left, bottom, right } = this.getBoundingClientRect()
var { innerHeight, innerWidth } = window
return partial
? ((top > 0
&& top < innerHeight)
|| (bottom > 0
&& bottom < innerHeight))
&& ((left > 0
&& left < innerWidth)
|| (right > 0
&& right < innerWidth))
: (top >= 0
&& left >= 0
&& bottom <= innerHeight
&& right <= innerWidth) }
//---------------------------------------------------------------------
HTMLTextAreaElement.prototype.updateSize = function(){
this.style.height = ''
this.style.height = this.scrollHeight + 'px'
@ -20,6 +40,7 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
// get the relevant styles...
var style = getComputedStyle(this)
var paddingV = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)
var s = {}
for(var i=0; i < style.length; i++){
var k = style[i]
@ -34,7 +55,6 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
carret.style.padding = '0px'
var span = document.createElement('span')
span.innerText = text.slice(0, offset)
Object.assign(span.style, {
...s,
@ -42,22 +62,31 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
display: 'block',
top: '-100%',
left: '-100%',
width: this.offsetWidth + 'px',
height: this.scrollHeight + 'px',
width: style.width,
height: style.height,
padding: style.padding,
boxSizing: style.boxSizing,
outline: 'solid 1px red',
pointerEvents: 'none',
})
span.append(carret)
span.append(
text.slice(0, offset),
carret,
// NOTE: wee need the rest of the text for the carret to be typeset
// to the correct line...
text.slice(offset))
document.body.append(span)
var res = {
length: text.length,
lines: Math.floor(this.offsetHeight / carret.offsetHeight),
lines: Math.floor(
(this.offsetHeight - paddingV)
/ carret.offsetHeight),
line: Math.floor(carret.offsetTop / carret.offsetHeight),
offset: offset,
offsetLeft: carret.offsetLeft,

View File

@ -40,18 +40,17 @@ var setup = function(){
- Logseq
- Conboy (Nokia N900's Tomboy clone)
- Bonsai (on PalmOS)
- Google Keep
-
- // Seems that I unintentionally implemented quite a chunk of the markdown spec ;)
-
- ## Bugs:
focused:: true
- BUG: editor: FF seems to update the style every other key press -- should be live...
- BUG: scrolling into view needs tuning...
- BUG: mobile browsers behave quite chaotically ignoring parts of the styling...
-
- ## ToDo:
- crop: show crop path (and depth)
- pgup/pgdown/~home/end~ buttons
- identify a block:
- DONE index (flat)
- DONE path (index)
@ -88,6 +87,10 @@ var setup = function(){
- editor as a custom element...
- Nerd fonts (option???)
- multiple node selection
- smooth scrolling
- _...this is more complicated that adding `behavior: "smooth"` to `.scrollIntoView(..)` as scrolling animation will get interrupted by next user input..._
- need to cancel animation of things are moving too fast...
- make this generic
- FEATURE? block templates...
collapsed:: true
- something like: `TPL: [_] <editable/> -- <editable/>`
@ -115,6 +118,7 @@ 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 pgup/pgdown/home/end buttons
- DONE FEATURE: "crop" -- view block tree separately...
- DONE unify attr parsing
collapsed:: true
@ -318,7 +322,7 @@ var setup = function(){
- Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text
- </textarea>
<!-- outline -->
<div class="outline"></div>
<div class="outline" tabindex="0"></div>
<!-- toolbar (optional) -->
<!--div class="toolbar">
<button onclick="editor.deindent().focus()">&lt;</button>