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 = ``
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(`
@@ -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