From 52b8162d01d84a861c0b137c768c49f22810c603 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 23 Nov 2020 22:52:48 +0100 Subject: [PATCH] WIP per-tab hoisting --- db/migrations/0171__cleanup_options.sql | 15 ++++++++++++++ src/app.js | 1 - src/public/app/services/entrypoints.js | 6 +++++- .../app/services/frontend_script_api.js | 10 ++++++++-- src/public/app/services/hoisted_note.js | 20 +++++++------------ src/public/app/services/server.js | 13 ++++++++---- src/public/app/services/tab_manager.js | 4 ++-- src/public/app/widgets/note_tree.js | 19 +++--------------- src/routes/routes.js | 3 ++- src/services/app_info.js | 2 +- src/services/cls.js | 5 +++++ src/services/hoisted_note.js | 6 ------ src/services/hoisted_note_loader.js | 14 ------------- src/services/note_cache/note_cache_service.js | 10 +++------- src/services/notes.js | 4 +--- src/services/search/services/search.js | 6 +++--- 16 files changed, 64 insertions(+), 74 deletions(-) create mode 100644 db/migrations/0171__cleanup_options.sql delete mode 100644 src/services/hoisted_note.js delete mode 100644 src/services/hoisted_note_loader.js diff --git a/db/migrations/0171__cleanup_options.sql b/db/migrations/0171__cleanup_options.sql new file mode 100644 index 000000000..9acc7b5ed --- /dev/null +++ b/db/migrations/0171__cleanup_options.sql @@ -0,0 +1,15 @@ +DELETE FROM options WHERE name IN ( + 'noteInfoWidget', + 'attributesWidget', + 'linkMapWidget', + 'noteRevisionsWidget', + 'whatLinksHereWidget', + 'codeNotesMimeTypes', + 'similarNotesWidget', + 'editedNotesWidget', + 'calendarWidget', + 'sidebarMinWidth', + 'sidebarWidthPercent', + 'showSidebarInNewTab', + 'hoistedNoteId' +); diff --git a/src/app.js b/src/app.js index c7cd83ad3..3f2fdca71 100644 --- a/src/app.js +++ b/src/app.js @@ -10,7 +10,6 @@ const FileStore = require('session-file-store')(session); const sessionSecret = require('./services/session_secret'); const dataDir = require('./services/data_dir'); require('./services/handlers'); -require('./services/hoisted_note_loader'); require('./services/note_cache/note_cache_loader'); const app = express(); diff --git a/src/public/app/services/entrypoints.js b/src/public/app/services/entrypoints.js index c89f3efc3..0202c37ee 100644 --- a/src/public/app/services/entrypoints.js +++ b/src/public/app/services/entrypoints.js @@ -91,7 +91,11 @@ export default class Entrypoints extends Component { } async unhoistCommand() { - hoistedNoteService.unhoist(); + const activeTabContext = appContext.tabManager.getActiveTabContext(); + + if (activeTabContext) { + activeTabContext.unhoist(); + } } copyWithoutFormattingCommand() { diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index f589bef1a..1efa0ac22 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -379,13 +379,19 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain this.getYearNote = dateNotesService.getYearNote; /** - * Hoist note. See https://github.com/zadam/trilium/wiki/Note-hoisting + * Hoist note in the current tab. See https://github.com/zadam/trilium/wiki/Note-hoisting * * @method * @param {string} noteId - set hoisted note. 'root' will effectively unhoist * @return {Promise} */ - this.setHoistedNoteId = hoistedNoteService.setHoistedNoteId; + this.setHoistedNoteId = (noteId) => { + const activeTabContext = appContext.tabManager.getActiveTabContext(); + + if (activeTabContext) { + activeTabContext.setHoistedNoteId(noteId); + } + }; /** * @method diff --git a/src/public/app/services/hoisted_note.js b/src/public/app/services/hoisted_note.js index 089a6868c..5c668b2fb 100644 --- a/src/public/app/services/hoisted_note.js +++ b/src/public/app/services/hoisted_note.js @@ -1,23 +1,18 @@ -import options from './options.js'; import appContext from "./app_context.js"; import treeService from "./tree.js"; function getHoistedNoteId() { - return options.get('hoistedNoteId'); -} + const activeTabContext = appContext.tabManager.getActiveTabContext(); -async function setHoistedNoteId(noteId) { - if (getHoistedNoteId() === noteId) { - return; - } - - await options.save('hoistedNoteId', noteId); - - appContext.triggerEvent('hoistedNoteChanged', {noteId}); + return activeTabContext ? activeTabContext.hoistedNoteId : 'root'; } async function unhoist() { - await setHoistedNoteId('root'); + const activeTabContext = appContext.tabManager.getActiveTabContext(); + + if (activeTabContext) { + await activeTabContext.unhoist(); + } } function isTopLevelNode(node) { @@ -58,7 +53,6 @@ async function checkNoteAccess(notePath) { export default { getHoistedNoteId, - setHoistedNoteId, unhoist, isTopLevelNode, isRootNode, diff --git a/src/public/app/services/server.js b/src/public/app/services/server.js index 309b2b4fb..59ffe8aee 100644 --- a/src/public/app/services/server.js +++ b/src/public/app/services/server.js @@ -2,13 +2,16 @@ import utils from './utils.js'; const REQUEST_LOGGING_ENABLED = false; -function getHeaders(headers) { +async function getHeaders(headers) { + const appContext = (await import('./app_context.js')).default; + const activeTabContext = appContext.tabManager ? appContext.tabManager.getActiveTabContext() : null; + // headers need to be lowercase because node.js automatically converts them to lower case - // so hypothetical protectedSessionId becomes protectedsessionid on the backend // also avoiding using underscores instead of dashes since nginx filters them out by default const allHeaders = { 'trilium-source-id': glob.sourceId, 'trilium-local-now-datetime': utils.localNowDateTime(), + 'trilium-hoisted-note-id': activeTabContext ? activeTabContext.hoistedNoteId : null, 'x-csrf-token': glob.csrfToken }; @@ -52,6 +55,8 @@ async function call(method, url, data, headers = {}) { const start = Date.now(); + headers = await getHeaders(headers); + if (utils.isElectron()) { const ipc = utils.dynamicRequire('electron').ipcRenderer; const requestId = i++; @@ -65,7 +70,7 @@ async function call(method, url, data, headers = {}) { ipc.send('server-request', { requestId: requestId, - headers: getHeaders(headers), + headers: headers, method: method, url: "/" + baseApiUrl + url, data: data @@ -96,7 +101,7 @@ function ajax(url, method, data, headers) { const options = { url: baseApiUrl + url, type: method, - headers: getHeaders(headers), + headers: headers, timeout: 60000, success: (body, textStatus, jqXhr) => { const respHeaders = {}; diff --git a/src/public/app/services/tab_manager.js b/src/public/app/services/tab_manager.js index b874de29e..c4159e82d 100644 --- a/src/public/app/services/tab_manager.js +++ b/src/public/app/services/tab_manager.js @@ -193,8 +193,8 @@ export default class TabManager extends Component { return tabContext; } - async openTabWithNote(notePath, activate, tabId = null, hoistedNoteId = 'root') { - const tabContext = await this.openEmptyTab(tabId); + async openTabWithNote(notePath, activate, tabId, hoistedNoteId) { + const tabContext = await this.openEmptyTab(tabId, hoistedNoteId); if (notePath) { await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index 7e3952c4c..a37df0ebe 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -500,8 +500,6 @@ export default class NoteTreeWidget extends TabAwareWidget { } prepareRootNode() { - const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); - return this.prepareNode(treeCache.getBranch('root')); } @@ -532,16 +530,6 @@ export default class NoteTreeWidget extends TabAwareWidget { return noteList; } - getIcon(note, isFolder) { - const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); - - if (note.noteId !== 'root' && note.noteId === hoistedNoteId) { - return "bx bxs-arrow-from-bottom"; - } - - return note.getIcon(isFolder); - } - updateNode(node) { const note = treeCache.getNoteFromCache(node.data.noteId); const branch = treeCache.getBranch(node.data.branchId); @@ -552,7 +540,7 @@ export default class NoteTreeWidget extends TabAwareWidget { node.data.isProtected = note.isProtected; node.data.noteType = note.type; node.folder = isFolder; - node.icon = this.getIcon(note, isFolder); + node.icon = note.getIcon(isFolder); node.extraClasses = this.getExtraClasses(note); node.title = utils.escapeHtml(title); @@ -574,7 +562,6 @@ export default class NoteTreeWidget extends TabAwareWidget { } const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; - const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); const isFolder = this.isFolder(note); @@ -586,11 +573,11 @@ export default class NoteTreeWidget extends TabAwareWidget { noteType: note.type, title: utils.escapeHtml(title), extraClasses: this.getExtraClasses(note), - icon: this.getIcon(note, isFolder), + icon: note.getIcon(isFolder), refKey: note.noteId, lazy: true, folder: isFolder, - expanded: (branch.isExpanded || hoistedNoteId === note.noteId) && note.type !== 'search', + expanded: branch.isExpanded && note.type !== 'search', key: utils.randomString(12) // this should prevent some "duplicate key" errors }; diff --git a/src/routes/routes.js b/src/routes/routes.js index cdfcaac35..9caf54027 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -88,7 +88,8 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio const result = cls.init(() => { cls.set('sourceId', req.headers['trilium-source-id']); - cls.set('localNowDateTime', req.headers['`trilium-local-now-datetime`']); + cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); + cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root'); protectedSessionService.setProtectedSessionId(req); const cb = () => routeHandler(req, res, next); diff --git a/src/services/app_info.js b/src/services/app_info.js index 02d3644d3..fc8fa27ba 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -4,7 +4,7 @@ const build = require('./build'); const packageJson = require('../../package'); const {TRILIUM_DATA_DIR} = require('./data_dir'); -const APP_DB_VERSION = 170; +const APP_DB_VERSION = 171; const SYNC_VERSION = 16; const CLIPPER_PROTOCOL_VERSION = "1.0"; diff --git a/src/services/cls.js b/src/services/cls.js index c70212606..d3e76834f 100644 --- a/src/services/cls.js +++ b/src/services/cls.js @@ -24,6 +24,10 @@ function set(key, value) { namespace.set(key, value); } +function getHoistedNoteId() { + return namespace.get('hoistedNoteId'); +} + function getSourceId() { return namespace.get('sourceId'); } @@ -74,6 +78,7 @@ module.exports = { get, set, namespace, + getHoistedNoteId, getSourceId, getLocalNowDateTime, disableEntityEvents, diff --git a/src/services/hoisted_note.js b/src/services/hoisted_note.js deleted file mode 100644 index 2a45cd322..000000000 --- a/src/services/hoisted_note.js +++ /dev/null @@ -1,6 +0,0 @@ -let hoistedNoteId = 'root'; - -module.exports = { - getHoistedNoteId: () => hoistedNoteId, - setHoistedNoteId(noteId) { hoistedNoteId = noteId; } -}; diff --git a/src/services/hoisted_note_loader.js b/src/services/hoisted_note_loader.js deleted file mode 100644 index 474807c9b..000000000 --- a/src/services/hoisted_note_loader.js +++ /dev/null @@ -1,14 +0,0 @@ -const optionService = require('./options'); -const sqlInit = require('./sql_init'); -const eventService = require('./events'); -const hoistedNote = require('./hoisted_note'); - -eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => { - if (entityName === 'options' && entity.name === 'hoistedNoteId') { - hoistedNote.setHoistedNoteId(entity.value); - } -}); - -sqlInit.dbReady.then(() => { - hoistedNote.setHoistedNoteId(optionService.getOption('hoistedNoteId')); -}); diff --git a/src/services/note_cache/note_cache_service.js b/src/services/note_cache/note_cache_service.js index 2471afbca..811a6f593 100644 --- a/src/services/note_cache/note_cache_service.js +++ b/src/services/note_cache/note_cache_service.js @@ -1,7 +1,7 @@ "use strict"; const noteCache = require('./note_cache'); -const hoistedNoteService = require('../hoisted_note'); +const cls = require('../cls'); const protectedSessionService = require('../protected_session'); const log = require('../log'); @@ -88,10 +88,6 @@ function getNoteTitle(childNoteId, parentNoteId) { function getNoteTitleArrayForPath(notePathArray) { const titles = []; - if (notePathArray[0] === hoistedNoteService.getHoistedNoteId() && notePathArray.length === 1) { - return [ getNoteTitle(hoistedNoteService.getHoistedNoteId()) ]; - } - let parentNoteId = 'root'; let hoistedNotePassed = false; @@ -103,7 +99,7 @@ function getNoteTitleArrayForPath(notePathArray) { titles.push(title); } - if (noteId === hoistedNoteService.getHoistedNoteId()) { + if (noteId === cls.getHoistedNoteId()) { hoistedNotePassed = true; } @@ -130,7 +126,7 @@ function getSomePath(note, path = []) { path.push(note.noteId); path.reverse(); - if (!path.includes(hoistedNoteService.getHoistedNoteId())) { + if (!path.includes(cls.getHoistedNoteId())) { return false; } diff --git a/src/services/notes.js b/src/services/notes.js index 4aa944504..de6a10bf5 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -7,10 +7,8 @@ const eventService = require('./events'); const repository = require('./repository'); const cls = require('../services/cls'); const Note = require('../entities/note'); -const NoteRevision = require('../entities/note_revision'); const Branch = require('../entities/branch'); const Attribute = require('../entities/attribute'); -const hoistedNoteService = require('../services/hoisted_note'); const protectedSessionService = require('../services/protected_session'); const log = require('../services/log'); const utils = require('../services/utils'); @@ -524,7 +522,7 @@ function deleteBranch(branch, deleteId, taskContext) { if (branch.branchId === 'root' || branch.noteId === 'root' - || branch.noteId === hoistedNoteService.getHoistedNoteId()) { + || branch.noteId === cls.getHoistedNoteId()) { throw new Error("Can't delete root branch/note"); } diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js index eab2b32af..733455615 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.js @@ -8,15 +8,15 @@ const SearchResult = require("../search_result.js"); const SearchContext = require("../search_context.js"); const noteCache = require('../../note_cache/note_cache.js'); const noteCacheService = require('../../note_cache/note_cache_service.js'); -const hoistedNoteService = require('../../hoisted_note.js'); const utils = require('../../utils.js'); +const cls = require('../../cls.js'); /** * @param {Expression} expression * @return {SearchResult[]} */ function findNotesWithExpression(expression) { - const hoistedNote = noteCache.notes[hoistedNoteService.getHoistedNoteId()]; + const hoistedNote = noteCache.notes[cls.getHoistedNoteId()]; let allNotes = (hoistedNote && hoistedNote.noteId !== 'root') ? hoistedNote.subtreeNotes : Object.values(noteCache.notes); @@ -35,7 +35,7 @@ function findNotesWithExpression(expression) { const searchResults = noteSet.notes .map(note => searchContext.noteIdToNotePath[note.noteId] || noteCacheService.getSomePath(note)) - .filter(notePathArray => notePathArray.includes(hoistedNoteService.getHoistedNoteId())) + .filter(notePathArray => notePathArray.includes(cls.getHoistedNoteId())) .map(notePathArray => new SearchResult(notePathArray)); if (!noteSet.sorted) {