import { ROOT_NODE } from 'node_store' import { TreeNative } from 'tree' import { NodeUINative } from 'node' export class App { constructor() {// {{{ this.currentNode = null this.treeNative = new TreeNative() this.crumbs = new Crumbs() this.crumbsElement = document.getElementById('crumbs') this.nodeUI = new NodeUINative(document.getElementById('note')) _mbus.subscribe('TREE_TRUNK_FETCHED', async () => { document.getElementById('tree').append(this.treeNative.render()) document.getElementById('tree-nodes')?.focus() const startNode = await this.getStartNode() this.goToNode(startNode.UUID, false, false) }) _mbus.subscribe('TREE_NODE_SELECTED', event => { const node = event.detail.data this.goToNode(node.UUID, false, false) }) _mbus.subscribe('GO_TO_NODE', event => { const node = event.detail.data this.goToNode(node.nodeUUID, node.dontPush, node.dontExpand) }) window.addEventListener('keydown', event => this.keyHandler(event)) window._sync = new Sync() window._sync.run() }// }}} keyHandler(event) {//{{{ let handled = true // All keybindings is Alt+Shift, since the popular browsers at the time (2023) allows to override thees. // Ctrl+S is the exception to using Alt+Shift, since it is overridable and in such widespread use for saving. // Thus, the exception is acceptable to consequent use of alt+shift. if (!(event.shiftKey && event.altKey) && !(event.key.toUpperCase() === 'S' && event.ctrlKey)) return switch (event.key.toUpperCase()) { case 'T': console.log(document.activeElement.id) if (document.activeElement.id === 'tree-nodes') document.getElementById('node-content').focus() else document.getElementById('tree-nodes').focus() break case 'F': _mbus.dispatch('op-search') break /* case 'C': this.showPage('node') break case 'E': this.showPage('keys') break case 'M': this.toggleMarkdown() break case 'N': this.createNode() break case 'P': this.showPage('node-properties') break */ case 'S': this.saveNode() /* else if (this.page.value === 'node-properties') this.nodeProperties.current.save() */ break /* case 'U': this.showPage('upload') break case 'F': this.showPage('search') break */ default: handled = false } if (handled) { event.preventDefault() event.stopPropagation() } }//}}} async getStartNode() {//{{{ let nodeUUID = ROOT_NODE // Is a UUID provided on the URI as an anchor? const parts = document.URL.split('#') if (parts[1]?.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) nodeUUID = parts[1] return await nodeStore.get(nodeUUID) }//}}} async saveNode() {//{{{ if (!this.currentNode.isModified()) return /* The node history is a local store for node history. * This could be provisioned from the server or cleared if * deemed unnecessary. * * The send queue is what will be sent back to the server * to have a recorded history of the notes. * * A setting to be implemented in the future could be to * not save the history locally at all. */ const node = this.currentNode // The node is still in its old state and will present // the unmodified content to the node store. const history = nodeStore.nodesHistory.add(node) // Prepares the node object for saving. // Sets Updated value to current date and time. await node.save() // Updated node is added to the send queue to be stored on server. const sendQueue = nodeStore.sendQueue.add(node) // Updated node is saved to the primary node store. const nodeStoreAdding = nodeStore.add([node]) await Promise.all([history, sendQueue, nodeStoreAdding]) }//}}} async goToNode(nodeUUID, dontPush, dontExpand) {//{{{ if (nodeUUID === null || nodeUUID === undefined) return // Don't switch notes until saved. if (this.nodeUI.isModified()) { if (!confirm("Changes not saved. Do you want to discard changes?")) return } if (!dontPush) history.pushState({ nodeUUID }, '', `/notes2#${nodeUUID}`) const node = nodeStore.node(nodeUUID) node.reset() // any modifications are discarded. this.currentNode = node this.treeNative.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') // Scrolls node into view. this.treeNative.makeVisible(node) }//}}} } class Crumbs { constructor() {// {{{ this.crumbs = [] this.crumbsDiv = document.createElement('div') this.crumbsDiv.classList.add('crumbs') _mbus.subscribe('CRUMBS_SET', event => { this.crumbs = event.detail.data }) }// }}} render() {// {{{ const crumbs = this.crumbs.map(node => (new Crumb( node.get('Name'), node.UUID, )).render() ) const start = (new Crumb('Start', ROOT_NODE)).render() crumbs.push(start) this.crumbsDiv.replaceChildren(...crumbs.reverse()) return this.crumbsDiv }// }}} } class Crumb { constructor(label, uuid) {// {{{ this.label = label this.uuid = uuid }// }}} render() {// {{{ const crumb = document.createElement('div') crumb.classList.add('crumb') const link = document.createElement('a') link.href = `/notes2#${this.uuid}` link.innerText = this.label crumb.appendChild(link) return crumb }// }}} } class Op { constructor(id) { this.id = id _mbus.subscribe(this.id, p => this.render(p)) } render(html) { const op = document.getElementById('op') const t = document.createElement('template') t.innerHTML = `` op.replaceChildren(t.content) document.getElementById(this.id).showModal() } get(selector) { return document.querySelector(`#${this.id} ${selector}`) } bind(selector, event, fn) { this.get(selector).addEventListener(event, evt => fn(evt)) } } function tmpl(html) { const el = document.createElement('template') el.innerHTML = html return el.content.children } class OpSearch extends Op { constructor() { super('op-search') } render() { super.render(`