Compare commits

...

9 Commits

Author SHA1 Message Date
cb62d1e1c2 typo...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 18:29:50 +03:00
558ccaa812 found a bug...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 18:29:17 +03:00
11b9f04954 notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 16:29:36 +03:00
41ea814849 notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 16:27:45 +03:00
99eab1f99e notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 16:22:38 +03:00
03b308391f notes + fixes and tweaks...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 16:16:21 +03:00
b3325c40c4 notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 15:59:58 +03:00
4cfa65f60c fixes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 15:53:05 +03:00
0525efc018 cleanup + refactoring....
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-10-29 15:31:23 +03:00
3 changed files with 149 additions and 87 deletions

View File

@ -768,16 +768,18 @@ var Outline = {
get header(){ get header(){
return this.dom.querySelector('.header') }, return this.dom?.querySelector('.header') },
get outline(){ get outline(){
return this.dom.querySelector('.outline') }, return this.dom?.querySelector('.outline') },
get toolbar(){ get toolbar(){
return this.dom.querySelector('.toolbar') }, return this.dom?.querySelector('.toolbar') },
get code(){ get code(){
return this.dom.querySelector('.code')?.value }, return this.dom?.querySelector('.code')?.value },
set code(value){ set code(value){
var c = this.dom.querySelector('.code') if(value == null){
return }
var c = this.dom?.querySelector('.code')
if(c){ if(c){
c.value = value } }, c.value = value } },
@ -1459,9 +1461,9 @@ var Outline = {
parse: function(text){ parse: function(text){
var that = this var that = this
text = text text = text
.replace(/^\s*\n/, '') .replace(/^[ \t]*\n/, '')
text = ('\n' + text) text = ('\n' + text)
.split(/\n(\s*)(?:- |-\s*$)/gm) .split(/\n([ \t]*)(?:- |-\s*$)/gm)
.slice(1) .slice(1)
var tab = ' '.repeat(this.tab_size || 8) var tab = ' '.repeat(this.tab_size || 8)
var level = function(lst, prev_sep=undefined, parent=[]){ var level = function(lst, prev_sep=undefined, parent=[]){
@ -1779,22 +1781,23 @@ var Outline = {
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...
if(edited){ if(edited){
if(evt.ctrlKey if(evt.ctrlKey
|| evt.shiftKey){ || evt.shiftKey){
return } return }
// split text...
evt.preventDefault() evt.preventDefault()
var a = edited.selectionStart var a = edited.selectionStart
var b = edited.selectionEnd var b = edited.selectionEnd
var prev = edited.value.slice(0, a) var prev = edited.value.slice(0, a)
var next = edited.value.slice(b) var next = edited.value.slice(b)
edited.value = prev edited.value = prev
this.Block('next') this.Block({text: next}, 'next')
edited = this.edit('next') // focus next if not at position 0, otherwise keep focus...
edited.value = next if(a != 0){
edited.selectionStart = 0 edited = this.edit('next')
edited.selectionEnd = 0 edited.selectionStart = 0
edited.selectionEnd = 0 }
return } return }
// view -> edit... // view -> edit...
evt.preventDefault() evt.preventDefault()
@ -2138,93 +2141,113 @@ var Outline = {
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Custom element... // Custom element...
window.customElements.define('outline-editor', window.customElements.define('outline-editor',
window.OutlineEditor = window.OutlineEditor =
Object.assign( Object.assign(
function(){ function(){
var obj = Reflect.construct(HTMLElement, [...arguments], OutlineEditor) var obj = Reflect.construct(HTMLElement, [...arguments], OutlineEditor)
obj.editor = { var shadow = obj.attachShadow({mode: 'open'})
__proto__: Outline,
get code(){ var style = document.createElement('link');
return obj.hasAttribute('value') ? style.setAttribute('rel', 'stylesheet');
obj.getAttribute('value') style.setAttribute('href', 'editor.css');
: (obj.children.length == 1
&& obj.children[0].nodeName == 'TEXTAREA') ?
obj.children[0].value
: obj.innerHTML },
set code(value){
// XXX this can break in conjunction with .attributeChangedCallback(..)
if(obj.hasAttribute('value')){
obj.setAttribute('value', value)
} else if(obj.children.length == 1
&& obj.children[0].nodeName == 'TEXTAREA'){
obj.children[0].value = value
} else {
obj.innerHTML = value } },
}
return obj }, // XXX it is not rational to have this...
{ var editor = obj.dom = document.createElement('div')
// constructor stuff... editor.classList.add('editor')
observedAttributes: [
'value',
],
// instance stuff... var header = document.createElement('div')
prototype: { header.classList.add('header')
var outline = document.createElement('div')
outline.classList.add('outline')
outline.setAttribute('tabindex', '0')
//var toolbar = document.createElement('div')
//toolbar.classList.add('toolbar')
editor.append(
style,
header,
outline)
shadow.append(editor)
obj.setup(editor)
return obj },
// constructor stuff...
{
observedAttributes: [
'value',
],
prototype: Object.assign(
{
__proto__: HTMLElement.prototype, __proto__: HTMLElement.prototype,
// XXX HACK these are copies from Outline, use
// object.mixin(...) instead...
get header(){
return this.dom?.querySelector('.header') },
set header(val){},
get outline(){
return this.dom?.querySelector('.outline') },
set outline(val){},
get toolbar(){
return this.dom?.querySelector('.toolbar') },
set toolbar(val){},
// XXX these are generic...
encode: function(text){
var span = document.createElement('span')
span.innerText = text
return span.innerHTML },
decode: function(text){
var span = document.createElement('span')
span.innerHTML = text
return span.innerText },
get code(){
return this.hasAttribute('value') ?
this.getAttribute('value')
: this.decode(this.innerHTML) },
set code(value){
if(value == null){
return }
// XXX this can break in conjunction with .attributeChangedCallback(..)
if(this.hasAttribute('value')){
this.setAttribute('value', value)
} else {
this.innerHTML = this.encode(value) } },
// XXX do we need this???
// ...rename .code -> .value ???
get value(){ get value(){
return this.getAttribute('value') }, return this.code },
set value(value){ set value(value){
this.setAttribute('value', value) }, this.code = value },
connectedCallback: function(){ connectedCallback: function(){
var that = this var that = this
var shadow = this.attachShadow({mode: 'open'})
var style = document.createElement('link');
style.setAttribute('rel', 'stylesheet');
style.setAttribute('href', 'editor.css');
// XXX it is not rational to have this...
var editor = document.createElement('div')
editor.classList.add('editor')
var header = document.createElement('div')
header.classList.add('header')
var outline = document.createElement('div')
outline.classList.add('outline')
outline.setAttribute('tabindex', '0')
//var toolbar = document.createElement('div')
//toolbar.classList.add('toolbar')
// load the data... // load the data...
setTimeout(function(){ setTimeout(function(){
that.editor.setup(editor) }, 0) that.load(that.code) }, 0) },
editor.append( attributeChangedCallback(name, before, after){
style,
header,
outline)
shadow.append(editor) },
disconnectedCallback: function(){
},
adoptedCallback: function(){
},
attributeChangedCallback: function(name, oldvalue, newvalue){
if(name == 'value'){ if(name == 'value'){
console.log('---', newvalue) if(before != after){
//oldvalue != newvalue // XXX
// && this.editor.load(newvalue) console.log('---', before, '->', after)
}
return } return }
}, },
}, },
})) // XXX this will fail due to all the getters/setters -- use object.mixin(..)...
Outline),
}))

View File

@ -68,6 +68,7 @@ HTMLTextAreaElement.prototype.getTextGeometry = function(){
padding: style.padding, padding: style.padding,
boxSizing: style.boxSizing, boxSizing: style.boxSizing,
//whiteSpace: 'pre-wrap',
outline: 'solid 1px red', outline: 'solid 1px red',

View File

@ -48,6 +48,16 @@ var setup = function(){
- -
- ## Bugs: - ## Bugs:
focused:: true focused:: true
- BUG: focus at times seems to be biased a bit to the right -- the caret is placed to the right from where expected...
-
_this seems to only affect text with leading whitespace only, like this._
- BUG: undo: does not handle element splitting correctly...
- place cursor somewhere here, hit `Enter`, and then undo.
- _this will correctly restore the old node but will not remove the new one_
- need to:
- add undo to `.Block(..)`
- group `new` and `cange` into one undo action...
- _undo chaining? ...i.e. support nested arrays?_
- BUG: mobile browsers behave quite chaotically ignoring parts of the styling... - BUG: mobile browsers behave quite chaotically ignoring parts of the styling...
- FF: - FF:
- zooming on edited field - zooming on edited field
@ -56,7 +66,31 @@ var setup = function(){
- side margins are a bit too large (account for toolbat to the right) - side margins are a bit too large (account for toolbat to the right)
- -
- ## ToDo: - ## ToDo:
- selection / multiple node selection (via shift+motion) - trailing whitespace is ignored in `.view`... (BUG?)
- this node has a second empty line
- there are several ways to deal with this:#
- remove trailing whitespace completely on refocus (a-la logseq)
- show whitespace in both modes
- keep current behavior
-
_BTW: leading whitespace is shown..._
- Q: should we select text without first focusing??
- _...logseq does not do this either_
- editor as a custom element / web component
- DONE data interface:
collapsed:: true
- the "natural" way to pass data is to use the same mechanism as `<textarea>` the problem is that we can't extend `HTMLTextAreaElement` as it can not have shadow dom (reject?)
- adding an explicit textarea element is an odd requirement (reject?)
- seems that the least bad way to go is to use the `value` attribute
- DONE API: directly mixin Outline?
- `.value` / `.code` should be both updated internally and also load new content when updated externally -- not yet sure how...
- events
- test nesting...
-
- selection
- multiple node selection (via shift+motion)
- touch/mouse (???)
- copy/paste nodes/trees - copy/paste nodes/trees
- numbered lists: add counters to a depth of 6-7... - numbered lists: add counters to a depth of 6-7...
- _or find a way to make them repeat..._ - _or find a way to make them repeat..._
@ -80,7 +114,6 @@ var setup = function(){
- FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)` - FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)`
- table inline editing a-la code editing -- click cell and edit directly... - table inline editing a-la code editing -- click cell and edit directly...
- a way to make a block monospace (???) - a way to make a block monospace (???)
- editor as a custom element...
- Nerd fonts (option???) - Nerd fonts (option???)
- smooth scrolling - smooth scrolling
- _...this is more complicated than adding `behavior: "smooth"` to `.scrollIntoView(..)` as scrolling animation will get interrupted by next user input..._ - _...this is more complicated than adding `behavior: "smooth"` to `.scrollIntoView(..)` as scrolling animation will get interrupted by next user input..._
@ -111,6 +144,9 @@ var setup = function(){
- empty item height is a bit off... - empty item height is a bit off...
- search? - search?
- _...not sure if search should be internal or external yet..._ - _...not sure if search should be internal or external yet..._
- DONE might be a good idea to focus the prev (empty) node if pressing `Enter` at 0 position
collapsed:: true
- <- place cursor here and press enter
- DONE make `---` block not show list bullets... - DONE make `---` block not show list bullets...
- DONE: undo: checkboxes and DONE?? - DONE: undo: checkboxes and DONE??
collapsed:: true collapsed:: true
@ -361,6 +397,9 @@ var setup = function(){
<button onclick="editor.dom.classList.toggle('show-click-zones')">show/hide click zones</button> <button onclick="editor.dom.classList.toggle('show-click-zones')">show/hide click zones</button>
<button onclick="editor.dom.classList.toggle('block-offsets')">show/hide block offsets</button> <button onclick="editor.dom.classList.toggle('block-offsets')">show/hide block offsets</button>
<hr> <hr>
<h1>Outline editor as web component</h1> <h1>Outline editor as web component</h1>
@ -375,16 +414,15 @@ var setup = function(){
<outline-editor> <outline-editor>
<textarea>- ## code enclosed in `<textarea>` element <textarea>- ## code enclosed in `<textarea>` element
- code is treated as-is - code is treated as-is
- the only exception is the closing textarea tag</textarea> - the only exception is the closing textarea tag</textarea></outline-editor>
</outline-editor>
<hr> <hr>
<outline-editor> <outline-editor>
- ## raw outline editor element - ## raw outline editor `&lt;element&gt;`
- the children are not protected - the children are not protected
- any html <elements> are going to be parsed by the browser - any html <elements> are going to be parsed by the browser</outline-editor>
</outline-editor>