From 989542be914323ad7758d700e289cf6addd70ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 28 Jun 2025 09:13:26 +0200 Subject: [PATCH] First steps to creating a new node --- main.go | 10 ++++-- static/js/app.mjs | 72 ++++++++++++++++++++++++---------------- static/js/node.mjs | 45 +++++++++++++++++++++++-- static/js/node_store.mjs | 2 ++ static/js/sync.mjs | 2 +- static/js/tree.mjs | 14 +++----- 6 files changed, 101 insertions(+), 44 deletions(-) diff --git a/main.go b/main.go index a9ac728..3e5a05f 100644 --- a/main.go +++ b/main.go @@ -334,9 +334,15 @@ func actionSyncToServer(w http.ResponseWriter, r *http.Request) { // {{{ return } - db.Exec(`CALL add_nodes($1, $2, $3::jsonb)`, user.UserID, user.ClientUUID, request.NodeData) + _, err = db.Exec(`CALL add_nodes($1, $2, $3::jsonb)`, user.UserID, user.ClientUUID, request.NodeData) + if err != nil { + Log.Error("sync", "error", err) + httpError(w, err) + return + } - responseData(w, map[string]interface{}{ + + responseData(w, map[string]any{ "OK": true, }) } // }}} diff --git a/static/js/app.mjs b/static/js/app.mjs index c6529fc..7882923 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -1,6 +1,6 @@ import { ROOT_NODE } from 'node_store' import { TreeNative } from 'tree' -import { NodeUINative } from 'node' +import { NodeUINative, Node } from 'node' export class App { constructor() {// {{{ @@ -29,6 +29,10 @@ export class App { }) window.addEventListener('keydown', event => this.keyHandler(event)) + document.getElementById('notes2').addEventListener('click', event => { + if (event.target.id === 'notes2') + document.getElementById('node-content')?.focus() + }) window._sync = new Sync() window._sync.run() @@ -45,7 +49,6 @@ export class App { switch (event.key.toUpperCase()) { case 'T': - console.log(document.activeElement.id) if (document.activeElement.id === 'tree-nodes') document.getElementById('node-content').focus() else @@ -68,10 +71,12 @@ export class App { this.toggleMarkdown() break + */ case 'N': this.createNode() break + /* case 'P': this.showPage('node-properties') break @@ -145,6 +150,18 @@ export class App { await Promise.all([history, sendQueue, nodeStoreAdding]) }//}}} + async createNode() {//{{{ + let name = prompt("Name") + if (!name) + return + + const nn = Node.create(name, this.currentNode.UUID) + nn.save() + + nodeStore.sendQueue.add(nn) + nodeStore.add([nn]) + + }//}}} async goToNode(nodeUUID, dontPush, dontExpand) {//{{{ if (nodeUUID === null || nodeUUID === undefined) return @@ -220,38 +237,37 @@ class Crumb { }// }}} } +function tmpl(html) {// {{{ + const el = document.createElement('template') + el.innerHTML = html + return el.content.children +}// }}} + class Op { - constructor(id) { + constructor(id) {// {{{ this.id = id _mbus.subscribe(this.id, p => this.render(p)) - } - render(html) { + }// }}} + 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) { + }// }}} + get(selector) {// {{{ return document.querySelector(`#${this.id} ${selector}`) - } - bind(selector, event, fn) { + }// }}} + 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() { + constructor() {// {{{ super('op-search') - } - - render() { + }// }}} + render() {// {{{ super.render(`
Search
@@ -262,29 +278,27 @@ class OpSearch extends Op { `) this.bind('input[type="text"]', 'keydown', evt => this.search(evt)) - } - - search(event) { + }// }}} + 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) { + }// }}} + 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)) + 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('
') @@ -292,7 +306,7 @@ class OpSearch extends Op { rs.push(ancDev[0]) } this.get('.results').replaceChildren(...rs) - } + }// }}} } // vim: foldmethod=marker diff --git a/static/js/node.mjs b/static/js/node.mjs index 7c2d64e..dfd1052 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -342,11 +342,11 @@ export class NodeUINative { this.render() }) - _mbus.subscribe('NODE_MODIFIED', ()=>{ + _mbus.subscribe('NODE_MODIFIED', () => { document.querySelector('#crumbs .crumbs')?.classList.add('node-modified') }) - _mbus.subscribe('NODE_UNMODIFIED', ()=>{ + _mbus.subscribe('NODE_UNMODIFIED', () => { document.querySelector('#crumbs .crumbs')?.classList.remove('node-modified') }) }// }}} @@ -359,7 +359,7 @@ export class NodeUINative {
` - tmpl.content.querySelector('#node-content').addEventListener('input', event=>this.contentChanged(event)) + tmpl.content.querySelector('#node-content').addEventListener('input', event => this.contentChanged(event)) return tmpl.content }// }}} @@ -392,7 +392,20 @@ export class Node { if (a.data.Name > b.data.Name) return 0 return 0 }//}}} + static create(name, parentUUID) { + return new Node({ + UUID: uuidv7(), + Created: (new Date()).toISOString(), + Content: '', + Name: name, + ParentUUID: parentUUID, + Markdown: false, + History: false, + }) + } + constructor(nodeData, level) {//{{{ + this.Level = level this.data = nodeData this.UUID = nodeData.UUID @@ -525,4 +538,30 @@ export class Node { }//}}} } +function uuidv7() { + // random bytes + const value = new Uint8Array(16) + crypto.getRandomValues(value) + + // current timestamp in ms + const timestamp = BigInt(Date.now()) + + // timestamp + value[0] = Number((timestamp >> 40n) & 0xffn) + value[1] = Number((timestamp >> 32n) & 0xffn) + value[2] = Number((timestamp >> 24n) & 0xffn) + value[3] = Number((timestamp >> 16n) & 0xffn) + value[4] = Number((timestamp >> 8n) & 0xffn) + value[5] = Number(timestamp & 0xffn) + + // version and variant + value[6] = (value[6] & 0x0f) | 0x70 + value[8] = (value[8] & 0x3f) | 0x80 + + const str = Array.from(value) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}` +} + // vim: foldmethod=marker diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs index 80aa758..ef43233 100644 --- a/static/js/node_store.mjs +++ b/static/js/node_store.mjs @@ -159,6 +159,7 @@ export class NodeStore { }) }//}}} + /* TODO - Remove? async storeNode(node) {//{{{ return new Promise((resolve, reject) => { const t = this.db.transaction('nodes', 'readwrite') @@ -181,6 +182,7 @@ export class NodeStore { } }) }//}}} + */ async upsertNodeRecords(records) {//{{{ return new Promise((resolve, reject) => { diff --git a/static/js/sync.mjs b/static/js/sync.mjs index d66e27b..b1097a9 100644 --- a/static/js/sync.mjs +++ b/static/js/sync.mjs @@ -149,7 +149,7 @@ export class Sync { const res = await API.query('POST', '/sync/to_server', request) if (!res.OK) { // TODO - implement better error management here. - console.log(res) + console.error(res) alert(res) return } diff --git a/static/js/tree.mjs b/static/js/tree.mjs index 2bf9f7a..b66736d 100644 --- a/static/js/tree.mjs +++ b/static/js/tree.mjs @@ -19,18 +19,16 @@ export class TreeNative {
- - + +
` - /* - onclick=${() => _mbus.dispatch('op-search')} - onclick=${() => _sync.run()} - */ - 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()) tmpl.content.getElementById('logo').addEventListener('click', ()=>_app.goToNode(ROOT_NODE, false, false)) @@ -38,8 +36,6 @@ export class TreeNative { const treenode = new TreeNodeNative(this, node) this.treeNodeComponents[node.UUID] = treenode treeEl.appendChild(treenode.render()) - - //return html`<${TreeNode} key=${`treenode_${node.UUID}`} tree=${this} node=${node} ref=${this.treeNodeComponents[node.UUID]} selected=${node.UUID === app.state.startNode?.UUID} />` } this.rendered = true