started experimenting with web component...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-10-28 22:52:46 +03:00
parent d04a6ee538
commit 883e6994c3
2 changed files with 141 additions and 13 deletions

View File

@ -769,13 +769,18 @@ var Outline = {
get header(){ get header(){
return this.dom.querySelector('.header') }, return this.dom.querySelector('.header') },
get code(){
return this.dom.querySelector('.code') },
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(){
return this.dom.querySelector('.code')?.value },
set code(value){
var c = this.dom.querySelector('.code')
if(c){
c.value = value } },
path: function(node='focused', mode='index'){ path: function(node='focused', mode='index'){
if(['index', 'text', 'node', 'data'].includes(node)){ if(['index', 'text', 'node', 'data'].includes(node)){
@ -1026,14 +1031,15 @@ var Outline = {
var parsed = {} var parsed = {}
if('text' in data){ if('text' in data){
var text = node.querySelector('textarea') var text = node.querySelector('.code')
var html = node.querySelector('span') var html = node.querySelector('.view')
if(this.__code2html__){ if(this.__code2html__){
// NOTE: we are ignoring the .collapsed attr here // NOTE: we are ignoring the .collapsed attr here
parsed = this.__code2html__(data.text, {...data}) parsed = this.__code2html__(data.text, {...data})
html.innerHTML = parsed.text html.innerHTML = parsed.text
// heading... // heading...
node.classList.remove(...this.__styles) this.__styles != null
&& node.classList.remove(...this.__styles)
parsed.style parsed.style
&& node.classList.add(...parsed.style) && node.classList.add(...parsed.style)
delete parsed.style delete parsed.style
@ -1201,6 +1207,7 @@ var Outline = {
return node }, return node },
// crop... // crop...
// XXX the header links are not component-compatible...
crop: function(node='focused'){ crop: function(node='focused'){
this.dom.classList.add('crop') this.dom.classList.add('crop')
for(var block of [...this.outline.querySelectorAll('[cropped]')]){ for(var block of [...this.outline.querySelectorAll('[cropped]')]){
@ -1212,7 +1219,9 @@ var Outline = {
+ this.path(...arguments, 'text') + this.path(...arguments, 'text')
.slice(0, -1) .slice(0, -1)
.map(function(s, i, {length}){ .map(function(s, i, {length}){
return `<span class="path-item" onclick="editor.uncrop(${ length-i })">${s}</span> ` }) return `<span class="path-item" onclick="editor.uncrop(${ length-i })">${
plugin.encode(s)
}</span> ` })
.join(' / ') .join(' / ')
return this }, return this },
uncrop: function(count=1){ uncrop: function(count=1){
@ -1563,20 +1572,20 @@ 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)...
// NOTE: this is needed to make initial clicking into multi-line
// blocks place the cursor into the clicked location.
// ...this is done by expanding the textarea to the element
// size and enabling it to intercept clicks correctly...
setTimeout(function(){ setTimeout(function(){
for(var e of [...that.outline.querySelectorAll('textarea')]){ for(var e of [...that.outline.querySelectorAll('textarea')]){
e.updateSize() } }, 0) e.updateSize() } }, 0)
//*/
// restore focus... // restore focus...
this.focus() this.focus()
return this }, return this },
sync: function(){ sync: function(){
var code = this.code this.code = this.text()
if(code){
code.value = this.text() }
return this }, return this },
@ -2109,7 +2118,7 @@ var Outline = {
var code = this.code var code = this.code
if(code){ if(code){
var t = Date.now() var t = Date.now()
this.load(code.value this.load(code
.replace(/&lt;/g, '<') .replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')) .replace(/&gt;/g, '>'))
console.log(`Parse: ${Date.now() - t}ms`) } console.log(`Parse: ${Date.now() - t}ms`) }
@ -2126,5 +2135,99 @@ var Outline = {
//---------------------------------------------------------------------
// Custom element...
window.customElements.define('outline-editor',
window.OutlineEditor =
Object.assign(
function(){
var obj = Reflect.construct(HTMLElement, [...arguments], OutlineEditor)
obj.editor = {
__proto__: Outline,
get code(){
return obj.hasAttribute('value') ?
obj.getAttribute('value')
: (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 },
{
// constructor stuff...
observedAttributes: [
'value',
],
// instance stuff...
prototype: {
__proto__: HTMLElement.prototype,
get value(){
return this.getAttribute('value') },
set value(value){
this.setAttribute('value', value) },
connectedCallback: function(){
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...
setTimeout(function(){
that.editor.setup(editor) }, 0)
editor.append(
style,
header,
outline)
shadow.append(editor) },
disconnectedCallback: function(){
},
adoptedCallback: function(){
},
attributeChangedCallback: function(name, oldvalue, newvalue){
if(name == 'value'){
console.log('---', newvalue)
//oldvalue != newvalue
// && this.editor.load(newvalue)
return }
},
},
}))
/********************************************************************** /**********************************************************************
* vim:set ts=4 sw=4 : */ * vim:set ts=4 sw=4 : */

View File

@ -354,7 +354,6 @@ var setup = function(){
<button onclick="editor.toggleCollapse()?.focus()">&#709;&#708;</button> <button onclick="editor.toggleCollapse()?.focus()">&#709;&#708;</button>
<button onclick="editor.remove()">&times;</button> <button onclick="editor.remove()">&times;</button>
</div--> </div-->
<span class="__textarea"></span>
</div> </div>
<hr> <hr>
@ -362,6 +361,32 @@ 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>
<h1>Outline editor as web component</h1>
<outline-editor value="
- ## code as part of an attribute
- as long as &quot;quotes&quot; are sanitized in the html, this is the safest">
</outline-editor>
<hr>
<outline-editor>
<textarea>- ## code enclosed in `<textarea>` element
- code is treated as-is
- the only exception is the closing textarea tag</textarea>
</outline-editor>
<hr>
<outline-editor>
- ## raw outline editor element
- the children are not protected
- any html <elements> are going to be parsed by the browser
</outline-editor>
</body> </body>
</html> </html>