diff --git a/static/js/lib/custom_html_element.mjs b/static/js/lib/custom_html_element.mjs
new file mode 100644
index 0000000..dedb5d8
--- /dev/null
+++ b/static/js/lib/custom_html_element.mjs
@@ -0,0 +1,57 @@
+export class CustomHTMLElement extends HTMLElement {
+ constructor() {// {{{
+ super()
+
+ this.appendChild(this.constructor.tmpl.content.cloneNode(true))
+
+ this.querySelectorAll('*').forEach(el => {
+ const field = el.dataset.field
+ if (field !== undefined) {
+ const fieldName = this.toElementName('field', field)
+ this[fieldName] = el
+ }
+
+ const name = el.dataset.el
+ if (name !== undefined) {
+ const elName = this.toElementName('el', name)
+ this[elName] = el
+ el.classList.add('el-' + name)
+ }
+ })
+ }// }}}
+ toElementName(prefix, str) {// {{{
+ str = prefix + '-' + str
+ return str.replace(/-(id|[a-z])/g, match => match.toUpperCase().replace('-', ''))
+ }// }}}
+}
+
+export class StupidPreactCustomHTMLElement extends HTMLElement {
+ constructor() {// {{{
+ super()
+
+ // Stupid stuff because of Preact.
+ this.clonedNodes = this.constructor.tmpl.content.cloneNode(true)
+ this.clonedNodes.querySelectorAll('*').forEach(el => {
+ const field = el.dataset.field
+ if (field !== undefined) {
+ const fieldName = this.toElementName('field', field)
+ this[fieldName] = el
+ }
+
+ const name = el.dataset.el
+ if (name !== undefined) {
+ const elName = this.toElementName('el', name)
+ this[elName] = el
+ el.classList.add('el-' + name)
+ }
+ })
+ }// }}}
+ toElementName(prefix, str) {// {{{
+ str = prefix + '-' + str
+ return str.replace(/-(id|[a-z])/g, match => match.toUpperCase().replace('-', ''))
+ }// }}}
+ connectedCallback() {// {{{
+ // Stupid stuff because of Preact.
+ this.appendChild(this.clonedNodes)
+ }// }}}
+}
diff --git a/static/js/notes2.mjs b/static/js/notes2.mjs
index 22820f0..7bef2ad 100644
--- a/static/js/notes2.mjs
+++ b/static/js/notes2.mjs
@@ -3,7 +3,7 @@ import { signal } from 'preact/signals'
import htm from 'htm'
import { Node, NodeUI } from 'node'
import { ROOT_NODE } from 'node_store'
-import { TreeNative, TreeNodeNative } from 'tree'
+import { TreeNative } from 'tree'
const html = htm.bind(h)
export class Notes2 extends Component {
@@ -542,13 +542,13 @@ class OpSearch extends Op {
for (const r of results) {
const ancestors = r.ancestry.reverse().map(a => {
const div = tmpl(`
${a.data.Name}
`)
- div[0].addEventListener('click', ()=>_notes2.current.goToNode(a.UUID))
+ div[0].addEventListener('click', () => _notes2.current.goToNode(a.UUID))
return div[0]
})
const div = tmpl(`${r.name}
`)
- div[0].addEventListener('click', ()=>_notes2.current.goToNode(r.uuid))
+ div[0].addEventListener('click', () => _notes2.current.goToNode(r.uuid))
rs.push(...div)
const ancDev = tmpl('')
diff --git a/static/js/tree.mjs b/static/js/tree.mjs
index b66736d..43649cc 100644
--- a/static/js/tree.mjs
+++ b/static/js/tree.mjs
@@ -1,4 +1,5 @@
import { ROOT_NODE } from 'node_store'
+import { CustomHTMLElement } from './lib/custom_html_element.mjs'
export class TreeNative {
constructor() {// {{{
@@ -26,14 +27,14 @@ export class TreeNative {
const treeEl = tmpl.content.getElementById('tree-nodes')
- treeEl.addEventListener('keydown', event=>this.keyHandler(event))
- tmpl.content.querySelector('.icons .search').addEventListener('click', ()=>_mbus.dispatch('op-search'))
- tmpl.content.querySelector('.icons .sync').addEventListener('click', ()=>_sync.run())
+ treeEl.addEventListener('keydown', event => this.keyHandler(event))
+ tmpl.content.querySelector('.icons .search').addEventListener('click', () => _mbus.dispatch('op-search'))
+ tmpl.content.querySelector('.icons .sync').addEventListener('click', () => _sync.run())
- tmpl.content.getElementById('logo').addEventListener('click', ()=>_app.goToNode(ROOT_NODE, false, false))
+ tmpl.content.getElementById('logo').addEventListener('click', () => _app.goToNode(ROOT_NODE, false, false))
for (const node of this.treeTrunk) {
- const treenode = new TreeNodeNative(this, node)
+ const treenode = new N2TreeNode(this, node)
this.treeNodeComponents[node.UUID] = treenode
treeEl.appendChild(treenode.render())
}
@@ -326,47 +327,46 @@ export class TreeNative {
}
// The ROOT_NODE for example hasn't got a treenode.
- treenode?.element.scrollIntoView({ block: 'nearest' })
+ treenode?.scrollIntoView({ block: 'nearest' })
}// }}}
}
-export class TreeNodeNative {
+export class N2TreeNode extends CustomHTMLElement {
+ static {// {{{
+ this.tmpl = document.createElement('template')
+ this.tmpl.innerHTML = `
+
+
![]()
+
+
+
+ `
+ }// }}}
+
constructor(tree, node, parent) {//{{{
+ super()
+ this.classList.add('node')
+
this.tree = tree
this.node = node
this.parent = parent
- this.element = document.createElement('div')
- this.element.classList.add('node')
- this.icon_expand = document.createElement('img')
-
this.children_populated = false
this.rendered = false
- this.createElements()
+ this.elExpandToggle.addEventListener('click', () => this.tree.setNodeExpanded(this.node, !this.tree.getNodeExpanded(this.node.UUID)))
+ this.elName.addEventListener('click', () => _mbus.dispatch('TREE_NODE_SELECTED', this.node))
- _mbus.subscribe(`NODE_CHILDREN_FETCHED_${node.UUID}`, ()=>{
+ _mbus.subscribe(`NODE_CHILDREN_FETCHED_${node.UUID}`, () => {
this.render(true)
})
- _mbus.subscribe(`NODE_EXPAND_${node.UUID}`, state=>{
+ _mbus.subscribe(`NODE_EXPAND_${node.UUID}`, state => {
this.render(true)
})
if (this.node.Level === 0 || this.tree.getNodeExpanded(this.node.UUID))
this.fetchChildren()
- }//}}}
- createElements() {// {{{
- this.element.innerHTML = `
-
-
-
- `
-
- this.element.children[0].addEventListener('click', ()=>this.tree.setNodeExpanded(this.node, !this.tree.getNodeExpanded(this.node.UUID)))
- this.element.children[0].appendChild(this.icon_expand)
-
- this.element.children[1].addEventListener('click', ()=>_mbus.dispatch('TREE_NODE_SELECTED', this.node))
}// }}}
async fetchChildren() {//{{{
await this.node.fetchChildren()
@@ -374,53 +374,57 @@ export class TreeNodeNative {
}//}}}
render(force_update) {//{{{
if (this.rendered && force_update !== true)
- return this.element
+ return this
// Fetch the next level of children if the parent tree node is expanded and our children thus will be visible.
const expanded = this.node.Children.length > 0 && this.tree.getNodeExpanded(this.node.UUID)
- const selected = this.tree.isSelected(this.node) ? 'selected' : ''
if (!this.children_populated && this.tree.getNodeExpanded(this.parent?.node.UUID)) {
- this.node.fetchChildren().then(()=>this.children_populated = true)
+ this.node.fetchChildren().then(() => this.children_populated = true)
}
// Update the name and selected status
- this.element.children[1].innerText = this.node.get('Name')
- this.element.children[1].className = `name ${selected}`
+ this.elName.innerText = this.node.get('Name')
+ if (this.tree.isSelected(this.node))
+ this.elName.classList.add('selected')
+ else
+ this.elName.classList.remove('selected')
// Update expansion state
- this.element.children[2].className = `children ${expanded ? 'expanded' : 'collapsed'}`
-
- // The expand icon
is cached to not get a flickering when re-rendering.
- if (this.icon_expand === null)
- this.icon_expand = document.createElement('img')
+ if (expanded) {
+ this.elChildren.classList.add('expanded')
+ this.elChildren.classList.remove('collapsed')
+ } else {
+ this.elChildren.classList.remove('expanded')
+ this.elChildren.classList.add('collapsed')
+ }
+ // The expand icon
is only changed to not get a flickering when re-rendering.
if (this.node.Children.length === 0)
- this.setImgSrc(this.icon_expand, `/images/${window._VERSION}/leaf.svg`)
+ this.setImgSrc(this.elExpand, `/images/${window._VERSION}/leaf.svg`)
else if (this.tree.getNodeExpanded(this.node.UUID))
- this.setImgSrc(this.icon_expand, `/images/${window._VERSION}/expanded.svg`)
+ this.setImgSrc(this.elExpand, `/images/${window._VERSION}/expanded.svg`)
else
- this.setImgSrc(this.icon_expand, `/images/${window._VERSION}/collapsed.svg`)
+ this.setImgSrc(this.elExpand, `/images/${window._VERSION}/collapsed.svg`)
// Should children be rendered?
- this.element.children[2].innerHTML = ''
+ this.elChildren.innerHTML = ''
let children = []
if (expanded)
children = this.node.Children.map(node => {
let treenode = this.tree.treeNodeComponents[node.UUID]
if (treenode === undefined) {
- treenode = new TreeNodeNative(this.tree, node, this)
+ treenode = new N2TreeNode(this.tree, node, this)
this.tree.treeNodeComponents[node.UUID] = treenode
}
return treenode
- //return html`<${TreeNode} key=${`treenode_${node.UUID}`} tree=${tree} node=${node} parent=${this} ref=${tree.treeNodeComponents[node.UUID]} selected=${node.UUID === tree.props.app.startNode?.UUID} />`
})
- for(const c of children)
- this.element.children[2].appendChild(c.render())
+ for (const c of children)
+ this.elChildren.appendChild(c.render())
this.rendered = true
- return this.element
+ return this
}//}}}
setImgSrc(img, newSrc) {// {{{
@@ -429,5 +433,6 @@ export class TreeNodeNative {
img.setAttribute('src', newSrc)
}// }}}
}
+customElements.define('n2-treenode', N2TreeNode)
// vim: foldmethod=marker