From 4976a6ebe0e19b731247b4f88cf7e41087d8b627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 8 Jun 2026 20:27:43 +0200 Subject: [PATCH] Fix quick arrow navigation not collapsing nodes correctly --- static/js/app.mjs | 42 ++++++++++++++++++----- static/js/node_store.mjs | 4 +-- static/js/page_history.mjs | 7 ++-- static/js/page_node.mjs | 7 ++-- static/js/sidebar.mjs | 69 +++++++++++--------------------------- 5 files changed, 60 insertions(+), 69 deletions(-) diff --git a/static/js/app.mjs b/static/js/app.mjs index 2b5cbe1..0842b03 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -6,11 +6,16 @@ import { Node } from 'node' export class App { constructor() {// {{{ this.currentNode = null - this.sidebar = new N2Sidebar() // XXX - rename this.tree + this.sidebar = new N2Sidebar() this.crumbs = new N2Crumbs() this.crumbsElement = document.getElementById('crumbs') this.nodeUI = document.getElementById('note') + this.showNodeEventSequence = new ShowNodeEventSequence() // discard all GO_TO_NODE events + this.sidebar.render().then(sidebar => { + document.getElementById('tree').append(sidebar) + document.getElementById('tree-nodes')?.focus() + }) _mbus.subscribe('TREE_TRUNK_FETCHED', async () => { // Subscribing to the start node existing after the tree trunk is @@ -18,9 +23,6 @@ export class App { // root node itself, and the root node should be selected in the tree // after it is rendered when the site is shown without UUID in the URL. const startNode = await this.getStartNode() - _mbus.subscribe(`NODE_COMPONENT_EXIST_${startNode.UUID}`, () => { - this.goToNode(startNode.UUID, false, false) - }) document.getElementById('tree').append(await this.sidebar.render()) document.getElementById('tree-nodes')?.focus() @@ -187,14 +189,36 @@ export class App { this.sidebar.setSelected(node, dontExpand) const ancestors = await nodeStore.getNodeAncestry(node) - _mbus.dispatch('CRUMBS_SET', ancestors, () => this.crumbsElement.replaceChildren(this.crumbs.render())) - _mbus.dispatch('NODE_UI_OPEN', node) - _mbus.dispatch('NODE_UNMODIFIED') - _mbus.dispatch('TREE_EXPANSION', { expand: false, when: 'narrow' }) // Scrolls node into view. - this.sidebar.makeVisible(node) + // makeVisible normally expands all ancestor nodes to make the whole chain visible. + // This is a bad idea when quickly navigating the tree, since the arrow navigation + // has collapsed nodes which the event calling goToNode can come to undo, if the + // event processing lags behind. + if (!dontExpand) + await this.sidebar.makeVisible(node, ancestors) + + _mbus.dispatch('CRUMBS_SET', ancestors, () => this.crumbsElement.replaceChildren(this.crumbs.render())) + _mbus.dispatch('NODE_UI_OPEN', { node, eventSequence: this.showNodeEventSequence.next() }) + _mbus.dispatch('TREE_EXPANSION', { expand: false, when: 'narrow' }) + _mbus.dispatch('NODE_UNMODIFIED') }//}}} + pageIsVisible(page) {// {{{ + let classList = document.querySelector('#main-page').classList + return classList.contains(page) + }// }}} +} + +class ShowNodeEventSequence { + constructor() { + this.seq = 0 + } + next() { + return ++this.seq + } + current() { + return this.seq + } } class N2Crumbs extends CustomHTMLElement { diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs index bd2a331..3bd2701 100644 --- a/static/js/node_store.mjs +++ b/static/js/node_store.mjs @@ -199,9 +199,7 @@ export class NodeStore { } Promise.all(hasChildrenPromises) - .then(() => { - resolve(nodes) - }) + .then(() => resolve(nodes)) } req.onerror = (event) => reject(event.target.error) }) diff --git a/static/js/page_history.mjs b/static/js/page_history.mjs index b03c810..44061f6 100644 --- a/static/js/page_history.mjs +++ b/static/js/page_history.mjs @@ -80,8 +80,11 @@ export class N2PageHistory extends CustomHTMLElement { }) - _mbus.subscribe('NODE_UI_OPEN', async (event) => { - await this.useNode(event.detail.data) + _mbus.subscribe('SHOW_PAGE', async (event) => { + if(event.detail.data.page != 'history') + return + + await this.useNode(_app.nodeUI.node) this.render() }) diff --git a/static/js/page_node.mjs b/static/js/page_node.mjs index 9a82b90..a2ebf52 100644 --- a/static/js/page_node.mjs +++ b/static/js/page_node.mjs @@ -42,7 +42,8 @@ export class N2PageNodeUI extends CustomHTMLElement { this.marked = new MarkedPosition() _mbus.subscribe('NODE_UI_OPEN', event => { - this.node = event.detail.data + console.log(event.detail.data.eventSequence, _app.showNodeEventSequence.current()) + this.node = event.detail.data.node this.showMarkdown(true) this.render() }) @@ -376,10 +377,6 @@ export class Node { this.Children[i]._parent = this } - // Notify the tree that all children are fetched and ready to process. - //_notes2.current.tree.fetchChildrenOn(this.UUID) - _mbus.dispatch(`NODE_CHILDREN_FETCHED_${this.UUID}`) - return this.Children }//}}} setHasChildren(v) {// {{{ diff --git a/static/js/sidebar.mjs b/static/js/sidebar.mjs index c0ef99e..9c06e81 100644 --- a/static/js/sidebar.mjs +++ b/static/js/sidebar.mjs @@ -1,7 +1,6 @@ import { ROOT_NODE } from 'node_store' import { CustomHTMLElement } from './lib/custom_html_element.mjs' import { Color, Solver } from './lib/css_colorize.mjs' -import { Node } from './page_node.mjs' // TreeExpandedHandler is responsible for collapsing or expanding // the node tree, wide view or narrow "mobile" view. @@ -145,24 +144,21 @@ export class N2Sidebar extends CustomHTMLElement { treenode.render(true) }) - this.populateFirstLevel() - /* XXX - set color */ let color = new Color(0x80, 0x00, 0x33) let solver = new Solver(color) let result = solver.solve() - // console.log(result.filter) + console.log(result.filter) }// }}} async render() {// {{{ if (this.rendered) alert('Tree should only be rendered once.') - this.expandedNodes[ROOT_NODE] = true const startnode = await nodeStore.get(ROOT_NODE) const starttreenode = new N2TreeNode(this, startnode, null) this.treeNodeComponents[startnode.UUID] = starttreenode - this.elTreenodes.appendChild(starttreenode.render()) + this.elTreenodes.appendChild(await starttreenode.render()) this.rendered = true return this @@ -174,27 +170,6 @@ export class N2Sidebar extends CustomHTMLElement { this.elTreenodes.replaceChildren() this.populateFirstLevel() }// }}} - populateFirstLevel() {//{{{ - nodeStore.get(ROOT_NODE) - .then(node => node.fetchChildren()) - .then(children => { - this.treeNodeComponents = {} - this.treeTrunk = [] - for (const node of children) { - // The root node isn't supposed to be shown in the tree. - if (node.UUID === ROOT_NODE) - continue - if (node.ParentUUID === ROOT_NODE) - this.treeTrunk.push(node) - } - _mbus.dispatch('TREE_TRUNK_FETCHED') - }) - .catch(e => { - console.error(e) - console.log(e.type, e.error) - alert(e.error) - }) - }//}}} getNodeExpanded(UUID) {//{{{ if (this.expandedNodes[UUID] === undefined) this.expandedNodes[UUID] = false @@ -249,6 +224,8 @@ export class N2Sidebar extends CustomHTMLElement { // Holding shift down does it recursively. case Space: case 'Enter': + if (n.UUID === ROOT_NODE) + return const expanded = this.getNodeExpanded(n.UUID) if (event.shiftKey) { this.recursiveExpand(n, !expanded) @@ -302,7 +279,7 @@ export class N2Sidebar extends CustomHTMLElement { return const expanded = this.getNodeExpanded(n.UUID) - if (expanded && n.hasChildren()) { + if (expanded && n.hasChildren() && n.UUID !== ROOT_NODE) { this.setNodeExpanded(n, false) return } @@ -452,15 +429,14 @@ export class N2Sidebar extends CustomHTMLElement { if (!state) await this.setNodeExpanded(node, false) }//}}} - async makeVisible(node) {// {{{ + async makeVisible(node, providedAncestors) {// {{{ const treenode = this.treeNodeComponents[node.UUID] - const ancestors = await nodeStore.getNodeAncestry(node) + const ancestors = providedAncestors || await nodeStore.getNodeAncestry(node) for (const ancestor of ancestors.reverse()) { this.setNodeExpanded(ancestor, true) } - // The ROOT_NODE for example hasn't got a treenode. treenode?.scrollIntoView({ block: 'nearest' }) }// }}} } @@ -517,33 +493,25 @@ export class N2TreeNode extends CustomHTMLElement { this.elExpandToggle.addEventListener('click', () => this.sidebar.setNodeExpanded(this.node, !this.sidebar.getNodeExpanded(this.node.UUID))) this.elName.addEventListener('click', () => _mbus.dispatch('TREE_NODE_SELECTED', this.node)) - _mbus.subscribe(`NODE_CHILDREN_FETCHED_${node.UUID}`, () => { - this.render(true) - }) - _mbus.subscribe(`NODE_EXPAND_${node.UUID}`, _state => { this.render(true) }) - - if (this.node.Level === 0 || this.sidebar.getNodeExpanded(this.node.UUID)) - this.fetchChildren() }// }}} - async fetchChildren() {//{{{ + async fetchChildren(force_fetch) {//{{{ + if (this.children_populated && !force_fetch) + return + await this.node.fetchChildren() this.children_populated = true }//}}} - render(force_update) {//{{{ + async render(force_update, force_refetch_children) {//{{{ if (this.rendered && force_update !== true) 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.hasChildren() && this.sidebar.getNodeExpanded(this.node.UUID) + if (this.sidebar.getNodeExpanded(this.node.UUID)) + await this.fetchChildren() - if (!this.children_populated && this.sidebar.getNodeExpanded(this.parent?.node.UUID)) { - this.node.fetchChildren().then(() => this.children_populated = true) - } - - // Update the name and selected status + // Update the name and selected status. this.elName.querySelector('span').innerText = this.node.get('Name') if (this.sidebar.isSelected(this.node)) @@ -552,6 +520,7 @@ export class N2TreeNode extends CustomHTMLElement { this.elName.classList.remove('selected') // Update expansion state + const expanded = this.node.hasChildren() && this.sidebar.getNodeExpanded(this.node.UUID) if (expanded) { this.elChildren.classList.add('expanded') this.elChildren.classList.remove('collapsed') @@ -571,7 +540,6 @@ export class N2TreeNode extends CustomHTMLElement { this.setImgSrc(this.elExpand, `/images/${window._VERSION}/collapsed.svg`) // Should children be rendered? - this.elChildren.innerHTML = '' let children = [] if (expanded) children = this.node.Children.map(node => { @@ -579,13 +547,14 @@ export class N2TreeNode extends CustomHTMLElement { if (treenode === undefined) { treenode = new N2TreeNode(this.sidebar, node, this) this.sidebar.treeNodeComponents[node.UUID] = treenode - _mbus.dispatch(`NODE_COMPONENT_EXIST_${node.UUID}`) } return treenode }) + const renderedChildren = [] for (const c of children) - this.elChildren.appendChild(c.render()) + renderedChildren.push(await c.render()) + this.elChildren.replaceChildren(...renderedChildren) this.rendered = true return this