From 1ce8e29e376ed61c032c731bfeb570364b4fe13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 25 Jun 2025 14:59:21 +0200 Subject: [PATCH] Tree render and navigation with note rendering --- static/css/notes2.css | 23 ++- static/js/app.mjs | 298 ++++++++++++++++++++++++++++++++++++++ static/js/mbus.mjs | 37 ++++- static/js/node.mjs | 74 +++++++++- static/js/tree.mjs | 98 +++++++++---- static/less/notes2.less | 27 ++-- views/pages/notes2.gotmpl | 20 ++- 7 files changed, 515 insertions(+), 62 deletions(-) create mode 100644 static/js/app.mjs diff --git a/static/css/notes2.css b/static/css/notes2.css index 74b6392..0a23116 100644 --- a/static/css/notes2.css +++ b/static/css/notes2.css @@ -17,15 +17,10 @@ html { display: none; } } -/* -#tree-native { - grid-area: tree; -} -*/ #tree { grid-area: tree; - padding: 16px 32px; - background-color: #333; + display: grid; + padding: 16px 0px 16px 16px; color: #ddd; z-index: 100; border-left: 2px solid #333; @@ -37,9 +32,11 @@ html { display: grid; position: relative; justify-items: center; + margin-top: 8px; margin-bottom: 8px; margin-left: 24px; margin-right: 24px; + cursor: pointer; } #tree #logo img { width: 128px; @@ -85,12 +82,18 @@ html { #tree .node .children.collapsed { display: none; } +#tree-nodes { + padding: 16px 32px; + background-color: #333; + border-radius: 8px; + box-shadow: 5px 5px 10px -5px rgba(0, 0, 0, 0.75); +} #crumbs { grid-area: crumbs; display: grid; align-items: start; justify-items: center; - margin: 0px 16px; + margin: 16px 16px; } #crumbs .crumbs { display: flex; @@ -114,6 +117,10 @@ html { user-select: none; -webkit-tap-highlight-color: transparent; } +#crumbs .crumbs .crumb a { + text-decoration: none; + color: inherit; +} #crumbs .crumbs .crumb:after { content: "•"; margin-left: 8px; diff --git a/static/js/app.mjs b/static/js/app.mjs new file mode 100644 index 0000000..c6529fc --- /dev/null +++ b/static/js/app.mjs @@ -0,0 +1,298 @@ +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 = `${html}` + 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(` +
Search
+
+ +
+
Results
+
+ `) + + this.bind('input[type="text"]', 'keydown', evt => this.search(evt)) + } + + search(event) { + if (event.key !== 'Enter') + return + + const searchFor = document.querySelector('#op-search input').value + nodeStore.search(searchFor, ROOT_NODE) + .then(res => this.displayResults(res)) + } + + displayResults(results) { + const rs = [] + 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)) + return div[0] + }) + + + const div = tmpl(`
${r.name}
`) + div[0].addEventListener('click', ()=>_notes2.current.goToNode(r.uuid)) + rs.push(...div) + + const ancDev = tmpl('
') + ancDev[0].append(...ancestors) + rs.push(ancDev[0]) + } + this.get('.results').replaceChildren(...rs) + } +} + +// vim: foldmethod=marker diff --git a/static/js/mbus.mjs b/static/js/mbus.mjs index da5f098..0daa63c 100644 --- a/static/js/mbus.mjs +++ b/static/js/mbus.mjs @@ -1,17 +1,48 @@ export class MessageBus { constructor() { + this.log = false this.bus = new EventTarget() } subscribe(eventName, fn) { - this.bus.addEventListener(eventName, fn) + if (this.log) { + console.groupCollapsed('MBUS subscribe - ', eventName); + console.trace(); // hidden in collapsed group + console.groupEnd(); + } + + this.bus.addEventListener(eventName, event=>{ + fn(event) + if (event.detail.callback !== undefined) + event.detail.callback(event) + }) } unsubscribe(eventName, fn) { + if (this.log) { + console.groupCollapsed('MBUS unsubscribe - ', eventName); + console.trace(); // hidden in collapsed group + console.groupEnd(); + } + this.bus.removeEventListener(eventName, fn) } - dispatch(eventName, data) { - this.bus.dispatchEvent(new CustomEvent(eventName, { detail: data })) + dispatch(eventName, data, callback) { + if (this.log) { + console.groupCollapsed('MBUS dispatch - ', eventName); + console.log('data', data); + console.trace(); // hidden in collapsed group + console.groupEnd(); + } + + const event = new CustomEvent(eventName, { + detail: { + data, + callback, + } + }) + + this.bus.dispatchEvent(event) } } diff --git a/static/js/node.mjs b/static/js/node.mjs index bd4dadd..7c2d64e 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -331,6 +331,61 @@ class NodeContent extends Component { }//}}} } +export class NodeUINative { + constructor(parentElement) {// {{{ + this.node = null + this.parent = parentElement + this.parent.replaceChildren(this.createElements()) + + _mbus.subscribe('NODE_UI_OPEN', event => { + this.node = event.detail.data + this.render() + }) + + _mbus.subscribe('NODE_MODIFIED', ()=>{ + document.querySelector('#crumbs .crumbs')?.classList.add('node-modified') + }) + + _mbus.subscribe('NODE_UNMODIFIED', ()=>{ + document.querySelector('#crumbs .crumbs')?.classList.remove('node-modified') + }) + }// }}} + createElements() {// {{{ + const tmpl = document.createElement('template') + tmpl.innerHTML = ` +
+ + ` + + tmpl.content.querySelector('#node-content').addEventListener('input', event=>this.contentChanged(event)) + + return tmpl.content + }// }}} + render() {// {{{ + this.parent.querySelector('.grow-wrap').style.display = (this.node === null ? 'none' : 'grid') + this.parent.querySelector('#name').innerText = this.node?.get('Name') ?? '' + this.parent.querySelector('#node-content').value = this.node?.get('Content') ?? '' + + this.resize() + return this.parent + }// }}} + + resize() {//{{{ + const textarea = this.parent.querySelector('#node-content') + textarea.parentNode.dataset.replicatedValue = textarea.value + }//}}} + contentChanged(event) {//{{{ + this.node.setContent(event.target.value) + this.resize() + }//}}} + isModified() {// {{{ + return this.node?.isModified() + }// }}} + +} + export class Node { static sort(a, b) {//{{{ if (a.data.Name < b.data.Name) return -1 @@ -340,7 +395,6 @@ export class Node { constructor(nodeData, level) {//{{{ this.Level = level this.data = nodeData - this.UUID = nodeData.UUID // Toplevel nodes are normalized to have the ROOT_NODE as parent. @@ -354,13 +408,12 @@ export class Node { this.Children = [] this.Ancestors = [] - this._content = this.data.Content - this._modified = false - this._sibling_before = null this._sibling_after = null this._parent = null + this.reset() + /* this.RenderMarkdown = signal(nodeData.RenderMarkdown) this.Markdown = false @@ -377,6 +430,10 @@ export class Node { */ }//}}} + reset() {// {{{ + this._content = this.data.Content + this._modified = false + }// }}} get(prop) {//{{{ return this.data[prop] }//}}} @@ -384,6 +441,9 @@ export class Node { // '2024-12-17T17:33:48.85939Z return new Date(Date.parse(this.data.Updated)) }//}}} + isModified() {// {{{ + return this._modified + }// }}} hasFetchedChildren() {//{{{ return this._children_fetched }//}}} @@ -436,12 +496,12 @@ export class Node { if (this.CryptoKeyID != 0 && !this._decrypted) this.#decrypt() */ - this.modified = true return this._content }//}}} - setContent(new_content) {//{{{ this._content = new_content + this._modified = true + _mbus.dispatch('NODE_MODIFIED') /* TODO - implement crypto if (this.CryptoKeyID == 0) // Logic behind plaintext not being decrypted is that @@ -456,6 +516,8 @@ export class Node { this.data.Updated = new Date().toISOString() this._modified = false + _mbus.dispatch('NODE_UNMODIFIED') + // When stored into database and ancestry was changed, // the ancestry path could be interesting. const ancestors = await nodeStore.getNodeAncestry(this) diff --git a/static/js/tree.mjs b/static/js/tree.mjs index 3c0a1ff..2bf9f7a 100644 --- a/static/js/tree.mjs +++ b/static/js/tree.mjs @@ -8,10 +8,6 @@ export class TreeNative { this.selectedNode = null this.rendered = false - window._mbus.subscribe('TREE_TRUNK_FETCHED', ()=>{ - document.getElementById('tree').append(this.render()) - }) - this.populateFirstLevel() }// }}} render() {// {{{ @@ -19,10 +15,25 @@ export class TreeNative { alert('Tree should only be rendered once.') const tmpl = document.createElement('template') - tmpl.innerHTML = '
' + tmpl.innerHTML = ` +
+ +
+ + +
+
` + + /* + onclick=${() => _mbus.dispatch('op-search')} + onclick=${() => _sync.run()} + */ + const treeEl = tmpl.content.getElementById('tree-nodes') treeEl.addEventListener('keydown', event=>this.keyHandler(event)) + tmpl.content.getElementById('logo').addEventListener('click', ()=>_app.goToNode(ROOT_NODE, false, false)) + for (const node of this.treeTrunk) { const treenode = new TreeNodeNative(this, node) this.treeNodeComponents[node.UUID] = treenode @@ -57,12 +68,23 @@ export class TreeNative { return this.expandedNodes[UUID] }//}}} setNodeExpanded(node, value) {//{{{ - // Creating a default value if it doesn't exist already. - this.getNodeExpanded(node.UUID) + let expanded = this.expandedNodes[node.UUID] + + if (expanded === undefined) { + this.expandedNodes[node.UUID] = false + expanded = false + } + + if (expanded === value) + return + this.expandedNodes[node.UUID] = value _mbus.dispatch(`NODE_EXPAND_${node.UUID}`, value) }//}}} setSelected(node, dontExpand) {//{{{ + if (node === undefined) + return + // The previously selected node, if any, needs to be rerendered // to not retain its 'selected' class. const prevUUID = this.selectedNode?.UUID @@ -143,7 +165,7 @@ export class TreeNative { } }//}}} async navigateLeft(n) {//{{{ - if (n === null) + if (n === null || n === undefined) return const expanded = this.getNodeExpanded(n.UUID) @@ -153,7 +175,7 @@ export class TreeNative { } if (n.isFirstSibling() && n.getParent().UUID !== ROOT_NODE) { - await _notes2.current.goToNode(n.getParent()?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getParent()?.UUID, dontPush: true, dontExpand: true }) return } @@ -161,14 +183,14 @@ export class TreeNative { const siblingExpanded = this.getNodeExpanded(siblingBefore?.UUID) if (siblingBefore !== null && siblingExpanded && siblingBefore.hasChildren()) { const siblingAbove = this.getLastExpandedNode(siblingBefore) - await _notes2.current.goToNode(siblingAbove?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: siblingAbove?.UUID, dontPush: true, dontExpand: true }) return } - await _notes2.current.goToNode(n.getSiblingBefore()?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getSiblingBefore()?.UUID, dontPush: true, dontExpand: true }) }//}}} async navigateRight(n) {//{{{ - if (n === null) + if (n === null || n === undefined) return const siblingAfter = n.getSiblingAfter() @@ -180,20 +202,20 @@ export class TreeNative { } if (expanded && n.hasChildren()) { - await _notes2.current.goToNode(n.Children[0]?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: n.Children[0]?.UUID, dontPush: true, dontExpand: true }) return } if (n.isLastSibling()) { const nextNode = this.getParentWithNextSibling(n) - await _notes2.current.goToNode(nextNode?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: nextNode?.UUID, dontPush: true, dontExpand: true }) return } - await _notes2.current.goToNode(n.getSiblingAfter()?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getSiblingAfter()?.UUID, dontPush: true, dontExpand: true }) }//}}} async navigateUp(n) {//{{{ - if (n === null) + if (n === null || n === undefined) return let parent = null @@ -206,22 +228,22 @@ export class TreeNative { parent = n.getParent() if (parent?.UUID === ROOT_NODE) return - await _notes2.current.goToNode(parent?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: parent?.UUID, dontPush: true, dontExpand: true }) return } if (siblingBefore !== null && siblingExpanded && siblingBefore.hasChildren()) { - await _notes2.current.goToNode(siblingBefore.Children[siblingBefore.Children.length - 1]?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: siblingBefore.Children[siblingBefore.Children.length - 1]?.UUID, dontPush: true, dontExpand: true }) return } if (siblingBefore) { - await _notes2.current.goToNode(siblingBefore.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: siblingBefore.UUID, dontPush: true, dontExpand: true }) return } }//}}} async navigateDown(n) {//{{{ - if (n === null) + if (n === null || n === undefined) return const nodeExpanded = this.getNodeExpanded(n.UUID) @@ -230,27 +252,27 @@ export class TreeNative { // Traverse upward to nearest parent with next sibling. if (!nodeExpanded && n.isLastSibling()) { const wantedNode = this.getParentWithNextSibling(n) - await _notes2.current.goToNode(wantedNode?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: wantedNode?.UUID, dontPush: true, dontExpand: true }) return } if (nodeExpanded && n.isLastSibling() && !n.hasChildren()) { const wantedNode = this.getParentWithNextSibling(n) - await _notes2.current.goToNode(wantedNode?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: wantedNode?.UUID, dontPush: true, dontExpand: true }) return } // Node not expanded. Go to this node's next sibling. // GoToNode will abort if given null. if (!nodeExpanded || !n.hasChildren()) { - await _notes2.current.goToNode(n.getSiblingAfter()?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getSiblingAfter()?.UUID, dontPush: true, dontExpand: true }) return } // Node is expanded. // Children will be visually beneath this node, if any. if (nodeExpanded && n.hasChildren()) { - await _notes2.current.goToNode(n.Children[0].UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: n.Children[0].UUID, dontPush: true, dontExpand: true }) return } }//}}} @@ -258,7 +280,7 @@ export class TreeNative { const root = await nodeStore.get(ROOT_NODE) if (root.Children.length === 0) return - await _notes2.current.goToNode(root.Children[0]?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: root.Children[0]?.UUID, dontPush: true, dontExpand: true }) }//}}} async navigateBottom() {//{{{ const root = await nodeStore.get(ROOT_NODE) @@ -270,9 +292,9 @@ export class TreeNative { if (toplevelExpanded) { const lastnode = this.getLastExpandedNode(toplevel) - await _notes2.current.goToNode(lastnode?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: lastnode?.UUID, dontPush: true, dontExpand: true }) } else - await _notes2.current.goToNode(root.Children[root.Children.length - 1]?.UUID, true, true) + _mbus.dispatch("GO_TO_NODE", { nodeUUID: root.Children[root.Children.length - 1]?.UUID, dontPush: true, dontExpand: true }) }//}}} getParentWithNextSibling(node) {//{{{ @@ -299,6 +321,17 @@ export class TreeNative { if (!state) await this.setNodeExpanded(node, false) }//}}} + async makeVisible(node) {// {{{ + const treenode = this.treeNodeComponents[node.UUID] + + const ancestors = 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?.element.scrollIntoView({ block: 'nearest' }) + }// }}} } export class TreeNodeNative { @@ -312,6 +345,7 @@ export class TreeNodeNative { this.icon_expand = document.createElement('img') this.children_populated = false + this.rendered = false this.createElements() @@ -323,7 +357,8 @@ export class TreeNodeNative { this.render(true) }) - this.rendered = false + if (this.node.Level === 0 || this.tree.getNodeExpanded(this.node.UUID)) + this.fetchChildren() }//}}} createElements() {// {{{ this.element.innerHTML = ` @@ -334,8 +369,13 @@ export class TreeNodeNative { 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', ()=>window._notes2.current.goToNode(this.node.UUID)) + + this.element.children[1].addEventListener('click', ()=>_mbus.dispatch('TREE_NODE_SELECTED', this.node)) }// }}} + async fetchChildren() {//{{{ + await this.node.fetchChildren() + this.children_populated = true + }//}}} render(force_update) {//{{{ if (this.rendered && force_update !== true) return this.element diff --git a/static/less/notes2.less b/static/less/notes2.less index a5778ed..2c57f53 100644 --- a/static/less/notes2.less +++ b/static/less/notes2.less @@ -46,16 +46,10 @@ html { } } -/* -#tree-native { - grid-area: tree; -} -*/ - #tree { grid-area: tree; - padding: 16px 32px; - background-color: #333; + display: grid; + padding: 16px 0px 16px 16px; color: #ddd; z-index: 100; // Over crumbs shadow border-left: 2px solid #333; @@ -68,9 +62,12 @@ html { display: grid; position: relative; justify-items: center; + margin-top: 8px; margin-bottom: 8px; margin-left: 24px; margin-right: 24px; + cursor: pointer; + img { width: 128px; left: -20px; @@ -130,12 +127,19 @@ html { } } +#tree-nodes { + padding: 16px 32px; + background-color: #333; + border-radius: 8px; + box-shadow: 5px 5px 10px -5px rgba(0,0,0,0.75); +} + #crumbs { grid-area: crumbs; display: grid; align-items: start; justify-items: center; - margin: 0px 16px; + margin: 16px 16px; .crumbs { background: #e4e4e4; @@ -160,6 +164,11 @@ html { cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; + + a { + text-decoration: none; + color: inherit; + } } .crumb:after { diff --git a/views/pages/notes2.gotmpl b/views/pages/notes2.gotmpl index 4a828cd..52d128a 100644 --- a/views/pages/notes2.gotmpl +++ b/views/pages/notes2.gotmpl @@ -1,11 +1,18 @@ {{ define "page" }} -
-
-
+
+
+
+
+
+ +
+
-