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
+ 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')
});
}));