From da7999fb24a1113d6ab5dccff0422e5ef44d2f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 15 Jun 2026 19:13:27 +0200 Subject: [PATCH] Work on special pages --- node.go | 2 + static/css/notes2.css | 5 ++ static/images/leaf.svg | 6 +- static/images/leaf_deleted.svg | 68 ++++++++++++++ static/js/node_store.mjs | 158 +++++++++++++++------------------ static/js/page_node.mjs | 17 ++-- static/js/sidebar.mjs | 24 ++++- 7 files changed, 185 insertions(+), 95 deletions(-) create mode 100644 static/images/leaf_deleted.svg diff --git a/node.go b/node.go index e3a61d5..a25c771 100644 --- a/node.go +++ b/node.go @@ -54,6 +54,7 @@ type Node struct { DeletedSeq sql.NullInt64 `db:"deleted_seq"` Content string ContentEncrypted string `db:"content_encrypted" json:"-"` + Special bool } func NodeTree(userID, offset int, synced uint64) (nodes []TreeNode, maxSeq uint64, moreRowsExist bool, err error) { // {{{ @@ -135,6 +136,7 @@ func Nodes(userID, offset int, synced uint64, clientUUID string) (nodes []Node, FROM public.node WHERE + NOT special AND user_id = $1 AND client != $5::uuid AND ( diff --git a/static/css/notes2.css b/static/css/notes2.css index dfe4156..f6af751 100644 --- a/static/css/notes2.css +++ b/static/css/notes2.css @@ -186,6 +186,11 @@ button { img { width: auto; height: 18px; + + &.deleted { + height: 24px; + transform: translateX(3px) translateY(3px); + } } } diff --git a/static/images/leaf.svg b/static/images/leaf.svg index 9d200c3..17f4fe2 100644 --- a/static/images/leaf.svg +++ b/static/images/leaf.svg @@ -24,12 +24,12 @@ inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" inkscape:zoom="31.614857" - inkscape:cx="5.0609117" - inkscape:cy="9.5524708" + inkscape:cx="5.0450964" + inkscape:cy="9.5682862" inkscape:window-width="2190" inkscape:window-height="1401" inkscape:window-x="1463" - inkscape:window-y="0" + inkscape:window-y="18" inkscape:window-maximized="1" inkscape:current-layer="layer1" showgrid="false" /> + + +folder-openfolder-open-outlinenotebook-outlinetext-box-outlinedelete-circle diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs index 324f004..488294f 100644 --- a/static/js/node_store.mjs +++ b/static/js/node_store.mjs @@ -1,6 +1,8 @@ import { Node } from 'node' export const ROOT_NODE = '00000000-0000-0000-0000-000000000000' +export const ORPHANED_NODE = '00000000-0000-0000-0000-000000000001' +export const DELETED_NODE = '00000000-0000-0000-0000-000000000002' export class NodeStore { constructor() {//{{{ @@ -76,8 +78,7 @@ export class NodeStore { this.sendQueue = new SimpleNodeStore(this.db, 'send_queue') this.nodesHistory = new NodeHistoryStore(this.db, 'nodes_history') this.files = new SimpleNodeStore(this.db, 'files') - this.initializeRootNode() - .then(() => resolve()) + resolve() } req.onerror = (event) => { @@ -85,37 +86,6 @@ export class NodeStore { } }) }//}}} - initializeRootNode() {//{{{ - return new Promise((resolve, reject) => { - // The root node is a magical node which displays as the first node if none is specified. - // If not already existing, it will be created. - const trx = this.db.transaction('nodes', 'readwrite') - const nodes = trx.objectStore('nodes') - const getRequest = nodes.get(ROOT_NODE) - getRequest.onsuccess = (event) => { - // Root node exists - nice! - if (event.target.result !== undefined) { - resolve(event.target.result) - return - } - - const putRequest = nodes.put({ - UUID: ROOT_NODE, - Name: 'Notes2', - Content: 'Hello, World!', - Updated: new Date().toISOString(), - ParentUUID: '', - }) - putRequest.onsuccess = (event) => { - resolve(event.target.result) - } - putRequest.onerror = (event) => { - reject(event.target.error) - } - } - getRequest.onerror = (event) => reject(event.target.error) - }) - }//}}} purgeCache() {//{{{ this.nodes = {} }//}}} @@ -272,74 +242,92 @@ export class NodeStore { }//}}} get(uuid, suppliedNodestore) {//{{{ return new Promise((resolve, reject) => { + switch (uuid) { + case ROOT_NODE: + const rootNode = new Node({ UUID: ROOT_NODE, Name: 'Start', Special: true }, -1) + this.nodes[ROOT_NODE] = rootNode + resolve(rootNode) + return + case DELETED_NODE: + const deletedNode = new Node({ UUID: DELETED_NODE, Name: 'Deleted nodes', Special: true }, -1) + this.nodes[DELETED_NODE] = deletedNode + resolve(deletedNode) + return + } + // A nodestore can be provided in order to // avoid creating new transactions. let trx let nodeStore = suppliedNodestore if (nodeStore === undefined) { - trx = this.db.transaction('nodes', 'readonly') - nodeStore = trx.objectStore('nodes') + trx = this.db.transaction('nodes', 'readonly') + nodeStore = trx.objectStore('nodes') + } + + const getRequest = nodeStore.get(uuid) + + getRequest.onsuccess = (event) => { + // Node not found in IndexedDB. + if (event.target.result === undefined) { + reject("No such node") + return } + const node = this.node(uuid, event.target.result, -1) + resolve(node) + } + }) +}//}}} +getNodeAncestry(node, accumulated) {//{{{ + return new Promise((resolve, reject) => { + if (accumulated === undefined) + accumulated = [] - const getRequest = nodeStore.get(uuid) + const nodeParentIndex = this.db + .transaction('nodes', 'readonly') + .objectStore('nodes') - getRequest.onsuccess = (event) => { - // Node not found in IndexedDB. - if (event.target.result === undefined) { - reject("No such node") - return - } - const node = this.node(uuid, event.target.result, -1) - resolve(node) - } - }) - }//}}} - getNodeAncestry(node, accumulated) {//{{{ - return new Promise((resolve, reject) => { - if (accumulated === undefined) - accumulated = [] + if (node.UUID === ROOT_NODE || node.ParentUUID === ROOT_NODE) { + resolve(accumulated) + return + } - const nodeParentIndex = this.db - .transaction('nodes', 'readonly') - .objectStore('nodes') + if (node.UUID === DELETED_NODE || node.ParentUUID === DELETED_NODE) { + resolve(accumulated) + return + } - if (node.UUID === ROOT_NODE || node.ParentUUID === ROOT_NODE) { - resolve(accumulated) + const getRequest = nodeParentIndex.get(node.ParentUUID) + getRequest.onsuccess = (event) => { + // Node not found in IndexedDB. + // Not expected to happen. + const parentNodeData = event.target.result + if (parentNodeData === undefined) { + reject("No such node") return } - const getRequest = nodeParentIndex.get(node.ParentUUID) - getRequest.onsuccess = (event) => { - // Node not found in IndexedDB. - // Not expected to happen. - const parentNodeData = event.target.result - if (parentNodeData === undefined) { - reject("No such node") - return - } + const parentNode = this.node(parentNodeData.UUID, parentNodeData, -1) + this.getNodeAncestry(parentNode, accumulated.concat(parentNode)) + .then(accumulated => resolve(accumulated)) + } + }) - const parentNode = this.node(parentNodeData.UUID, parentNodeData, -1) - this.getNodeAncestry(parentNode, accumulated.concat(parentNode)) - .then(accumulated => resolve(accumulated)) - } - }) +}//}}} +newTransaction(objectStore, mode) {// {{{ + return this.db.transaction(objectStore, mode) +}// }}} - }//}}} - newTransaction(objectStore, mode) {// {{{ - return this.db.transaction(objectStore, mode) - }// }}} - - nodeCount() {//{{{ - return new Promise((resolve, reject) => { - const t = this.db.transaction('nodes', 'readwrite') - const nodeStore = t.objectStore('nodes') - const countReq = nodeStore.count() - countReq.onsuccess = event => { - resolve(event.target.result) - } - }) - }//}}} +nodeCount() {//{{{ + return new Promise((resolve, reject) => { + const t = this.db.transaction('nodes', 'readwrite') + const nodeStore = t.objectStore('nodes') + const countReq = nodeStore.count() + countReq.onsuccess = event => { + resolve(event.target.result) + } + }) +}//}}} } class SimpleNodeStore { diff --git a/static/js/page_node.mjs b/static/js/page_node.mjs index 339f903..403456b 100644 --- a/static/js/page_node.mjs +++ b/static/js/page_node.mjs @@ -46,7 +46,10 @@ export class N2PageNodeUI extends CustomHTMLElement { _mbus.subscribe('NODE_UI_OPEN', event => { this.node = event.detail.data - this.showMarkdown(true) + + + if (!this.node.isSpecial()) + this.showMarkdown(true) this.render() }) @@ -437,6 +440,9 @@ export class Node { isFirstSibling() {//{{{ return this._sibling_before === null }//}}} + isSpecial() {// {{{ + return this.data.Special + }// }}} content() {//{{{ /* TODO - implement crypto if (this.CryptoKeyID != 0 && !this._decrypted) @@ -506,16 +512,15 @@ export class Node { } class N2Menu extends CustomHTMLElement { - static { + static {// {{{ this.tmpl = document.createElement('template') this.tmpl.innerHTML = `
Popover content
` - } - - constructor() { + }// }}} + constructor() {// {{{ super() - } + }// }}} } customElements.define('n2-menu', N2Menu) diff --git a/static/js/sidebar.mjs b/static/js/sidebar.mjs index becf44d..4162208 100644 --- a/static/js/sidebar.mjs +++ b/static/js/sidebar.mjs @@ -1,4 +1,5 @@ -import { ROOT_NODE } from 'node_store' +import { ROOT_NODE, ORPHANED_NODE , DELETED_NODE} from 'node_store' +import { Node } from 'node' import { CustomHTMLElement } from './lib/custom_html_element.mjs' import { Color, Solver } from './lib/css_colorize.mjs' @@ -156,8 +157,15 @@ export class N2Sidebar extends CustomHTMLElement { this.expandedNodes[ROOT_NODE] = true const startnode = await nodeStore.get(ROOT_NODE) const starttreenode = new N2TreeNode(this, startnode, null) + + const deletednode = await nodeStore.get(DELETED_NODE) + const deletedtreenode = new SpecialNodeDeleted(this, deletednode, null) + this.treeNodeComponents[startnode.UUID] = starttreenode + this.treeNodeComponents[deletednode.UUID] = deletedtreenode + this.elTreenodes.appendChild(await starttreenode.render()) + this.elTreenodes.appendChild(await deletedtreenode.render()) // Notify the application that the initial tree is rendered (with children) // and that initial node selection can take place. App will check URL to @@ -668,6 +676,12 @@ export class N2TreeNode extends CustomHTMLElement { // The expand icon is only changed to not get a flickering when re-rendering. if (this.node.UUID === ROOT_NODE) this.setImgSrc(this.elExpand, `/images/${window._VERSION}/icon_home.svg`) + + else if (this.node.UUID === '00000000-0000-0000-0000-000000000002') { + this.setImgSrc(this.elExpand, `/images/${window._VERSION}/leaf_deleted.svg`) + this.elExpand.classList.add('deleted') + } + else if (!this.node.hasChildren()) this.setImgSrc(this.elExpand, `/images/${window._VERSION}/leaf.svg`) else if (this.sidebar.getNodeExpanded(this.node.UUID)) @@ -703,7 +717,15 @@ export class N2TreeNode extends CustomHTMLElement { }// }}} } +class SpecialNodeDeleted extends N2TreeNode { + constructor(sidebar, node, parent) {//{{{ + super(sidebar, node, parent) + this.removeAttribute('draggable') + }//}}} +} + customElements.define('n2-sidebar', N2Sidebar) customElements.define('n2-treenode', N2TreeNode) +customElements.define('n2-specialnodedeleted', SpecialNodeDeleted) // vim: foldmethod=marker