mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	basic lazy loading of tree now works, still WIP
This commit is contained in:
		| @@ -16,13 +16,18 @@ class NoteShort { | |||||||
|     async getBranches() { |     async getBranches() { | ||||||
|         const branches = []; |         const branches = []; | ||||||
|  |  | ||||||
|         for (const parent of this.treeCache.parents[this.noteId]) { |         for (const parentNoteId of this.treeCache.parents[this.noteId]) { | ||||||
|             branches.push(await this.treeCache.getBranchByChildParent(this.noteId, parent.noteId)); |             branches.push(await this.treeCache.getBranchByChildParent(this.noteId, parentNoteId)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return branches; |         return branches; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     hasChildren() { | ||||||
|  |         return this.treeCache.children[this.noteId] | ||||||
|  |             && this.treeCache.children[this.noteId].length > 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async getChildBranches() { |     async getChildBranches() { | ||||||
|         if (!this.treeCache.children[this.noteId]) { |         if (!this.treeCache.children[this.noteId]) { | ||||||
|             return []; |             return []; | ||||||
| @@ -30,19 +35,33 @@ class NoteShort { | |||||||
|  |  | ||||||
|         const branches = []; |         const branches = []; | ||||||
|  |  | ||||||
|         for (const child of this.treeCache.children[this.noteId]) { |         for (const childNoteId of this.treeCache.children[this.noteId]) { | ||||||
|             branches.push(await this.treeCache.getBranchByChildParent(child.noteId, this.noteId)); |             branches.push(await this.treeCache.getBranchByChildParent(childNoteId, this.noteId)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return branches; |         return branches; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async __getNotes(noteIds) { | ||||||
|  |         if (!noteIds) { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const notes = []; | ||||||
|  |  | ||||||
|  |         for (const noteId of noteIds) { | ||||||
|  |             notes.push(await this.treeCache.getNote(noteId)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return notes; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async getParentNotes() { |     async getParentNotes() { | ||||||
|         return this.treeCache.parents[this.noteId] || []; |         return this.__getNotes(this.treeCache.parents[this.noteId]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getChildNotes() { |     async getChildNotes() { | ||||||
|         return this.treeCache.children[this.noteId] || []; |         return this.__getNotes(this.treeCache.children[this.noteId]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get toString() { |     get toString() { | ||||||
|   | |||||||
| @@ -285,14 +285,14 @@ async function treeInitialized() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function initFancyTree(branch) { | function initFancyTree(tree) { | ||||||
|     utils.assertArguments(branch); |     utils.assertArguments(tree); | ||||||
|  |  | ||||||
|     $tree.fancytree({ |     $tree.fancytree({ | ||||||
|         autoScroll: true, |         autoScroll: true, | ||||||
|         keyboard: false, // we takover keyboard handling in the hotkeys plugin |         keyboard: false, // we takover keyboard handling in the hotkeys plugin | ||||||
|         extensions: ["hotkeys", "filter", "dnd", "clones"], |         extensions: ["hotkeys", "filter", "dnd", "clones"], | ||||||
|         source: branch, |         source: tree, | ||||||
|         scrollParent: $tree, |         scrollParent: $tree, | ||||||
|         click: (event, data) => { |         click: (event, data) => { | ||||||
|             const targetType = data.targetType; |             const targetType = data.targetType; | ||||||
| @@ -375,7 +375,7 @@ async function loadTree() { | |||||||
|         startNotePath = getNotePathFromAddress(); |         startNotePath = getNotePathFromAddress(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return await treeBuilder.prepareTree(resp.notes, resp.branches); |     return await treeBuilder.prepareTree(resp.notes, resp.branches, resp.parentToChildren); | ||||||
| } | } | ||||||
|  |  | ||||||
| function collapseTree(node = null) { | function collapseTree(node = null) { | ||||||
|   | |||||||
| @@ -5,10 +5,10 @@ import server from "./server.js"; | |||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
| import messagingService from "./messaging.js"; | import messagingService from "./messaging.js"; | ||||||
|  |  | ||||||
| async function prepareTree(noteRows, branchRows) { | async function prepareTree(noteRows, branchRows, parentToChildren) { | ||||||
|     utils.assertArguments(noteRows); |     utils.assertArguments(noteRows, branchRows, parentToChildren); | ||||||
|  |  | ||||||
|     treeCache.load(noteRows, branchRows); |     treeCache.load(noteRows, branchRows, parentToChildren); | ||||||
|  |  | ||||||
|     return await prepareRealBranch(await treeCache.getNote('root')); |     return await prepareRealBranch(await treeCache.getNote('root')); | ||||||
| } | } | ||||||
| @@ -49,9 +49,7 @@ async function prepareRealBranch(parentNote) { | |||||||
|             expanded: note.type !== 'search' && branch.isExpanded |             expanded: note.type !== 'search' && branch.isExpanded | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         const hasChildren = (await note.getChildNotes()).length > 0; |         if (note.hasChildren() || note.type === 'search') { | ||||||
|  |  | ||||||
|         if (hasChildren || note.type === 'search') { |  | ||||||
|             node.folder = true; |             node.folder = true; | ||||||
|  |  | ||||||
|             if (node.expanded && note.type !== 'search') { |             if (node.expanded && note.type !== 'search') { | ||||||
|   | |||||||
| @@ -2,45 +2,82 @@ import utils from "./utils.js"; | |||||||
| import Branch from "../entities/branch.js"; | import Branch from "../entities/branch.js"; | ||||||
| import NoteShort from "../entities/note_short.js"; | import NoteShort from "../entities/note_short.js"; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
|  | import server from "./server.js"; | ||||||
|  |  | ||||||
| class TreeCache { | class TreeCache { | ||||||
|     load(noteRows, branchRows) { |     load(noteRows, branchRows, parentToChildren) { | ||||||
|         this.parents = []; |         this.parents = {}; | ||||||
|         this.children = []; |         this.children = {}; | ||||||
|         this.childParentToBranch = {}; |         this.childParentToBranch = {}; | ||||||
|  |  | ||||||
|         /** @type {Object.<string, NoteShort>} */ |         /** @type {Object.<string, NoteShort>} */ | ||||||
|         this.notes = {}; |         this.notes = {}; | ||||||
|  |  | ||||||
|  |         /** @type {Object.<string, Branch>} */ | ||||||
|  |         this.branches = {}; | ||||||
|  |  | ||||||
|  |         this.addResp(noteRows, branchRows, parentToChildren); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     addResp(noteRows, branchRows, parentToChildren) { | ||||||
|         for (const noteRow of noteRows) { |         for (const noteRow of noteRows) { | ||||||
|             const note = new NoteShort(this, noteRow); |             const note = new NoteShort(this, noteRow); | ||||||
|  |  | ||||||
|             this.notes[note.noteId] = note; |             this.notes[note.noteId] = note; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /** @type {Object.<string, Branch>} */ |  | ||||||
|         this.branches = {}; |  | ||||||
|         for (const branchRow of branchRows) { |         for (const branchRow of branchRows) { | ||||||
|             const branch = new Branch(this, branchRow); |             const branch = new Branch(this, branchRow); | ||||||
|  |  | ||||||
|             this.addBranch(branch); |             this.addBranch(branch); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         for (const relation of parentToChildren) { | ||||||
|  |             this.addBranchRelationship(relation.branchId, relation.childNoteId, relation.parentNoteId); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @return NoteShort */ |     /** @return NoteShort */ | ||||||
|     async getNote(noteId) { |     async getNote(noteId) { | ||||||
|  |         if (this.notes[noteId] === undefined) { | ||||||
|  |             const resp = await server.post('tree/load', { | ||||||
|  |                 noteIds: [noteId] | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             this.addResp(resp.notes, resp.branches, resp.parentToChildren); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!this.notes[noteId]) { | ||||||
|  |             throw new Error(`Can't find note ${noteId}`); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return this.notes[noteId]; |         return this.notes[noteId]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     addBranch(branch) { |     addBranch(branch) { | ||||||
|         this.branches[branch.branchId] = branch; |         this.branches[branch.branchId] = branch; | ||||||
|  |  | ||||||
|         this.parents[branch.noteId] = this.parents[branch.noteId] || []; |         this.addBranchRelationship(branch.branchId, branch.noteId, branch.parentNoteId); | ||||||
|         this.parents[branch.noteId].push(this.notes[branch.parentNoteId]); |     } | ||||||
|  |  | ||||||
|         this.children[branch.parentNoteId] = this.children[branch.parentNoteId] || []; |     addBranchRelationship(branchId, childNoteId, parentNoteId) { | ||||||
|         this.children[branch.parentNoteId].push(this.notes[branch.noteId]); |         this.addParentChildRelationship(parentNoteId, childNoteId); | ||||||
|  |  | ||||||
|         this.childParentToBranch[branch.noteId + '-' + branch.parentNoteId] = branch; |         this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     addParentChildRelationship(parentNoteId, childNoteId) { | ||||||
|  |         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) { |     add(note, branch) { | ||||||
| @@ -51,19 +88,34 @@ class TreeCache { | |||||||
|  |  | ||||||
|     /** @return Branch */ |     /** @return Branch */ | ||||||
|     async getBranch(branchId) { |     async getBranch(branchId) { | ||||||
|  |         if (this.branches[branchId] === undefined) { | ||||||
|  |             const resp = await server.post('tree/load', { | ||||||
|  |                 branchIds: [branchId] | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             this.addResp(resp.notes, resp.branches, resp.parentToChildren); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!this.branches[branchId]) { | ||||||
|  |             throw new Error(`Can't find branch ${branchId}`); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return this.branches[branchId]; |         return this.branches[branchId]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @return Branch */ |     /** @return Branch */ | ||||||
|     async getBranchByChildParent(childNoteId, parentNoteId) { |     async getBranchByChildParent(childNoteId, parentNoteId) { | ||||||
|         const key = (childNoteId + '-' + parentNoteId); |         // this will make sure the note and its relationships are loaded | ||||||
|         const branch = this.childParentToBranch[key]; |         await this.getNote(parentNoteId); | ||||||
|  |  | ||||||
|         if (!branch) { |         const key = (childNoteId + '-' + parentNoteId); | ||||||
|  |         const branchId = this.childParentToBranch[key]; | ||||||
|  |  | ||||||
|  |         if (!branchId) { | ||||||
|             infoService.throwError("Cannot find branch for child-parent=" + key); |             infoService.throwError("Cannot find branch for child-parent=" + key); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return branch; |         return await this.getBranch(branchId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* Move note from one parent to another. */ |     /* Move note from one parent to another. */ | ||||||
| @@ -78,33 +130,14 @@ class TreeCache { | |||||||
|         delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId |         delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId | ||||||
|  |  | ||||||
|         // remove old associations |         // remove old associations | ||||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId].filter(p => p.noteId !== oldParentNoteId); |         treeCache.parents[childNoteId] = treeCache.parents[childNoteId].filter(p => p !== oldParentNoteId); | ||||||
|         treeCache.children[oldParentNoteId] = treeCache.children[oldParentNoteId].filter(ch => ch.noteId !== childNoteId); |         treeCache.children[oldParentNoteId] = treeCache.children[oldParentNoteId].filter(ch => ch !== childNoteId); | ||||||
|  |  | ||||||
|         // add new associations |         // add new associations | ||||||
|         treeCache.parents[childNoteId].push(await treeCache.getNote(newParentNoteId)); |         treeCache.parents[childNoteId].push(newParentNoteId); | ||||||
|  |  | ||||||
|         treeCache.children[newParentNoteId] = treeCache.children[newParentNoteId] || []; // this might be first child |         treeCache.children[newParentNoteId] = treeCache.children[newParentNoteId] || []; // this might be first child | ||||||
|         treeCache.children[newParentNoteId].push(await treeCache.getNote(childNoteId)); |         treeCache.children[newParentNoteId].push(childNoteId); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     removeParentChildRelation(parentNoteId, childNoteId) { |  | ||||||
|         utils.assertArguments(parentNoteId, childNoteId); |  | ||||||
|  |  | ||||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId].filter(p => p.noteId !== parentNoteId); |  | ||||||
|         treeCache.children[parentNoteId] = treeCache.children[parentNoteId].filter(ch => ch.noteId !== childNoteId); |  | ||||||
|  |  | ||||||
|         delete treeCache.childParentToBranch[childNoteId + '-' + parentNoteId]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async setParentChildRelation(branchId, parentNoteId, childNoteId) { |  | ||||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId] || []; |  | ||||||
|         treeCache.parents[childNoteId].push(await treeCache.getNote(parentNoteId)); |  | ||||||
|  |  | ||||||
|         treeCache.children[parentNoteId] = treeCache.children[parentNoteId] || []; |  | ||||||
|         treeCache.children[parentNoteId].push(await treeCache.getNote(childNoteId)); |  | ||||||
|  |  | ||||||
|         treeCache.childParentToBranch[childNoteId + '-' + parentNoteId] = await treeCache.getBranch(branchId); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,26 @@ | |||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const optionService = require('../../services/options'); | const optionService = require('../../services/options'); | ||||||
| const protectedSessionService = require('../../services/protected_session'); | const protectedSessionService = require('../../services/protected_session'); | ||||||
| const utils = require('../../services/utils'); |  | ||||||
|  | async function getNotes(noteIds) { | ||||||
|  |     const questionMarks = noteIds.map(() => "?").join(","); | ||||||
|  |  | ||||||
|  |     const notes = await sql.getRows(` | ||||||
|  |       SELECT noteId, title, isProtected, type, mime | ||||||
|  |       FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); | ||||||
|  |  | ||||||
|  |     protectedSessionService.decryptNotes(notes); | ||||||
|  |  | ||||||
|  |     notes.forEach(note => note.isProtected = !!note.isProtected); | ||||||
|  |     return notes; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getParentToChildren(noteIds) { | ||||||
|  |     const questionMarks = noteIds.map(() => "?").join(","); | ||||||
|  |  | ||||||
|  |     return await sql.getRows(`SELECT branchId, noteId AS 'childNoteId', parentNoteId FROM branches WHERE isDeleted = 0  | ||||||
|  |          AND parentNoteId IN (${questionMarks})`, noteIds); | ||||||
|  | } | ||||||
|  |  | ||||||
| async function getTree() { | async function getTree() { | ||||||
|     const branches = await sql.getRows(` |     const branches = await sql.getRows(` | ||||||
| @@ -18,34 +37,43 @@ async function getTree() { | |||||||
|         SELECT branches.* FROM tree JOIN branches USING(branchId);`); |         SELECT branches.* FROM tree JOIN branches USING(branchId);`); | ||||||
|  |  | ||||||
|     const noteIds = branches.map(b => b.noteId); |     const noteIds = branches.map(b => b.noteId); | ||||||
|     const questionMarks = branches.map(() => "?").join(","); |  | ||||||
|  |  | ||||||
|     const notes = await sql.getRows(` |     const notes = await getNotes(noteIds); | ||||||
|       SELECT noteId, title, isProtected, type, mime |  | ||||||
|       FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); |  | ||||||
|  |  | ||||||
|     protectedSessionService.decryptNotes(notes); |     const parentToChildren = await getParentToChildren(noteIds); | ||||||
|  |  | ||||||
|     notes.forEach(note => note.isProtected = !!note.isProtected); |  | ||||||
|  |  | ||||||
|     const relationships = await sql.getRows(`SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0  |  | ||||||
|          AND parentNoteId IN (${questionMarks})`, noteIds); |  | ||||||
|  |  | ||||||
|     const parentToChild = {}; |  | ||||||
|  |  | ||||||
|     for (const rel of relationships) { |  | ||||||
|         parentToChild[rel.parentNoteId] = parentToChild[rel.parentNoteId] || []; |  | ||||||
|         parentToChild[rel.parentNoteId].push(rel.noteId); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         startNotePath: await optionService.getOption('startNotePath'), |         startNotePath: await optionService.getOption('startNotePath'), | ||||||
|         branches: branches, |         branches, | ||||||
|         notes: notes, |         notes, | ||||||
|         parentToChild |         parentToChildren | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function load(req) { | ||||||
|  |     let noteIds = req.body.noteIds; | ||||||
|  |     const branchIds = req.body.branchIds; | ||||||
|  |  | ||||||
|  |     if (branchIds && branchIds.length > 0) { | ||||||
|  |         noteIds = await sql.getColumn(`SELECT noteId FROM branches WHERE isDeleted = 0 AND branchId IN(${branchIds.map(() => "?").join(",")})`, branchIds); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const questionMarks = noteIds.map(() => "?").join(","); | ||||||
|  |  | ||||||
|  |     const branches = await sql.getRows(`SELECT * FROM branches WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); | ||||||
|  |  | ||||||
|  |     const notes = await getNotes(noteIds); | ||||||
|  |  | ||||||
|  |     const parentToChildren = await getParentToChildren(noteIds); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         branches, | ||||||
|  |         notes, | ||||||
|  |         parentToChildren | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getTree |     getTree, | ||||||
|  |     load | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -99,6 +99,7 @@ function register(app) { | |||||||
|     route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); |     route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); | ||||||
|  |  | ||||||
|     apiRoute(GET, '/api/tree', treeApiRoute.getTree); |     apiRoute(GET, '/api/tree', treeApiRoute.getTree); | ||||||
|  |     apiRoute(POST, '/api/tree/load', treeApiRoute.load); | ||||||
|     apiRoute(PUT, '/api/branches/:branchId/set-prefix', branchesApiRoute.setPrefix); |     apiRoute(PUT, '/api/branches/:branchId/set-prefix', branchesApiRoute.setPrefix); | ||||||
|  |  | ||||||
|     apiRoute(PUT, '/api/branches/:branchId/move-to/:parentNoteId', branchesApiRoute.moveBranchToParent); |     apiRoute(PUT, '/api/branches/:branchId/move-to/:parentNoteId', branchesApiRoute.moveBranchToParent); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user