First steps to creating a new node
This commit is contained in:
parent
1ce8e29e37
commit
989542be91
6 changed files with 101 additions and 44 deletions
10
main.go
10
main.go
|
|
@ -334,9 +334,15 @@ func actionSyncToServer(w http.ResponseWriter, r *http.Request) { // {{{
|
||||||
return
|
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,
|
"OK": true,
|
||||||
})
|
})
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ROOT_NODE } from 'node_store'
|
import { ROOT_NODE } from 'node_store'
|
||||||
import { TreeNative } from 'tree'
|
import { TreeNative } from 'tree'
|
||||||
import { NodeUINative } from 'node'
|
import { NodeUINative, Node } from 'node'
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
constructor() {// {{{
|
constructor() {// {{{
|
||||||
|
|
@ -29,6 +29,10 @@ export class App {
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('keydown', event => this.keyHandler(event))
|
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 = new Sync()
|
||||||
window._sync.run()
|
window._sync.run()
|
||||||
|
|
@ -45,7 +49,6 @@ export class App {
|
||||||
|
|
||||||
switch (event.key.toUpperCase()) {
|
switch (event.key.toUpperCase()) {
|
||||||
case 'T':
|
case 'T':
|
||||||
console.log(document.activeElement.id)
|
|
||||||
if (document.activeElement.id === 'tree-nodes')
|
if (document.activeElement.id === 'tree-nodes')
|
||||||
document.getElementById('node-content').focus()
|
document.getElementById('node-content').focus()
|
||||||
else
|
else
|
||||||
|
|
@ -68,10 +71,12 @@ export class App {
|
||||||
this.toggleMarkdown()
|
this.toggleMarkdown()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
*/
|
||||||
case 'N':
|
case 'N':
|
||||||
this.createNode()
|
this.createNode()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
/*
|
||||||
case 'P':
|
case 'P':
|
||||||
this.showPage('node-properties')
|
this.showPage('node-properties')
|
||||||
break
|
break
|
||||||
|
|
@ -145,6 +150,18 @@ export class App {
|
||||||
|
|
||||||
await Promise.all([history, sendQueue, nodeStoreAdding])
|
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) {//{{{
|
async goToNode(nodeUUID, dontPush, dontExpand) {//{{{
|
||||||
if (nodeUUID === null || nodeUUID === undefined)
|
if (nodeUUID === null || nodeUUID === undefined)
|
||||||
return
|
return
|
||||||
|
|
@ -220,38 +237,37 @@ class Crumb {
|
||||||
}// }}}
|
}// }}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tmpl(html) {// {{{
|
||||||
|
const el = document.createElement('template')
|
||||||
|
el.innerHTML = html
|
||||||
|
return el.content.children
|
||||||
|
}// }}}
|
||||||
|
|
||||||
class Op {
|
class Op {
|
||||||
constructor(id) {
|
constructor(id) {// {{{
|
||||||
this.id = id
|
this.id = id
|
||||||
_mbus.subscribe(this.id, p => this.render(p))
|
_mbus.subscribe(this.id, p => this.render(p))
|
||||||
}
|
}// }}}
|
||||||
render(html) {
|
render(html) {// {{{
|
||||||
const op = document.getElementById('op')
|
const op = document.getElementById('op')
|
||||||
const t = document.createElement('template')
|
const t = document.createElement('template')
|
||||||
t.innerHTML = `<dialog id="${this.id}" class="op">${html}</dialog>`
|
t.innerHTML = `<dialog id="${this.id}" class="op">${html}</dialog>`
|
||||||
op.replaceChildren(t.content)
|
op.replaceChildren(t.content)
|
||||||
document.getElementById(this.id).showModal()
|
document.getElementById(this.id).showModal()
|
||||||
}
|
}// }}}
|
||||||
get(selector) {
|
get(selector) {// {{{
|
||||||
return document.querySelector(`#${this.id} ${selector}`)
|
return document.querySelector(`#${this.id} ${selector}`)
|
||||||
}
|
}// }}}
|
||||||
bind(selector, event, fn) {
|
bind(selector, event, fn) {// {{{
|
||||||
this.get(selector).addEventListener(event, evt => fn(evt))
|
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 {
|
class OpSearch extends Op {
|
||||||
constructor() {
|
constructor() {// {{{
|
||||||
super('op-search')
|
super('op-search')
|
||||||
}
|
}// }}}
|
||||||
|
render() {// {{{
|
||||||
render() {
|
|
||||||
super.render(`
|
super.render(`
|
||||||
<div class="header">Search</div>
|
<div class="header">Search</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -262,29 +278,27 @@ class OpSearch extends Op {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
this.bind('input[type="text"]', 'keydown', evt => this.search(evt))
|
this.bind('input[type="text"]', 'keydown', evt => this.search(evt))
|
||||||
}
|
}// }}}
|
||||||
|
search(event) {// {{{
|
||||||
search(event) {
|
|
||||||
if (event.key !== 'Enter')
|
if (event.key !== 'Enter')
|
||||||
return
|
return
|
||||||
|
|
||||||
const searchFor = document.querySelector('#op-search input').value
|
const searchFor = document.querySelector('#op-search input').value
|
||||||
nodeStore.search(searchFor, ROOT_NODE)
|
nodeStore.search(searchFor, ROOT_NODE)
|
||||||
.then(res => this.displayResults(res))
|
.then(res => this.displayResults(res))
|
||||||
}
|
}// }}}
|
||||||
|
displayResults(results) {// {{{
|
||||||
displayResults(results) {
|
|
||||||
const rs = []
|
const rs = []
|
||||||
for (const r of results) {
|
for (const r of results) {
|
||||||
const ancestors = r.ancestry.reverse().map(a => {
|
const ancestors = r.ancestry.reverse().map(a => {
|
||||||
const div = tmpl(`<div class="ancestor">${a.data.Name}</div>`)
|
const div = tmpl(`<div class="ancestor">${a.data.Name}</div>`)
|
||||||
div[0].addEventListener('click', ()=>_notes2.current.goToNode(a.UUID))
|
div[0].addEventListener('click', () => _notes2.current.goToNode(a.UUID))
|
||||||
return div[0]
|
return div[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const div = tmpl(`<div>${r.name}</div>`)
|
const div = tmpl(`<div>${r.name}</div>`)
|
||||||
div[0].addEventListener('click', ()=>_notes2.current.goToNode(r.uuid))
|
div[0].addEventListener('click', () => _notes2.current.goToNode(r.uuid))
|
||||||
rs.push(...div)
|
rs.push(...div)
|
||||||
|
|
||||||
const ancDev = tmpl('<div class="ancestors"></div>')
|
const ancDev = tmpl('<div class="ancestors"></div>')
|
||||||
|
|
@ -292,7 +306,7 @@ class OpSearch extends Op {
|
||||||
rs.push(ancDev[0])
|
rs.push(ancDev[0])
|
||||||
}
|
}
|
||||||
this.get('.results').replaceChildren(...rs)
|
this.get('.results').replaceChildren(...rs)
|
||||||
}
|
}// }}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// vim: foldmethod=marker
|
// vim: foldmethod=marker
|
||||||
|
|
|
||||||
|
|
@ -342,11 +342,11 @@ export class NodeUINative {
|
||||||
this.render()
|
this.render()
|
||||||
})
|
})
|
||||||
|
|
||||||
_mbus.subscribe('NODE_MODIFIED', ()=>{
|
_mbus.subscribe('NODE_MODIFIED', () => {
|
||||||
document.querySelector('#crumbs .crumbs')?.classList.add('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')
|
document.querySelector('#crumbs .crumbs')?.classList.remove('node-modified')
|
||||||
})
|
})
|
||||||
}// }}}
|
}// }}}
|
||||||
|
|
@ -359,7 +359,7 @@ export class NodeUINative {
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
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
|
return tmpl.content
|
||||||
}// }}}
|
}// }}}
|
||||||
|
|
@ -392,7 +392,20 @@ export class Node {
|
||||||
if (a.data.Name > b.data.Name) return 0
|
if (a.data.Name > b.data.Name) return 0
|
||||||
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) {//{{{
|
constructor(nodeData, level) {//{{{
|
||||||
|
|
||||||
this.Level = level
|
this.Level = level
|
||||||
this.data = nodeData
|
this.data = nodeData
|
||||||
this.UUID = nodeData.UUID
|
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
|
// vim: foldmethod=marker
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,7 @@ export class NodeStore {
|
||||||
})
|
})
|
||||||
}//}}}
|
}//}}}
|
||||||
|
|
||||||
|
/* TODO - Remove?
|
||||||
async storeNode(node) {//{{{
|
async storeNode(node) {//{{{
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const t = this.db.transaction('nodes', 'readwrite')
|
const t = this.db.transaction('nodes', 'readwrite')
|
||||||
|
|
@ -181,6 +182,7 @@ export class NodeStore {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}//}}}
|
}//}}}
|
||||||
|
*/
|
||||||
|
|
||||||
async upsertNodeRecords(records) {//{{{
|
async upsertNodeRecords(records) {//{{{
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ export class Sync {
|
||||||
const res = await API.query('POST', '/sync/to_server', request)
|
const res = await API.query('POST', '/sync/to_server', request)
|
||||||
if (!res.OK) {
|
if (!res.OK) {
|
||||||
// TODO - implement better error management here.
|
// TODO - implement better error management here.
|
||||||
console.log(res)
|
console.error(res)
|
||||||
alert(res)
|
alert(res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,16 @@ export class TreeNative {
|
||||||
<div id="tree-nodes" tabindex=0>
|
<div id="tree-nodes" tabindex=0>
|
||||||
<div id="logo"><img src="/images/${_VERSION}/logo.svg" /></div>
|
<div id="logo"><img src="/images/${_VERSION}/logo.svg" /></div>
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
<img src="/images/${_VERSION}/icon_search.svg" style="height: 22px" />
|
<img class='search' src="/images/${_VERSION}/icon_search.svg" style="height: 22px" />
|
||||||
<img src="/images/${_VERSION}/icon_refresh.svg" />
|
<img class='sync' src="/images/${_VERSION}/icon_refresh.svg" />
|
||||||
</div>
|
</div>
|
||||||
<div>`
|
<div>`
|
||||||
|
|
||||||
/*
|
|
||||||
onclick=${() => _mbus.dispatch('op-search')}
|
|
||||||
onclick=${() => _sync.run()}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const treeEl = tmpl.content.getElementById('tree-nodes')
|
const treeEl = tmpl.content.getElementById('tree-nodes')
|
||||||
|
|
||||||
treeEl.addEventListener('keydown', event=>this.keyHandler(event))
|
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))
|
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)
|
const treenode = new TreeNodeNative(this, node)
|
||||||
this.treeNodeComponents[node.UUID] = treenode
|
this.treeNodeComponents[node.UUID] = treenode
|
||||||
treeEl.appendChild(treenode.render())
|
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
|
this.rendered = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue