From ed9ecf2a578dd83305f53968ba3f1b7c017009a6 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 26 Oct 2019 09:51:08 +0200 Subject: [PATCH] simplification of tree cache --- src/public/javascripts/entities/note_full.js | 10 +- src/public/javascripts/entities/note_short.js | 78 +++++-- src/public/javascripts/services/branches.js | 2 +- src/public/javascripts/services/cloning.js | 8 +- src/public/javascripts/services/date_notes.js | 6 +- .../services/frontend_script_api.js | 8 +- .../javascripts/services/note_detail.js | 4 +- src/public/javascripts/services/tree.js | 8 +- src/public/javascripts/services/tree_cache.js | 197 ++++++------------ src/public/javascripts/services/tree_utils.js | 10 +- 10 files changed, 159 insertions(+), 172 deletions(-) diff --git a/src/public/javascripts/entities/note_full.js b/src/public/javascripts/entities/note_full.js index d45dc3e97..5ee5723b0 100644 --- a/src/public/javascripts/entities/note_full.js +++ b/src/public/javascripts/entities/note_full.js @@ -4,8 +4,8 @@ import NoteShort from './note_short.js'; * Represents full note, specifically including note's content. */ class NoteFull extends NoteShort { - constructor(treeCache, row) { - super(treeCache, row); + constructor(treeCache, row, noteShort) { + super(treeCache, row, []); /** @param {string} */ this.content = row.content; @@ -21,6 +21,12 @@ class NoteFull extends NoteShort { /** @param {string} */ this.utcDateModified = row.utcDateModified; + + /* ugly */ + this.parents = noteShort.parents; + this.parentToBranch = noteShort.parentToBranch; + this.children = noteShort.children; + this.childToBranch = noteShort.childToBranch; } } diff --git a/src/public/javascripts/entities/note_short.js b/src/public/javascripts/entities/note_short.js index 38411c508..b737c0002 100644 --- a/src/public/javascripts/entities/note_short.js +++ b/src/public/javascripts/entities/note_short.js @@ -1,5 +1,6 @@ import server from '../services/server.js'; import Attribute from './attribute.js'; +import branches from "../services/branches.js"; const LABEL = 'label'; const LABEL_DEFINITION = 'label-definition'; @@ -13,7 +14,12 @@ const RELATION_DEFINITION = 'relation-definition'; * This note's representation is used in note tree and is kept in TreeCache. */ class NoteShort { - constructor(treeCache, row) { + /** + * @param {TreeCache} treeCache + * @param {Object.} row + * @param {Branch[]} branches - all relevant branches, i.e. where this note is either child or parent + */ + constructor(treeCache, row, branches) { this.treeCache = treeCache; /** @param {string} */ this.noteId = row.noteId; @@ -29,6 +35,55 @@ class NoteShort { this.archived = row.archived; /** @param {string} */ this.cssClass = row.cssClass; + + /** @type {string[]} */ + this.parents = []; + /** @type {string[]} */ + this.children = []; + + /** @type {Object.} */ + this.parentToBranch = {}; + + /** @type {Object.} */ + this.childToBranch = {}; + + for (const branch of branches) { + if (this.noteId === branch.noteId) { + this.parents.push(branch.parentNoteId); + this.parentToBranch[branch.parentNoteId] = branch.branchId; + } + else if (this.noteId === branch.parentNoteId) { + this.children.push(branch.noteId); + this.childToBranch[branch.noteId] = branch.branchId; + } + else { + throw new Error(`Unknown branch ${branch.branchId} for note ${this.noteId}`); + } + } + } + + addParent(parentNoteId, branchId) { + if (!this.parents.includes(parentNoteId)) { + this.parents.push(parentNoteId); + } + + this.parentToBranch[parentNoteId] = branchId; + } + + addChild(childNoteId, branchId) { + if (!this.children.includes(childNoteId)) { + this.children.push(childNoteId); + } + + this.childToBranch[childNoteId] = branchId; + + const branchIdPos = {}; + + for (const branchId of Object.values(this.childToBranch)) { + branchIdPos[branchId] = this.treeCache.branches[branchId].notePosition; + } + + this.children.sort((a, b) => branchIdPos[this.childToBranch[a]] < branchIdPos[this.childToBranch[b]] ? -1 : 1); } /** @returns {boolean} */ @@ -58,48 +113,41 @@ class NoteShort { /** @returns {Promise} */ async getBranches() { - const branchIds = this.treeCache.parents[this.noteId].map( - parentNoteId => this.treeCache.getBranchIdByChildParent(this.noteId, parentNoteId)); + const branchIds = Object.values(this.parentToBranch); return this.treeCache.getBranches(branchIds); } /** @returns {boolean} */ hasChildren() { - return this.treeCache.children[this.noteId] - && this.treeCache.children[this.noteId].length > 0; + return this.children.length > 0; } /** @returns {Promise} */ async getChildBranches() { - if (!this.treeCache.children[this.noteId]) { - return []; - } - - const branchIds = this.treeCache.children[this.noteId].map( - childNoteId => this.treeCache.getBranchIdByChildParent(childNoteId, this.noteId)); + const branchIds = Object.values(this.childToBranch); return await this.treeCache.getBranches(branchIds); } /** @returns {string[]} */ getParentNoteIds() { - return this.treeCache.parents[this.noteId] || []; + return this.parents; } /** @returns {Promise} */ async getParentNotes() { - return await this.treeCache.getNotes(this.getParentNoteIds()); + return await this.treeCache.getNotes(this.parents); } /** @returns {string[]} */ getChildNoteIds() { - return this.treeCache.children[this.noteId] || []; + return this.children; } /** @returns {Promise} */ async getChildNotes() { - return await this.treeCache.getNotes(this.getChildNoteIds()); + return await this.treeCache.getNotes(this.children); } /** diff --git a/src/public/javascripts/services/branches.js b/src/public/javascripts/services/branches.js index 1007a7a1c..a875f9073 100644 --- a/src/public/javascripts/services/branches.js +++ b/src/public/javascripts/services/branches.js @@ -214,7 +214,7 @@ async function changeNode(func, node, beforeNoteId = null, afterNoteId = null) { node.data.parentNoteId = thisNewParentNode.data.noteId; - await treeCache.moveNote(childNoteId, thisOldParentNode.data.noteId, thisNewParentNode.data.noteId, beforeNoteId, afterNoteId); + await treeCache.reloadNotes([childNoteId]); await treeService.checkFolderStatus(thisOldParentNode); await treeService.checkFolderStatus(thisNewParentNode); diff --git a/src/public/javascripts/services/cloning.js b/src/public/javascripts/services/cloning.js index 412b33494..b801e70b5 100644 --- a/src/public/javascripts/services/cloning.js +++ b/src/public/javascripts/services/cloning.js @@ -12,9 +12,7 @@ async function cloneNoteTo(childNoteId, parentNoteId, prefix) { return; } - treeCache.addBranchRelationship(resp.branchId, childNoteId, parentNoteId); - - await treeService.reloadNotes([parentNoteId]); + await treeService.reloadNotes([childNoteId, parentNoteId]); } // beware that first arg is noteId and second is branchId! @@ -28,9 +26,7 @@ async function cloneNoteAfter(noteId, afterBranchId) { const afterBranch = await treeCache.getBranch(afterBranchId); - treeCache.addBranchRelationship(resp.branchId, noteId, afterBranch.parentNoteId); - - await treeService.reloadNotes([afterBranch.parentNoteId]); + await treeService.reloadNotes([noteId, afterBranch.parentNoteId]); } export default { diff --git a/src/public/javascripts/services/date_notes.js b/src/public/javascripts/services/date_notes.js index 97da168fb..9c5429dbb 100644 --- a/src/public/javascripts/services/date_notes.js +++ b/src/public/javascripts/services/date_notes.js @@ -10,7 +10,7 @@ async function getTodayNote() { async function getDateNote(date) { const note = await server.get('date-notes/date/' + date); - await treeCache.reloadParents(note.noteId); + await treeCache.reloadNotes([note.noteId]); return await treeCache.getNote(note.noteId); } @@ -19,7 +19,7 @@ async function getDateNote(date) { async function getMonthNote(month) { const note = await server.get('date-notes/month/' + month); - await treeCache.reloadParents(note.noteId); + await treeCache.reloadNotes([note.noteId]); return await treeCache.getNote(note.noteId); } @@ -28,7 +28,7 @@ async function getMonthNote(month) { async function getYearNote(year) { const note = await server.get('date-notes/year/' + year); - await treeCache.reloadParents(note.noteId); + await treeCache.reloadNotes([note.noteId]); return await treeCache.getNote(note.noteId); } diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index edb3dc199..252e3837f 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -215,13 +215,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte * @param {string} noteId * @method */ - this.reloadNotesAndTheirChildren = async noteId => await treeCache.reloadNotesAndTheirChildren(noteId); - - /** - * @param {string} noteId - * @method - */ - this.reloadParents = async noteId => await treeCache.reloadParents(noteId); + this.reloadNotes = async noteId => await treeCache.reloadNotes(noteId); /** * Instance name identifies particular Trilium instance. It can be useful for scripts diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 96b5e39e6..c7c9e8b18 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -250,7 +250,9 @@ async function loadNoteDetail(origNotePath, options = {}) { async function loadNote(noteId) { const row = await server.get('notes/' + noteId); - return new NoteFull(treeCache, row); + const noteShort = await treeCache.getNote(noteId); + + return new NoteFull(treeCache, row, noteShort); } async function filterTabs(noteId) { diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index dfaa3e5d8..6f29f44de 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -670,10 +670,8 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusAndSelectTitle); - const noteEntity = new NoteShort(treeCache, note); - const branchEntity = new Branch(treeCache, branch); - - treeCache.add(noteEntity, branchEntity); + const noteEntity = await treeCache.getNote(note.noteId); + const branchEntity = await treeCache.getBranch(branch.branchId); let newNode = { title: newNoteName, @@ -836,7 +834,7 @@ async function reloadNotes(noteIds) { console.debug("Reloading notes", noteIds); - await treeCache.reloadNotesAndTheirChildren(noteIds); + await treeCache.reloadNotes(noteIds); const activeNotePath = noteDetailService.getActiveTabNotePath(); diff --git a/src/public/javascripts/services/tree_cache.js b/src/public/javascripts/services/tree_cache.js index 6fd3b7b45..8da92f48f 100644 --- a/src/public/javascripts/services/tree_cache.js +++ b/src/public/javascripts/services/tree_cache.js @@ -1,7 +1,6 @@ import utils from "./utils.js"; import Branch from "../entities/branch.js"; import NoteShort from "../entities/note_short.js"; -import toastService from "./toast.js"; import ws from "./ws.js"; import server from "./server.js"; @@ -14,14 +13,6 @@ class TreeCache { } init() { - /** @type {Object.} */ - this.parents = {}; - /** @type {Object.} */ - this.children = {}; - - /** @type {Object.} */ - this.childParentToBranch = {}; - /** @type {Object.} */ this.notes = {}; @@ -36,58 +27,88 @@ class TreeCache { } addResp(noteRows, branchRows) { - for (const noteRow of noteRows) { - const note = new NoteShort(this, noteRow); - - this.notes[note.noteId] = note; - } + const branchesByNotes = {}; for (const branchRow of branchRows) { const branch = new Branch(this, branchRow); this.addBranch(branch); + + branchesByNotes[branch.noteId] = branchesByNotes[branch.noteId] || []; + branchesByNotes[branch.noteId].push(branch); + + branchesByNotes[branch.parentNoteId] = branchesByNotes[branch.parentNoteId] || []; + branchesByNotes[branch.parentNoteId].push(branch); + } + + for (const noteRow of noteRows) { + const {noteId} = noteRow; + + const oldNote = this.notes[noteId]; + + if (oldNote) { + for (const childNoteId of oldNote.children) { + const childNote = this.notes[childNoteId]; + + if (childNote) { + childNote.parents = childNote.parents.filter(p => p !== noteId); + + const branchId = childNote.parentToBranch[noteId]; + + if (branchId in this.branches) { + delete this.branches[branchId]; + } + + delete childNote.parentToBranch[noteId]; + } + } + + for (const parentNoteId of oldNote.parents) { + const parentNote = this.notes[parentNoteId]; + + if (parentNote) { + parentNote.children = parentNote.children.filter(p => p !== noteId); + + const branchId = parentNote.childToBranch[noteId]; + + if (branchId in this.branches) { + delete this.branches[branchId]; + } + + delete parentNote.childToBranch[noteId]; + } + } + } + + const note = new NoteShort(this, noteRow, branchesByNotes[noteId]); + + this.notes[note.noteId] = note; + + for (const childNoteId of note.children) { + const childNote = this.notes[childNoteId]; + + if (childNote) { + childNote.addParent(noteId, note.childToBranch[childNoteId]); + } + } + + for (const parentNoteId of note.parents) { + const parentNote = this.notes[parentNoteId]; + + if (parentNote) { + parentNote.addChild(noteId, note.parentToBranch[parentNoteId]); + } + } } } - /** - * Reload notes and their children. - */ - async reloadNotesAndTheirChildren(noteIds) { + async reloadNotes(noteIds) { // first load the data before clearing the cache const resp = await server.post('tree/load', { noteIds }); - for (const noteId of noteIds) { - for (const childNoteId of this.children[noteId] || []) { - this.parents[childNoteId] = this.parents[childNoteId].filter(p => p !== noteId); - - const branchId = this.getBranchIdByChildParent(childNoteId, noteId); - - delete this.branches[branchId]; - delete this.childParentToBranch[childNoteId + '-' + noteId]; - } - - this.children[noteId] = []; - - delete this.notes[noteId]; - } - this.addResp(resp.notes, resp.branches); } - /** - * Reloads parents of given noteId - useful when new note is created to make sure note is loaded - * in a correct order. - */ - async reloadParents(noteId) { - // to be able to find parents we need first to make sure it is actually loaded - await this.getNote(noteId); - - await this.reloadNotesAndTheirChildren(this.parents[noteId] || []); - - // this is done to load the new parents for the noteId - await this.reloadNotesAndTheirChildren([noteId]); - } - /** @return {Promise} */ async getNotes(noteIds, silentNotFoundError = false) { const missingNoteIds = noteIds.filter(noteId => this.notes[noteId] === undefined); @@ -128,34 +149,6 @@ class TreeCache { addBranch(branch) { this.branches[branch.branchId] = branch; - - this.addBranchRelationship(branch.branchId, branch.noteId, branch.parentNoteId); - } - - addBranchRelationship(branchId, childNoteId, parentNoteId) { - if (parentNoteId === 'none') { // applies only to root element - return; - } - - this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId; - - this.parents[childNoteId] = this.parents[childNoteId] || []; - - if (!this.parents[childNoteId].includes(parentNoteId)) { - this.parents[childNoteId].push(parentNoteId); - } - - this.children[parentNoteId] = this.children[parentNoteId] || []; - - if (!this.children[parentNoteId].includes(childNoteId)) { - this.children[parentNoteId].push(childNoteId); - } - } - - add(note, branch) { - this.notes[note.noteId] = note; - - this.addBranch(branch); } async getBranches(branchIds) { @@ -181,60 +174,6 @@ class TreeCache { async getBranch(branchId) { return (await this.getBranches([branchId]))[0]; } - - /** @return Branch */ - async getBranchByChildParent(childNoteId, parentNoteId) { - const branchId = this.getBranchIdByChildParent(childNoteId, parentNoteId); - - return await this.getBranch(branchId); - } - - getBranchIdByChildParent(childNoteId, parentNoteId) { - const key = childNoteId + '-' + parentNoteId; - const branchId = this.childParentToBranch[key]; - - if (!branchId) { - toastService.throwError("Cannot find branch for child-parent=" + key); - } - - return branchId; - } - - /* Move note from one parent to another. */ - async moveNote(childNoteId, oldParentNoteId, newParentNoteId, beforeNoteId, afterNoteId) { - utils.assertArguments(childNoteId, oldParentNoteId, newParentNoteId); - - if (oldParentNoteId === newParentNoteId) { - return; - } - - const branchId = this.childParentToBranch[childNoteId + '-' + oldParentNoteId]; - const branch = await this.getBranch(branchId); - branch.parentNoteId = newParentNoteId; - - this.childParentToBranch[childNoteId + '-' + newParentNoteId] = branchId; - delete this.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId - - // remove old associations - this.parents[childNoteId] = this.parents[childNoteId].filter(p => p !== oldParentNoteId); - this.children[oldParentNoteId] = this.children[oldParentNoteId].filter(ch => ch !== childNoteId); - - // add new associations - this.parents[childNoteId].push(newParentNoteId); - - const children = this.children[newParentNoteId] = this.children[newParentNoteId] || []; // this might be first child - - // we try to put the note into precise order which might be used again by lazy-loaded nodes - if (beforeNoteId && children.includes(beforeNoteId)) { - children.splice(children.indexOf(beforeNoteId), 0, childNoteId); - } - else if (afterNoteId && children.includes(afterNoteId)) { - children.splice(children.indexOf(afterNoteId) + 1, 0, childNoteId); - } - else { - children.push(childNoteId); - } - } } const treeCache = new TreeCache(); diff --git a/src/public/javascripts/services/tree_utils.js b/src/public/javascripts/services/tree_utils.js index 739ffbade..79cf8b203 100644 --- a/src/public/javascripts/services/tree_utils.js +++ b/src/public/javascripts/services/tree_utils.js @@ -59,10 +59,14 @@ async function getNoteTitle(noteId, parentNoteId = null) { let {title} = note; if (parentNoteId !== null) { - const branch = await treeCache.getBranchByChildParent(noteId, parentNoteId); + const branchId = note.parentToBranch[parentNoteId]; - if (branch && branch.prefix) { - title = branch.prefix + ' - ' + title; + if (branchId) { + const branch = await treeCache.getBranch(branchId); + + if (branch && branch.prefix) { + title = branch.prefix + ' - ' + title; + } } }