Cleanup of old code, moved Node class, many improvements on file handling

This commit is contained in:
Magnus Åhall 2026-06-29 10:43:21 +02:00
parent 65f8cd14a7
commit 16992db6b1
19 changed files with 485 additions and 3281 deletions

View file

@ -1,4 +1,3 @@
import { ROOT_NODE, uuidv7 } from 'node_store'
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
import { MarkedPosition } from './marked_position.mjs'
@ -398,213 +397,4 @@ export class N2PageNodeUI extends CustomHTMLElement {
}
customElements.define('n2-nodeui', N2PageNodeUI)
export class Node {
static sort(a, b) {//{{{
// Nodes with children ("folders") are sorted first.
if (a._has_children && !b._has_children) return -1
if (!a._has_children && b._has_children) return 1
// Otherwise sort by lowercased name.
const an = a.data.Name.toLowerCase()
const bn = b.data.Name.toLowerCase()
if (an < bn) return -1
if (an > bn) return 1
return 0
}//}}}
static create(name, parentUUID) {// {{{
const node = new Node({
UUID: uuidv7(),
Created: (new Date()).toISOString(),
Content: '',
Name: name,
ParentUUID: parentUUID,
Markdown: false,
})
// Newly created node (not constructed from existing data) is considered modified
// since node.save returns early if it isn't modified.
node._modified = true
return 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.
if (nodeData.UUID !== ROOT_NODE && nodeData.ParentUUID === '') {
this.ParentUUID = ROOT_NODE
this.data.ParentUUID = ROOT_NODE
} else
this.ParentUUID = nodeData.ParentUUID
this._children_fetched = false
this._has_children = null // this will be set by nodeStore.getTreeNodes
this.Children = []
this.Ancestors = []
this._sibling_before = null
this._sibling_after = null
this._parent = null
this.reset()
}//}}}
reset() {// {{{
// this._content is the runtime content in the editor.
// this.data.Content is the original data.
this._content = this.data.Content
this._modified = false
}// }}}
get(prop) {//{{{
return this.data[prop]
}//}}}
updated() {//{{{
// '2024-12-17T17:33:48.85939Z
return new Date(Date.parse(this.data.Updated))
}//}}}
isModified() {// {{{
return this._modified
}// }}}
hasFetchedChildren() {//{{{
return this._children_fetched
}//}}}
async fetchChildren() {//{{{
this.Children = await nodeStore.getTreeNodes(this.UUID, this.Level + 1)
this._children_fetched = true
// Children are sorted to allow for storing siblings befare and after.
// These are used with keyboard navigation in the tree.
this.Children.sort(Node.sort)
const numChildren = this.Children.length
this.setHasChildren(numChildren > 0)
for (let i = 0; i < numChildren; i++) {
if (i > 0)
this.Children[i]._sibling_before = this.Children[i - 1]
if (i < numChildren - 1)
this.Children[i]._sibling_after = this.Children[i + 1]
this.Children[i]._parent = this
}
return this.Children
}//}}}
setHasChildren(v) {// {{{
this._has_children = v
}// }}}
hasChildren() {//{{{
return this._has_children
}//}}}
getSiblingBefore() {// {{{
return this._sibling_before
}// }}}
getSiblingAfter() {// {{{
return this._sibling_after
}// }}}
getParent() {//{{{
return this._parent
}//}}}
moveToParent(newParentUUID) {// {{{
if (this.UUID === newParentUUID)
throw new Error("New parent UUID is the same as node UUID. Can't be your own parent.")
this.ParentUUID = newParentUUID
this.data.ParentUUID = newParentUUID
this._modified = true
}// }}}
isLastSibling() {//{{{
return this._sibling_after === null
}//}}}
isFirstSibling() {//{{{
return this._sibling_before === null
}//}}}
isSpecial() {// {{{
return this.data.Special
}// }}}
content() {//{{{
// TODO - implement crypto
return this._content
}//}}}
setContent(new_content) {//{{{
this._content = new_content
this._modified = true
_mbus.dispatch('NODE_MODIFIED', { node: this })
}//}}}
setName(new_name) {// {{{
if (new_name.trim() === '')
throw new Error(`The name can't be empty`)
this.data.Name = new_name
this._modified = true
_mbus.dispatch('NODE_MODIFIED', { node: this })
}// }}}
async save() {//{{{
try {
const dblink = /db:\/\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi
// Just safeguarding not using the root node,
// which sort of exist but isn't supposed to communicate to server.
if (this.UUID == ROOT_NODE)
return
// File links from the original, unmodified content are found to compare to links from the new content.
// All files (images) needs to be indexed to nodes in order to track orphaned files.
const before = new Set([...this.data.Content.matchAll(dblink)].map(m => m[1]))
const after = new Set([...this._content.matchAll(dblink)].map(m => m[1]))
const removed = before.difference(after)
const added = after.difference(before)
await nodeStore.filesNodelink.removeFileUUIDs(this.UUID, removed)
await nodeStore.filesNodelink.addFileUUIDs(this.UUID, added)
// Authoritative content and metadata is set to prepare it for IndexedDB update.
this.data.Content = this._content
this.data.Updated = new Date().toISOString()
this.data.HistoryUUID = uuidv7() // every time the node is saved a new history UUID identifies the changed node.
_mbus.dispatch('NODE_UNMODIFIED')
this._modified = false
// When stored into database and ancestry was changed,
// the ancestry path could be interesting.
/*
const ancestors = await nodeStore.getNodeAncestry(this)
this.data.Ancestors = ancestors.map(a => a.get('Name')).reverse()
*/
/* 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. */
// Current node is added to history. It will be duplicated with the "nodes" store
// for simplicity, to hopefully avoid bugs.
const history = nodeStore.nodesHistory.add(this)
// Updated node is added to the send queue to be stored on server.
const sendQueue = nodeStore.sendQueue.add(this)
// Updated node is saved to the primary node store.
const nodeStoreAdding = nodeStore.add([this])
await Promise.all([history, sendQueue, nodeStoreAdding])
} catch (err) {
console.error(err)
alert(err.message)
}
return
}//}}}
}
// vim: foldmethod=marker