From 511fb89af025e89673da5d86d5510c132706b6a3 Mon Sep 17 00:00:00 2001 From: azivner Date: Sat, 24 Mar 2018 20:41:27 -0400 Subject: [PATCH] WIP refactoring of data structures in note tree --- .idea/dataSources.xml | 14 + .../a2c75661-f9e2-478f-a69f-6a9409e69997.xml | 588 ++++++++++++++++++ .../storage_v2/_src_/schema/main.uQUzAA.meta | 2 + .idea/jsLinters/jslint.xml | 9 + .idea/misc.xml | 3 + src/public/javascripts/note_tree.js | 251 +++++--- src/routes/api/tree.js | 59 +- 7 files changed, 817 insertions(+), 109 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997.xml create mode 100644 .idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997/storage_v2/_src_/schema/main.uQUzAA.meta create mode 100644 .idea/jsLinters/jslint.xml diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 000000000..e0d09fa93 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,14 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$USER_HOME$/trilium-data/document.db + + + + + + \ No newline at end of file diff --git a/.idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997.xml b/.idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997.xml new file mode 100644 index 000000000..608eeef04 --- /dev/null +++ b/.idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997.xml @@ -0,0 +1,588 @@ + + + + + + 1 + 1 + + + + + +
+
+
+
+
+
+
+
+
+
+
+ 1 +
+ + 1 +
+ + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + INT|0s + 1 + 0 + + + 1 + apiTokenId + + 1 + + + apiTokenId + 1 + sqlite_autoindex_api_tokens_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + TEXT|0s + 1 + '' + + + 5 + INT|0s + 1 + 0 + + + 6 + TEXT|0s + 1 + + + 7 + TEXT|0s + 1 + + + 8 + INT|0s + 1 + + + 1 + attributeId + + 1 + + + noteId + + + + name +value + + + + + attributeId + 1 + sqlite_autoindex_attributes_1 + + + 1 + INTEGER|0s + 1 + 1 + + + 2 + TEXT|0s + + + 3 + TEXT|0s + + + 4 + TEXT|0s + 1 + + + id + 1 + + + noteId + notes + noteId + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + TEXT|0s + 1 + + + 5 + BLOB|0s + + + 6 + INT|0s + 1 + 0 + + + 7 + TEXT|0s + 1 + + + 8 + TEXT|0s + 1 + + + 1 + imageId + + 1 + + + imageId + 1 + sqlite_autoindex_images_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + INT|0s + 1 + 0 + + + 5 + TEXT|0s + 1 + + + 6 + TEXT|0s + 1 + + + 1 + noteImageId + + 1 + + + noteId +imageId + + + + + noteId + + + + imageId + + + + noteImageId + 1 + sqlite_autoindex_note_images_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + + + 4 + TEXT|0s + + + 5 + INT|0s + 1 + 0 + + + 6 + TEXT|0s + 1 + + + 7 + TEXT|0s + 1 + + + 1 + noteRevisionId + + 1 + + + noteId + + + + dateModifiedFrom + + + + dateModifiedTo + + + + noteRevisionId + 1 + sqlite_autoindex_note_revisions_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + INTEGER|0s + 1 + + + 5 + TEXT|0s + + + 6 + BOOLEAN|0s + + + 7 + INTEGER|0s + 1 + 0 + + + 8 + TEXT|0s + 1 + + + 1 + noteTreeId + + 1 + + + noteId +parentNoteId + + + + + noteId + + + + noteTreeId + 1 + sqlite_autoindex_note_tree_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + + + 3 + TEXT|0s + + + 4 + INT|0s + 1 + 0 + + + 5 + INT|0s + 1 + 0 + + + 6 + TEXT|0s + 1 + + + 7 + TEXT|0s + 1 + + + 8 + TEXT|0s + 1 + 'text' + + + 9 + TEXT|0s + 1 + 'text/html' + + + 1 + noteId + + 1 + + + isDeleted + + + + noteId + 1 + sqlite_autoindex_notes_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + + + 3 + INT|0s + + + 4 + INTEGER|0s + 1 + 0 + + + 1 + name + + 1 + + + name + 1 + sqlite_autoindex_options_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + INT|0s + + + 1 + noteTreeId + + 1 + + + noteTreeId + 1 + sqlite_autoindex_recent_notes_1 + + + 1 + TEXT|0s + 1 + + + 2 + TEXT|0s + 1 + + + 1 + sourceId + + 1 + + + sourceId + 1 + sqlite_autoindex_source_ids_1 + + + 1 + text|0s + + + 2 + text|0s + + + 3 + text|0s + + + 4 + integer|0s + + + 5 + text|0s + + + 1 + + + 2 + + + 1 + INTEGER|0s + 1 + 1 + + + 2 + TEXT|0s + 1 + + + 3 + TEXT|0s + 1 + + + 4 + TEXT|0s + 1 + + + 5 + TEXT|0s + 1 + + + entityName +entityId + + + 1 + + + syncDate + + + + id + 1 + + + \ No newline at end of file diff --git a/.idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997/storage_v2/_src_/schema/main.uQUzAA.meta b/.idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997/storage_v2/_src_/schema/main.uQUzAA.meta new file mode 100644 index 000000000..8dab49c6b --- /dev/null +++ b/.idea/dataSources/a2c75661-f9e2-478f-a69f-6a9409e69997/storage_v2/_src_/schema/main.uQUzAA.meta @@ -0,0 +1,2 @@ +#n:main +! [0, 0, null, null, -2147483648, -2147483648] diff --git a/.idea/jsLinters/jslint.xml b/.idea/jsLinters/jslint.xml new file mode 100644 index 000000000..742a5fe03 --- /dev/null +++ b/.idea/jsLinters/jslint.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 639900d13..7e5bdf89f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + diff --git a/src/public/javascripts/note_tree.js b/src/public/javascripts/note_tree.js index cc988f40d..f247d5409 100644 --- a/src/public/javascripts/note_tree.js +++ b/src/public/javascripts/note_tree.js @@ -1,6 +1,118 @@ "use strict"; +class TreeCache { + constructor(noteRows, noteTreeRows) { + this.parents = []; + this.children = []; + this.childParentToNoteTree = {}; + + this.notes = {}; + for (const noteRow of noteRows) { + const note = new NoteShort(this, noteRow); + + this.notes[note.noteId] = note; + } + + this.noteTree = {}; + for (const noteTreeRow of noteTreeRows) { + const nt = new NoteTree(this, noteTreeRow); + + this.noteTree[nt.noteTreeId] = nt; + this.addNoteTree(nt); + } + } + + getNote(noteId) { + return this.notes[noteId]; + } + + addNoteTree(nt) { + this.parents[nt.noteId] = this.parents[nt.noteId] || []; + this.parents[nt.noteId].push(this.notes[nt.parentNoteId]); + + this.children[nt.parentNoteId] = this.children[nt.parentNoteId] || []; + this.children[nt.parentNoteId].push(this.notes[nt.noteId]); + + this.childParentToNoteTree[nt.noteId + '-' + nt.parentNoteId] = nt; + } + + add(note, nt) { + this.notes[note.noteId] = note; + } + + async getNoteTree(childNoteId, parentNoteId) { + return this.childParentToNoteTree[childNoteId + '-' + parentNoteId]; + } +} + +class NoteShort { + constructor(treeCache, row) { + this.treeCache = treeCache; + this.noteId = row.noteId; + this.title = row.title; + this.isProtected = row.isProtected; + this.type = row.type; + this.mime = row.mime; + this.hideInAutocomplete = row.hideInAutocomplete; + } + + async getNoteTrees() { + const noteTrees = []; + + for (const parent of this.treeCache.parents[this.noteId]) { + noteTrees.push(await this.treeCache.getNoteTree(this.noteId, p.noteId)); + } + + return noteTrees; + } + + async getChildNoteTrees() { + const noteTrees = []; + + for (const child of this.treeCache.children[this.noteId]) { + noteTrees.push(await this.treeCache.getNoteTree(child.noteId, this.noteId)); + } + + return noteTrees; + } + + async getParentNotes() { + return this.treeCache.parents[this.noteId] || []; + } + + async getChildNotes() { + return this.treeCache.children[this.noteId] || []; + } + + get toString() { + return `Note(noteId=${this.noteId}, title=${this.title})`; + } +} + +class NoteTree { + constructor(treeCache, row) { + this.treeCache = treeCache; + this.noteTreeId = row.noteTreeId; + this.noteId = row.noteId; + this.note = null; + this.parentNoteId = row.parentNoteId; + this.notePosition = row.notePosition; + this.prefix = row.prefix; + this.isExpanded = row.isExpanded; + } + + async getNote() { + return this.treeCache.getNote(this.noteId); + } + + get toString() { + return `NoteTree(noteTreeId=${this.noteTreeId})`; + } +} + const noteTree = (function() { + let treeCache; + const $tree = $("#tree"); const $parentList = $("#parent-list"); const $parentListList = $("#parent-list-inner"); @@ -11,18 +123,14 @@ const noteTree = (function() { let instanceName = null; // should have better place let startNotePath = null; - let notesTreeMap = {}; - let parentToChildren = {}; - let childToParents = {}; - - let parentChildToNoteTreeId = {}; - let noteIdToNote = {}; - - let hiddenInAutocomplete = {}; + /** @type {Object.} */ + let noteMap = {}; + /** @type {Object.} */ + let noteTreeMap = {}; function getNote(noteId) { - const note = noteIdToNote[noteId]; + const note = noteMap[noteId]; if (!note) { throwError("Can't find title for noteId='" + noteId + "'"); @@ -34,6 +142,8 @@ const noteTree = (function() { function getNoteTreeId(parentNoteId, childNoteId) { assertArguments(parentNoteId, childNoteId); + + const key = parentNoteId + "-" + childNoteId; // this can return undefined and client code should deal with it somehow @@ -44,13 +154,13 @@ const noteTree = (function() { function getNoteTitle(noteId, parentNoteId = null) { assertArguments(noteId); - let title = getNote(noteId).title; + let title = treeCache.getNote(noteId).title; if (parentNoteId !== null) { const noteTreeId = getNoteTreeId(parentNoteId, noteId); if (noteTreeId) { - const noteTree = notesTreeMap[noteTreeId]; + const noteTree = noteTreeMap[noteTreeId]; if (noteTree.prefix) { title = noteTree.prefix + ' - ' + title; @@ -75,7 +185,7 @@ const noteTree = (function() { function getNodesByNoteTreeId(noteTreeId) { assertArguments(noteTreeId); - const noteTree = notesTreeMap[noteTreeId]; + const noteTree = noteTreeMap[noteTreeId]; return getNodesByNoteId(noteTree.noteId).filter(node => node.data.noteTreeId === noteTreeId); } @@ -90,14 +200,14 @@ const noteTree = (function() { function setPrefix(noteTreeId, prefix) { assertArguments(noteTreeId); - notesTreeMap[noteTreeId].prefix = prefix; + noteTreeMap[noteTreeId].prefix = prefix; getNodesByNoteTreeId(noteTreeId).map(node => setNodeTitleWithPrefix(node)); } function setNodeTitleWithPrefix(node) { const noteTitle = getNoteTitle(node.data.noteId); - const noteTree = notesTreeMap[node.data.noteTreeId]; + const noteTree = noteTreeMap[node.data.noteTreeId]; const prefix = noteTree.prefix; @@ -109,61 +219,38 @@ const noteTree = (function() { function removeParentChildRelation(parentNoteId, childNoteId) { assertArguments(parentNoteId, childNoteId); - const key = parentNoteId + "-" + childNoteId; + const parentNote = noteMap[parentNoteId]; + const childNote = noteMap[childNoteId]; - delete parentChildToNoteTreeId[key]; - - parentToChildren[parentNoteId] = parentToChildren[parentNoteId].filter(noteId => noteId !== childNoteId); - childToParents[childNoteId] = childToParents[childNoteId].filter(noteId => noteId !== parentNoteId); + parentNote.children = parentNote.children.filter(note => note.noteId !== childNoteId); + childNote.parents = childNote.parents.filter(note => note.noteId !== parentNoteId); + childNote.noteTree = childNote.noteTree.filter(nt => nt.parentNoteId !== parentNoteId); } function setParentChildRelation(noteTreeId, parentNoteId, childNoteId) { assertArguments(noteTreeId, parentNoteId, childNoteId); - const key = parentNoteId + "-" + childNoteId; + const parentNote = noteMap[parentNoteId]; + const childNote = noteMap[childNoteId]; - parentChildToNoteTreeId[key] = noteTreeId; + // FIXME: assert - if (!parentToChildren[parentNoteId]) { - parentToChildren[parentNoteId] = []; - } + childNote.parents.push(parentNote); + parentNote.children.push(childNote); - parentToChildren[parentNoteId].push(childNoteId); - - if (!childToParents[childNoteId]) { - childToParents[childNoteId] = []; - } - - childToParents[childNoteId].push(parentNoteId); + const noteTree = noteTreeMap[noteTreeId]; + childNote.noteTree.push(noteTree); } - function prepareNoteTree(notes) { - assertArguments(notes); + async function prepareNoteTree(noteRows, noteTreeRows) { + assertArguments(noteRows); - parentToChildren = {}; - childToParents = {}; - notesTreeMap = {}; + treeCache = new TreeCache(noteRows, noteTreeRows); - for (const note of notes) { - notesTreeMap[note.noteTreeId] = note; - - noteIdToNote[note.noteId] = { - noteId: note.noteId, - title: note.title, - isProtected: note.isProtected, - type: note.type, - mime: note.mime - }; - - delete note.title; // this should not be used. Use noteIdToNote instead - - setParentChildRelation(note.noteTreeId, note.parentNoteId, note.noteId); - } - - return prepareNoteTreeInner('root'); + return await prepareNoteTreeInner(treeCache.getNote('root')); } - function getExtraClasses(note) { + async function getExtraClasses(note) { assertArguments(note); const extraClasses = []; @@ -172,7 +259,7 @@ const noteTree = (function() { extraClasses.push("protected"); } - if (childToParents[note.noteId].length > 1) { + if ((await note.getParentNotes()).length > 1) { extraClasses.push("multiple-parents"); } @@ -181,42 +268,40 @@ const noteTree = (function() { return extraClasses.join(" "); } - function prepareNoteTreeInner(parentNoteId) { - assertArguments(parentNoteId); + async function prepareNoteTreeInner(parentNote) { + assertArguments(parentNote); - const childNoteIds = parentToChildren[parentNoteId]; - if (!childNoteIds) { - messaging.logError("No children for " + parentNoteId + ". This shouldn't happen."); + const childrenNoteTrees = await parentNote.getChildNoteTrees(); + + if (!childrenNoteTrees) { + messaging.logError(`No children for ${parentNote}. This shouldn't happen.`); return; } const noteList = []; - for (const noteId of childNoteIds) { - const note = getNote(noteId); - const noteTreeId = getNoteTreeId(parentNoteId, noteId); - const noteTree = notesTreeMap[noteTreeId]; - + for (const noteTree of childrenNoteTrees) { + const note = await noteTree.getNote(); const title = (noteTree.prefix ? (noteTree.prefix + " - ") : "") + note.title; const node = { - noteId: noteId, + noteId: note.noteId, parentNoteId: noteTree.parentNoteId, noteTreeId: noteTree.noteTreeId, isProtected: note.isProtected, title: escapeHtml(title), extraClasses: getExtraClasses(note), - refKey: noteId, + refKey: note.noteId, expanded: note.type !== 'search' && noteTree.isExpanded }; - const hasChildren = parentToChildren[noteId] && parentToChildren[noteId].length > 0; + const hasChildren = (await note.getChildNotes()).length > 0; if (hasChildren || note.type === 'search') { node.folder = true; if (node.expanded && note.type !== 'search') { - node.children = prepareNoteTreeInner(noteId); + node.children = await prepareNoteTreeInner(note); } else { node.lazy = true; @@ -609,7 +694,7 @@ const noteTree = (function() { init: (event, data) => { const noteId = treeUtils.getNoteIdFromNotePath(startNotePath); - if (noteIdToNote[noteId] === undefined) { + if (noteMap[noteId] === undefined) { // note doesn't exist so don't try to activate it startNotePath = null; } @@ -638,7 +723,7 @@ const noteTree = (function() { mode: "hide" // Grayout unmatched nodes (pass "hide" to remove unmatched node instead) }, dnd: dragAndDropSetup, - lazyLoad: function(event, data){ + lazyLoad: async function(event, data){ const noteId = data.node.data.noteId; const note = getNote(noteId); @@ -646,7 +731,7 @@ const noteTree = (function() { data.result = loadSearchNote(noteId); } else { - data.result = prepareNoteTreeInner(noteId); + data.result = await prepareNoteTreeInner(note); } }, clones: { @@ -667,7 +752,7 @@ const noteTree = (function() { for (const noteId of noteIds) { const noteTreeId = "virt" + randomString(10); - notesTreeMap[noteTreeId] = { + noteTreeMap[noteTreeId] = { noteTreeId: noteTreeId, noteId: noteId, parentNoteId: searchNoteId, @@ -678,7 +763,7 @@ const noteTree = (function() { setParentChildRelation(noteTreeId, searchNoteId, noteId); } - return prepareNoteTreeInner(searchNoteId); + return await prepareNoteTreeInner(searchNoteId); } function getTree() { @@ -705,13 +790,7 @@ const noteTree = (function() { startNotePath = getNotePathFromAddress(); } - hiddenInAutocomplete = {}; - - for (const noteId of resp.hiddenInAutocomplete) { - hiddenInAutocomplete[noteId] = true; - } - - return prepareNoteTree(resp.notes); + return await prepareNoteTree(resp.notes, resp.noteTree); } $(() => loadTree().then(noteTree => initFancyTree(noteTree))); @@ -827,9 +906,9 @@ const noteTree = (function() { setParentChildRelation(result.noteTreeId, parentNoteId, result.noteId); - notesTreeMap[result.noteTreeId] = result; + noteTreeMap[result.noteTreeId] = result; - noteIdToNote[result.noteId] = { + noteMap[result.noteId] = { noteId: result.noteId, title: result.title, isProtected: result.isProtected, @@ -889,11 +968,7 @@ const noteTree = (function() { } function getNoteTree(noteTreeId) { - return notesTreeMap[noteTreeId]; - } - - function getNote(noteId) { - return noteIdToNote[noteId]; + return noteTreeMap[noteTreeId]; } $(document).bind('keydown', 'ctrl+o', e => { diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index f8ea3b208..3d6289ae2 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -12,39 +12,56 @@ const sync_table = require('../../services/sync_table'); const wrap = require('express-promise-wrap').wrap; router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => { - const notes = await sql.getRows(` + const noteTree = await sql.getRows(` SELECT - note_tree.*, - notes.title, - notes.isProtected, - notes.type - FROM + noteTreeId, + noteId, + parentNoteId, + notePosition, + prefix, + isExpanded + FROM note_tree - JOIN - notes ON notes.noteId = note_tree.noteId WHERE - notes.isDeleted = 0 - AND note_tree.isDeleted = 0 + isDeleted = 0 ORDER BY notePosition`); + let notes = [{ + noteId: 'root', + title: 'root', + isProtected: false, + type: 'none', + mime: 'none' + }]; + + notes = notes.concat(await sql.getRows(` + SELECT + notes.noteId, + notes.title, + notes.isProtected, + notes.type, + notes.mime, + hideInAutocomplete.attributeId AS 'hideInAutocomplete' + FROM + notes + LEFT JOIN attributes AS hideInAutocomplete ON hideInAutocomplete.noteId = notes.noteId + AND hideInAutocomplete.name = 'hide_in_autocomplete' + AND hideInAutocomplete.isDeleted = 0 + WHERE + notes.isDeleted = 0`)); + protected_session.decryptNotes(req, notes); - const hiddenInAutocomplete = await sql.getColumn(` - SELECT - DISTINCT noteId - FROM - attributes - JOIN notes USING(noteId) - WHERE - attributes.name = 'hide_in_autocomplete' - AND attributes.isDeleted = 0 - AND notes.isDeleted = 0`); + notes.forEach(note => { + note.hideInAutocomplete = !!note.hideInAutocomplete; + note.isProtected = !!note.isProtected; + }); res.send({ instanceName: config.General ? config.General.instanceName : null, + noteTree: noteTree, notes: notes, - hiddenInAutocomplete: hiddenInAutocomplete, start_note_path: await options.getOption('start_note_path') }); }));