mirror of
https://github.com/flynx/pWiki.git
synced 2025-12-25 12:21:58 +00:00
Compare commits
7 Commits
90cdc0c3c7
...
9ecbf4e060
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ecbf4e060 | |||
| f6fea34821 | |||
| fbd55abddf | |||
| 5c28b8d10e | |||
| 0e5d188cad | |||
| ecf40bb455 | |||
| b2a3173f8d |
@ -7,7 +7,7 @@
|
|||||||
:root {
|
:root {
|
||||||
--font-size: 5mm;
|
--font-size: 5mm;
|
||||||
|
|
||||||
--outline-padding: 5rem;
|
--outline-padding: 7rem;
|
||||||
|
|
||||||
--item-indent: 2rem;
|
--item-indent: 2rem;
|
||||||
--item-padding-ratio: 0.2;
|
--item-padding-ratio: 0.2;
|
||||||
@ -54,8 +54,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: calc(100% - var(--button-size) - var(--outline-padding) * 2);
|
|
||||||
|
|
||||||
padding: 1em var(--outline-padding);
|
padding: 1em var(--outline-padding);
|
||||||
padding-bottom: 1.2em
|
padding-bottom: 1.2em
|
||||||
}
|
}
|
||||||
|
|||||||
@ -720,6 +720,7 @@ var Outline = {
|
|||||||
// .get('visible')
|
// .get('visible')
|
||||||
// .get('editable')
|
// .get('editable')
|
||||||
// .get('selected')
|
// .get('selected')
|
||||||
|
// .get('viewport')
|
||||||
// .get('top')
|
// .get('top')
|
||||||
// -> <nodes>
|
// -> <nodes>
|
||||||
//
|
//
|
||||||
@ -739,7 +740,7 @@ var Outline = {
|
|||||||
if(node == 'top'){
|
if(node == 'top'){
|
||||||
return [...outline.children] }
|
return [...outline.children] }
|
||||||
// groups defaulting to .outline as base...
|
// 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) }
|
return this.get(outline, node) }
|
||||||
// groups defaulting to .focused as base...
|
// groups defaulting to .focused as base...
|
||||||
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
|
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
|
||||||
@ -806,6 +807,11 @@ var Outline = {
|
|||||||
[...node.querySelectorAll('.block')]
|
[...node.querySelectorAll('.block')]
|
||||||
.filter(function(e){
|
.filter(function(e){
|
||||||
return e.offsetParent != null })
|
return e.offsetParent != null })
|
||||||
|
: offset == 'viewport' ?
|
||||||
|
[...node.querySelectorAll('.block')]
|
||||||
|
.filter(function(e){
|
||||||
|
return e.offsetParent != null
|
||||||
|
&& e.querySelector('.code').visibleInViewport() })
|
||||||
: offset == 'editable' ?
|
: offset == 'editable' ?
|
||||||
[...node.querySelectorAll('.block>.code')]
|
[...node.querySelectorAll('.block>.code')]
|
||||||
: offset == 'selected' ?
|
: offset == 'selected' ?
|
||||||
@ -840,13 +846,20 @@ var Outline = {
|
|||||||
focus: function(node='focused', offset){
|
focus: function(node='focused', offset){
|
||||||
var elem = this.get(...arguments)
|
var elem = this.get(...arguments)
|
||||||
?? this.get(0)
|
?? this.get(0)
|
||||||
|
if(elem){
|
||||||
|
elem.focus({preventScroll: true})
|
||||||
|
;(elem.classList.contains('code') ?
|
||||||
elem
|
elem
|
||||||
&& elem.focus()
|
: elem.querySelector('.code'))
|
||||||
|
.scrollIntoView({
|
||||||
|
block: 'nearest',
|
||||||
|
//behavior: 'smooth',
|
||||||
|
}) }
|
||||||
return elem },
|
return elem },
|
||||||
edit: function(node='focused', offset){
|
edit: function(node='focused', offset){
|
||||||
var elem = this.get(...arguments)
|
var elem = this.get(...arguments)
|
||||||
if(elem.nodeName != 'TEXTAREA'){
|
if(!elem.classList.contains('code')){
|
||||||
elem = elem.querySelector('textarea') }
|
elem = elem.querySelector('.code') }
|
||||||
elem?.focus()
|
elem?.focus()
|
||||||
return elem },
|
return elem },
|
||||||
|
|
||||||
@ -1027,7 +1040,8 @@ var Outline = {
|
|||||||
this.get(elem, 'prev')
|
this.get(elem, 'prev')
|
||||||
: this.get(elem, 'next') }
|
: this.get(elem, 'next') }
|
||||||
elem?.remove()
|
elem?.remove()
|
||||||
next?.focus()
|
next
|
||||||
|
&& this.focus(next)
|
||||||
this.__change__()
|
this.__change__()
|
||||||
return this },
|
return this },
|
||||||
clear: function(){
|
clear: function(){
|
||||||
@ -1037,12 +1051,13 @@ var Outline = {
|
|||||||
|
|
||||||
|
|
||||||
// crop...
|
// crop...
|
||||||
|
// XXX add crop/path indicator...
|
||||||
__crop_stack: undefined,
|
__crop_stack: undefined,
|
||||||
crop: function(node='focused'){
|
crop: function(node='focused'){
|
||||||
var stack = this.__crop_stack ??= []
|
var stack = this.__crop_stack ??= []
|
||||||
stack.push([this.json(), this.path()])
|
stack.push([this.json(), this.path()])
|
||||||
this.load(this.data())
|
this.load(this.data())
|
||||||
.focus()
|
this.dom.classList.add('crop')
|
||||||
return this },
|
return this },
|
||||||
// XXX use JSON API...
|
// XXX use JSON API...
|
||||||
uncrop: function(){
|
uncrop: function(){
|
||||||
@ -1050,7 +1065,8 @@ var Outline = {
|
|||||||
return this}
|
return this}
|
||||||
var [state, path] = this.__crop_stack.pop()
|
var [state, path] = this.__crop_stack.pop()
|
||||||
if(this.__crop_stack.length == 0){
|
if(this.__crop_stack.length == 0){
|
||||||
this.__crop_stack = undefined }
|
this.__crop_stack = undefined
|
||||||
|
this.dom.classList.remove('crop') }
|
||||||
// update state...
|
// update state...
|
||||||
path
|
path
|
||||||
.slice(0, -1)
|
.slice(0, -1)
|
||||||
@ -1058,7 +1074,6 @@ var Outline = {
|
|||||||
return res[i].children }, state)
|
return res[i].children }, state)
|
||||||
.splice(path.at(-1), 1, ...this.json())
|
.splice(path.at(-1), 1, ...this.json())
|
||||||
this.load(state)
|
this.load(state)
|
||||||
.focus()
|
|
||||||
return this },
|
return this },
|
||||||
|
|
||||||
// block render...
|
// block render...
|
||||||
@ -1316,6 +1331,7 @@ var Outline = {
|
|||||||
cur[place](block)
|
cur[place](block)
|
||||||
: undefined }
|
: undefined }
|
||||||
return block },
|
return block },
|
||||||
|
// XXX see inside...
|
||||||
load: function(data){
|
load: function(data){
|
||||||
var that = this
|
var that = this
|
||||||
data = typeof(data) == 'string' ?
|
data = typeof(data) == 'string' ?
|
||||||
@ -1336,9 +1352,14 @@ var Outline = {
|
|||||||
.clear()
|
.clear()
|
||||||
.outline
|
.outline
|
||||||
.append(...level(data))
|
.append(...level(data))
|
||||||
|
//* XXX do we actually need this???
|
||||||
// update sizes of all the textareas (transparent)...
|
// update sizes of all the textareas (transparent)...
|
||||||
for(var e of [...this.outline.querySelectorAll('textarea')]){
|
setTimeout(function(){
|
||||||
e.updateSize() }
|
for(var e of [...that.outline.querySelectorAll('textarea')]){
|
||||||
|
e.updateSize() } }, 0)
|
||||||
|
//*/
|
||||||
|
// restore focus...
|
||||||
|
this.focus()
|
||||||
return this },
|
return this },
|
||||||
|
|
||||||
sync: function(){
|
sync: function(){
|
||||||
@ -1360,7 +1381,6 @@ var Outline = {
|
|||||||
right: function(){},
|
right: function(){},
|
||||||
|
|
||||||
// XXX move the code here into methods/actions...
|
// XXX move the code here into methods/actions...
|
||||||
// XXX add scrollIntoView(..) to nav...
|
|
||||||
// XXX use keyboard.js...
|
// XXX use keyboard.js...
|
||||||
keyboard: {
|
keyboard: {
|
||||||
// vertical navigation...
|
// vertical navigation...
|
||||||
@ -1383,7 +1403,7 @@ var Outline = {
|
|||||||
var edited = this.get('edited')
|
var edited = this.get('edited')
|
||||||
if(edited){
|
if(edited){
|
||||||
var {line, lines} = edited.getTextGeometry()
|
var {line, lines} = edited.getTextGeometry()
|
||||||
if(line == lines -1){
|
if(line == lines - 1){
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
that.focus('edited', 'next') }
|
that.focus('edited', 'next') }
|
||||||
} else {
|
} else {
|
||||||
@ -1448,19 +1468,35 @@ var Outline = {
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
this.focus(-1) },
|
this.focus(-1) },
|
||||||
PageUp: function(evt){
|
PageUp: function(evt){
|
||||||
|
var that = this
|
||||||
var edited = this.get('edited')
|
var edited = this.get('edited')
|
||||||
if(!edited
|
if(edited){
|
||||||
&& (evt.shiftKey
|
return }
|
||||||
|| evt.ctrlKey)){
|
if(evt.shiftKey
|
||||||
|
|| evt.ctrlKey){
|
||||||
evt.preventDefault()
|
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){
|
PageDown: function(evt){
|
||||||
|
var that = this
|
||||||
var edited = this.get('edited')
|
var edited = this.get('edited')
|
||||||
if(!edited
|
if(edited){
|
||||||
&& (evt.shiftKey
|
return }
|
||||||
|| evt.ctrlKey)){
|
if(evt.shiftKey
|
||||||
|
|| evt.ctrlKey){
|
||||||
evt.preventDefault()
|
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..
|
// indent..
|
||||||
Tab: function(evt){
|
Tab: function(evt){
|
||||||
@ -1485,7 +1521,6 @@ var Outline = {
|
|||||||
this.edit(
|
this.edit(
|
||||||
this.Block('next')) } },
|
this.Block('next')) } },
|
||||||
Enter: function(evt){
|
Enter: function(evt){
|
||||||
|
|
||||||
var edited = this.get('edited')
|
var edited = this.get('edited')
|
||||||
// edit -> split text...
|
// edit -> split text...
|
||||||
if(edited){
|
if(edited){
|
||||||
@ -1636,6 +1671,20 @@ var Outline = {
|
|||||||
if(elem.classList.contains('block')){
|
if(elem.classList.contains('block')){
|
||||||
elem.querySelector('.code').focus() }
|
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) })
|
that.runPlugins('__click__', evt, that, elem) })
|
||||||
// keyboard handling...
|
// keyboard handling...
|
||||||
outline.addEventListener('keydown',
|
outline.addEventListener('keydown',
|
||||||
@ -1666,26 +1715,20 @@ var Outline = {
|
|||||||
return }
|
return }
|
||||||
|
|
||||||
// handle focus...
|
// handle focus...
|
||||||
|
if(elem !== that.outline){
|
||||||
for(var e of [...that.dom.querySelectorAll('.focused')]){
|
for(var e of [...that.dom.querySelectorAll('.focused')]){
|
||||||
e.classList.remove('focused') }
|
e.classList.remove('focused') }
|
||||||
that.get('focused')?.classList?.add('focused')
|
that.get('focused')?.classList?.add('focused') }
|
||||||
// textarea...
|
// textarea...
|
||||||
if(elem.classList.contains('code')){
|
if(elem.classList.contains('code')){
|
||||||
elem.updateSize() }
|
elem.updateSize() }
|
||||||
|
|
||||||
/*/ scroll...
|
|
||||||
that.get(node).querySelector('view')
|
|
||||||
?.scrollIntoView({
|
|
||||||
block: 'nearest',
|
|
||||||
behavior: 'smooth',
|
|
||||||
})
|
|
||||||
//*/
|
|
||||||
|
|
||||||
// XXX do we need this???
|
// XXX do we need this???
|
||||||
that.runPlugins('__focusin__', evt, that, elem) })
|
that.runPlugins('__focusin__', evt, that, elem) })
|
||||||
outline.addEventListener('focusout',
|
outline.addEventListener('focusout',
|
||||||
function(evt){
|
function(evt){
|
||||||
var elem = evt.target
|
var elem = evt.target
|
||||||
|
// update code...
|
||||||
if(elem.classList.contains('code')){
|
if(elem.classList.contains('code')){
|
||||||
var block = elem.parentElement
|
var block = elem.parentElement
|
||||||
// clean out attrs...
|
// clean out attrs...
|
||||||
@ -1713,7 +1756,7 @@ var Outline = {
|
|||||||
focus_textarea = document.activeElement.nodeName == 'TEXTAREA' }
|
focus_textarea = document.activeElement.nodeName == 'TEXTAREA' }
|
||||||
var refocusNode = function(){
|
var refocusNode = function(){
|
||||||
focus_textarea ?
|
focus_textarea ?
|
||||||
editor.get().querySelector('textarea').focus()
|
editor.get().querySelector('.code').focus()
|
||||||
: editor.focus()
|
: editor.focus()
|
||||||
focus_textarea = undefined }
|
focus_textarea = undefined }
|
||||||
// cache the focused node type before focus changes...
|
// cache the focused node type before focus changes...
|
||||||
|
|||||||
@ -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(){
|
HTMLTextAreaElement.prototype.updateSize = function(){
|
||||||
this.style.height = ''
|
this.style.height = ''
|
||||||
this.style.height = this.scrollHeight + 'px'
|
this.style.height = this.scrollHeight + 'px'
|
||||||
@ -20,6 +40,7 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
|
|||||||
|
|
||||||
// get the relevant styles...
|
// get the relevant styles...
|
||||||
var style = getComputedStyle(this)
|
var style = getComputedStyle(this)
|
||||||
|
var paddingV = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)
|
||||||
var s = {}
|
var s = {}
|
||||||
for(var i=0; i < style.length; i++){
|
for(var i=0; i < style.length; i++){
|
||||||
var k = style[i]
|
var k = style[i]
|
||||||
@ -34,7 +55,6 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
|
|||||||
carret.style.padding = '0px'
|
carret.style.padding = '0px'
|
||||||
|
|
||||||
var span = document.createElement('span')
|
var span = document.createElement('span')
|
||||||
span.innerText = text.slice(0, offset)
|
|
||||||
Object.assign(span.style, {
|
Object.assign(span.style, {
|
||||||
...s,
|
...s,
|
||||||
|
|
||||||
@ -42,22 +62,31 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
|
|||||||
display: 'block',
|
display: 'block',
|
||||||
top: '-100%',
|
top: '-100%',
|
||||||
left: '-100%',
|
left: '-100%',
|
||||||
width: this.offsetWidth + 'px',
|
width: style.width,
|
||||||
height: this.scrollHeight + 'px',
|
height: style.height,
|
||||||
|
|
||||||
padding: style.padding,
|
padding: style.padding,
|
||||||
|
|
||||||
|
boxSizing: style.boxSizing,
|
||||||
|
|
||||||
outline: 'solid 1px red',
|
outline: 'solid 1px red',
|
||||||
|
|
||||||
pointerEvents: 'none',
|
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)
|
document.body.append(span)
|
||||||
|
|
||||||
var res = {
|
var res = {
|
||||||
length: text.length,
|
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),
|
line: Math.floor(carret.offsetTop / carret.offsetHeight),
|
||||||
offset: offset,
|
offset: offset,
|
||||||
offsetLeft: carret.offsetLeft,
|
offsetLeft: carret.offsetLeft,
|
||||||
|
|||||||
@ -40,18 +40,17 @@ var setup = function(){
|
|||||||
- Logseq
|
- Logseq
|
||||||
- Conboy (Nokia N900's Tomboy clone)
|
- Conboy (Nokia N900's Tomboy clone)
|
||||||
- Bonsai (on PalmOS)
|
- Bonsai (on PalmOS)
|
||||||
|
- Google Keep
|
||||||
-
|
-
|
||||||
- // Seems that I unintentionally implemented quite a chunk of the markdown spec ;)
|
- // Seems that I unintentionally implemented quite a chunk of the markdown spec ;)
|
||||||
-
|
-
|
||||||
- ## Bugs:
|
- ## Bugs:
|
||||||
focused:: true
|
focused:: true
|
||||||
- BUG: editor: FF seems to update the style every other key press -- should be live...
|
- 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...
|
- BUG: mobile browsers behave quite chaotically ignoring parts of the styling...
|
||||||
-
|
-
|
||||||
- ## ToDo:
|
- ## ToDo:
|
||||||
- crop: show crop path (and depth)
|
- crop: show crop path (and depth)
|
||||||
- pgup/pgdown/~home/end~ buttons
|
|
||||||
- identify a block:
|
- identify a block:
|
||||||
- DONE index (flat)
|
- DONE index (flat)
|
||||||
- DONE path (index)
|
- DONE path (index)
|
||||||
@ -88,6 +87,10 @@ var setup = function(){
|
|||||||
- editor as a custom element...
|
- editor as a custom element...
|
||||||
- Nerd fonts (option???)
|
- Nerd fonts (option???)
|
||||||
- multiple node selection
|
- 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...
|
- FEATURE? block templates...
|
||||||
collapsed:: true
|
collapsed:: true
|
||||||
- something like: `TPL: [_] <editable/> -- <editable/>`
|
- something like: `TPL: [_] <editable/> -- <editable/>`
|
||||||
@ -115,6 +118,7 @@ var setup = function(){
|
|||||||
block text
|
block text
|
||||||
- NOTE: this is only a problem if making list-items manually -- disable???
|
- NOTE: this is only a problem if making list-items manually -- disable???
|
||||||
- empty item height is a bit off...
|
- empty item height is a bit off...
|
||||||
|
- DONE pgup/pgdown/home/end buttons
|
||||||
- DONE FEATURE: "crop" -- view block tree separately...
|
- DONE FEATURE: "crop" -- view block tree separately...
|
||||||
- DONE unify attr parsing
|
- DONE unify attr parsing
|
||||||
collapsed:: true
|
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
|
- 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>
|
- </textarea>
|
||||||
<!-- outline -->
|
<!-- outline -->
|
||||||
<div class="outline"></div>
|
<div class="outline" tabindex="0"></div>
|
||||||
<!-- toolbar (optional) -->
|
<!-- toolbar (optional) -->
|
||||||
<!--div class="toolbar">
|
<!--div class="toolbar">
|
||||||
<button onclick="editor.deindent().focus()"><</button>
|
<button onclick="editor.deindent().focus()"><</button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user