mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	WIP per-tab hoisting
This commit is contained in:
		
							
								
								
									
										15
									
								
								db/migrations/0171__cleanup_options.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								db/migrations/0171__cleanup_options.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | DELETE FROM options WHERE name IN ( | ||||||
|  |     'noteInfoWidget', | ||||||
|  |     'attributesWidget', | ||||||
|  |     'linkMapWidget', | ||||||
|  |     'noteRevisionsWidget', | ||||||
|  |     'whatLinksHereWidget', | ||||||
|  |     'codeNotesMimeTypes', | ||||||
|  |     'similarNotesWidget', | ||||||
|  |     'editedNotesWidget', | ||||||
|  |     'calendarWidget', | ||||||
|  |     'sidebarMinWidth', | ||||||
|  |     'sidebarWidthPercent', | ||||||
|  |     'showSidebarInNewTab', | ||||||
|  |     'hoistedNoteId' | ||||||
|  | ); | ||||||
| @@ -10,7 +10,6 @@ const FileStore = require('session-file-store')(session); | |||||||
| const sessionSecret = require('./services/session_secret'); | const sessionSecret = require('./services/session_secret'); | ||||||
| const dataDir = require('./services/data_dir'); | const dataDir = require('./services/data_dir'); | ||||||
| require('./services/handlers'); | require('./services/handlers'); | ||||||
| require('./services/hoisted_note_loader'); |  | ||||||
| require('./services/note_cache/note_cache_loader'); | require('./services/note_cache/note_cache_loader'); | ||||||
|  |  | ||||||
| const app = express(); | const app = express(); | ||||||
|   | |||||||
| @@ -91,7 +91,11 @@ export default class Entrypoints extends Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async unhoistCommand() { |     async unhoistCommand() { | ||||||
|         hoistedNoteService.unhoist(); |         const activeTabContext = appContext.tabManager.getActiveTabContext(); | ||||||
|  |  | ||||||
|  |         if (activeTabContext) { | ||||||
|  |             activeTabContext.unhoist(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     copyWithoutFormattingCommand() { |     copyWithoutFormattingCommand() { | ||||||
|   | |||||||
| @@ -379,13 +379,19 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
|     this.getYearNote = dateNotesService.getYearNote; |     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 |      * @method | ||||||
|      * @param {string} noteId - set hoisted note. 'root' will effectively unhoist |      * @param {string} noteId - set hoisted note. 'root' will effectively unhoist | ||||||
|      * @return {Promise} |      * @return {Promise} | ||||||
|      */ |      */ | ||||||
|     this.setHoistedNoteId = hoistedNoteService.setHoistedNoteId; |     this.setHoistedNoteId = (noteId) => { | ||||||
|  |         const activeTabContext = appContext.tabManager.getActiveTabContext(); | ||||||
|  |  | ||||||
|  |         if (activeTabContext) { | ||||||
|  |             activeTabContext.setHoistedNoteId(noteId); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @method |      * @method | ||||||
|   | |||||||
| @@ -1,23 +1,18 @@ | |||||||
| import options from './options.js'; |  | ||||||
| import appContext from "./app_context.js"; | import appContext from "./app_context.js"; | ||||||
| import treeService from "./tree.js"; | import treeService from "./tree.js"; | ||||||
|  |  | ||||||
| function getHoistedNoteId() { | function getHoistedNoteId() { | ||||||
|     return options.get('hoistedNoteId'); |     const activeTabContext = appContext.tabManager.getActiveTabContext(); | ||||||
| } |  | ||||||
|  |  | ||||||
| async function setHoistedNoteId(noteId) { |     return activeTabContext ? activeTabContext.hoistedNoteId : 'root'; | ||||||
|     if (getHoistedNoteId() === noteId) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await options.save('hoistedNoteId', noteId); |  | ||||||
|  |  | ||||||
|     appContext.triggerEvent('hoistedNoteChanged', {noteId}); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| async function unhoist() { | async function unhoist() { | ||||||
|     await setHoistedNoteId('root'); |     const activeTabContext = appContext.tabManager.getActiveTabContext(); | ||||||
|  |  | ||||||
|  |     if (activeTabContext) { | ||||||
|  |         await activeTabContext.unhoist(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function isTopLevelNode(node) { | function isTopLevelNode(node) { | ||||||
| @@ -58,7 +53,6 @@ async function checkNoteAccess(notePath) { | |||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     getHoistedNoteId, |     getHoistedNoteId, | ||||||
|     setHoistedNoteId, |  | ||||||
|     unhoist, |     unhoist, | ||||||
|     isTopLevelNode, |     isTopLevelNode, | ||||||
|     isRootNode, |     isRootNode, | ||||||
|   | |||||||
| @@ -2,13 +2,16 @@ import utils from './utils.js'; | |||||||
|  |  | ||||||
| const REQUEST_LOGGING_ENABLED = false; | 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 |     // 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 |     // also avoiding using underscores instead of dashes since nginx filters them out by default | ||||||
|     const allHeaders = { |     const allHeaders = { | ||||||
|         'trilium-source-id': glob.sourceId, |         'trilium-source-id': glob.sourceId, | ||||||
|         'trilium-local-now-datetime': utils.localNowDateTime(), |         'trilium-local-now-datetime': utils.localNowDateTime(), | ||||||
|  |         'trilium-hoisted-note-id': activeTabContext ? activeTabContext.hoistedNoteId : null, | ||||||
|         'x-csrf-token': glob.csrfToken |         'x-csrf-token': glob.csrfToken | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -52,6 +55,8 @@ async function call(method, url, data, headers = {}) { | |||||||
|  |  | ||||||
|     const start = Date.now(); |     const start = Date.now(); | ||||||
|  |  | ||||||
|  |     headers = await getHeaders(headers); | ||||||
|  |  | ||||||
|     if (utils.isElectron()) { |     if (utils.isElectron()) { | ||||||
|         const ipc = utils.dynamicRequire('electron').ipcRenderer; |         const ipc = utils.dynamicRequire('electron').ipcRenderer; | ||||||
|         const requestId = i++; |         const requestId = i++; | ||||||
| @@ -65,7 +70,7 @@ async function call(method, url, data, headers = {}) { | |||||||
|  |  | ||||||
|             ipc.send('server-request', { |             ipc.send('server-request', { | ||||||
|                 requestId: requestId, |                 requestId: requestId, | ||||||
|                 headers: getHeaders(headers), |                 headers: headers, | ||||||
|                 method: method, |                 method: method, | ||||||
|                 url: "/" + baseApiUrl + url, |                 url: "/" + baseApiUrl + url, | ||||||
|                 data: data |                 data: data | ||||||
| @@ -96,7 +101,7 @@ function ajax(url, method, data, headers) { | |||||||
|         const options = { |         const options = { | ||||||
|             url: baseApiUrl + url, |             url: baseApiUrl + url, | ||||||
|             type: method, |             type: method, | ||||||
|             headers: getHeaders(headers), |             headers: headers, | ||||||
|             timeout: 60000, |             timeout: 60000, | ||||||
|             success: (body, textStatus, jqXhr) => { |             success: (body, textStatus, jqXhr) => { | ||||||
|                 const respHeaders = {}; |                 const respHeaders = {}; | ||||||
|   | |||||||
| @@ -193,8 +193,8 @@ export default class TabManager extends Component { | |||||||
|         return tabContext; |         return tabContext; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async openTabWithNote(notePath, activate, tabId = null, hoistedNoteId = 'root') { |     async openTabWithNote(notePath, activate, tabId, hoistedNoteId) { | ||||||
|         const tabContext = await this.openEmptyTab(tabId); |         const tabContext = await this.openEmptyTab(tabId, hoistedNoteId); | ||||||
|  |  | ||||||
|         if (notePath) { |         if (notePath) { | ||||||
|             await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event |             await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event | ||||||
|   | |||||||
| @@ -500,8 +500,6 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     prepareRootNode() { |     prepareRootNode() { | ||||||
|         const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); |  | ||||||
|  |  | ||||||
|         return this.prepareNode(treeCache.getBranch('root')); |         return this.prepareNode(treeCache.getBranch('root')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -532,16 +530,6 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         return noteList; |         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) { |     updateNode(node) { | ||||||
|         const note = treeCache.getNoteFromCache(node.data.noteId); |         const note = treeCache.getNoteFromCache(node.data.noteId); | ||||||
|         const branch = treeCache.getBranch(node.data.branchId); |         const branch = treeCache.getBranch(node.data.branchId); | ||||||
| @@ -552,7 +540,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         node.data.isProtected = note.isProtected; |         node.data.isProtected = note.isProtected; | ||||||
|         node.data.noteType = note.type; |         node.data.noteType = note.type; | ||||||
|         node.folder = isFolder; |         node.folder = isFolder; | ||||||
|         node.icon = this.getIcon(note, isFolder); |         node.icon = note.getIcon(isFolder); | ||||||
|         node.extraClasses = this.getExtraClasses(note); |         node.extraClasses = this.getExtraClasses(note); | ||||||
|         node.title = utils.escapeHtml(title); |         node.title = utils.escapeHtml(title); | ||||||
|  |  | ||||||
| @@ -574,7 +562,6 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; |         const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; | ||||||
|         const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); |  | ||||||
|  |  | ||||||
|         const isFolder = this.isFolder(note); |         const isFolder = this.isFolder(note); | ||||||
|  |  | ||||||
| @@ -586,11 +573,11 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|             noteType: note.type, |             noteType: note.type, | ||||||
|             title: utils.escapeHtml(title), |             title: utils.escapeHtml(title), | ||||||
|             extraClasses: this.getExtraClasses(note), |             extraClasses: this.getExtraClasses(note), | ||||||
|             icon: this.getIcon(note, isFolder), |             icon: note.getIcon(isFolder), | ||||||
|             refKey: note.noteId, |             refKey: note.noteId, | ||||||
|             lazy: true, |             lazy: true, | ||||||
|             folder: isFolder, |             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 |             key: utils.randomString(12) // this should prevent some "duplicate key" errors | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -88,7 +88,8 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio | |||||||
|  |  | ||||||
|             const result = cls.init(() => { |             const result = cls.init(() => { | ||||||
|                 cls.set('sourceId', req.headers['trilium-source-id']); |                 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); |                 protectedSessionService.setProtectedSessionId(req); | ||||||
|  |  | ||||||
|                 const cb = () => routeHandler(req, res, next); |                 const cb = () => routeHandler(req, res, next); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ const build = require('./build'); | |||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 170; | const APP_DB_VERSION = 171; | ||||||
| const SYNC_VERSION = 16; | const SYNC_VERSION = 16; | ||||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,6 +24,10 @@ function set(key, value) { | |||||||
|     namespace.set(key, value); |     namespace.set(key, value); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getHoistedNoteId() { | ||||||
|  |     return namespace.get('hoistedNoteId'); | ||||||
|  | } | ||||||
|  |  | ||||||
| function getSourceId() { | function getSourceId() { | ||||||
|     return namespace.get('sourceId'); |     return namespace.get('sourceId'); | ||||||
| } | } | ||||||
| @@ -74,6 +78,7 @@ module.exports = { | |||||||
|     get, |     get, | ||||||
|     set, |     set, | ||||||
|     namespace, |     namespace, | ||||||
|  |     getHoistedNoteId, | ||||||
|     getSourceId, |     getSourceId, | ||||||
|     getLocalNowDateTime, |     getLocalNowDateTime, | ||||||
|     disableEntityEvents, |     disableEntityEvents, | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| let hoistedNoteId = 'root'; |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     getHoistedNoteId: () => hoistedNoteId, |  | ||||||
|     setHoistedNoteId(noteId) { hoistedNoteId = noteId; } |  | ||||||
| }; |  | ||||||
| @@ -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')); |  | ||||||
| }); |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const noteCache = require('./note_cache'); | const noteCache = require('./note_cache'); | ||||||
| const hoistedNoteService = require('../hoisted_note'); | const cls = require('../cls'); | ||||||
| const protectedSessionService = require('../protected_session'); | const protectedSessionService = require('../protected_session'); | ||||||
| const log = require('../log'); | const log = require('../log'); | ||||||
|  |  | ||||||
| @@ -88,10 +88,6 @@ function getNoteTitle(childNoteId, parentNoteId) { | |||||||
| function getNoteTitleArrayForPath(notePathArray) { | function getNoteTitleArrayForPath(notePathArray) { | ||||||
|     const titles = []; |     const titles = []; | ||||||
|  |  | ||||||
|     if (notePathArray[0] === hoistedNoteService.getHoistedNoteId() && notePathArray.length === 1) { |  | ||||||
|         return [ getNoteTitle(hoistedNoteService.getHoistedNoteId()) ]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     let parentNoteId = 'root'; |     let parentNoteId = 'root'; | ||||||
|     let hoistedNotePassed = false; |     let hoistedNotePassed = false; | ||||||
|  |  | ||||||
| @@ -103,7 +99,7 @@ function getNoteTitleArrayForPath(notePathArray) { | |||||||
|             titles.push(title); |             titles.push(title); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (noteId === hoistedNoteService.getHoistedNoteId()) { |         if (noteId === cls.getHoistedNoteId()) { | ||||||
|             hoistedNotePassed = true; |             hoistedNotePassed = true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -130,7 +126,7 @@ function getSomePath(note, path = []) { | |||||||
|         path.push(note.noteId); |         path.push(note.noteId); | ||||||
|         path.reverse(); |         path.reverse(); | ||||||
|  |  | ||||||
|         if (!path.includes(hoistedNoteService.getHoistedNoteId())) { |         if (!path.includes(cls.getHoistedNoteId())) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,10 +7,8 @@ const eventService = require('./events'); | |||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
| const cls = require('../services/cls'); | const cls = require('../services/cls'); | ||||||
| const Note = require('../entities/note'); | const Note = require('../entities/note'); | ||||||
| const NoteRevision = require('../entities/note_revision'); |  | ||||||
| const Branch = require('../entities/branch'); | const Branch = require('../entities/branch'); | ||||||
| const Attribute = require('../entities/attribute'); | const Attribute = require('../entities/attribute'); | ||||||
| const hoistedNoteService = require('../services/hoisted_note'); |  | ||||||
| const protectedSessionService = require('../services/protected_session'); | const protectedSessionService = require('../services/protected_session'); | ||||||
| const log = require('../services/log'); | const log = require('../services/log'); | ||||||
| const utils = require('../services/utils'); | const utils = require('../services/utils'); | ||||||
| @@ -524,7 +522,7 @@ function deleteBranch(branch, deleteId, taskContext) { | |||||||
|  |  | ||||||
|     if (branch.branchId === 'root' |     if (branch.branchId === 'root' | ||||||
|         || branch.noteId === 'root' |         || branch.noteId === 'root' | ||||||
|         || branch.noteId === hoistedNoteService.getHoistedNoteId()) { |         || branch.noteId === cls.getHoistedNoteId()) { | ||||||
|  |  | ||||||
|         throw new Error("Can't delete root branch/note"); |         throw new Error("Can't delete root branch/note"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -8,15 +8,15 @@ const SearchResult = require("../search_result.js"); | |||||||
| const SearchContext = require("../search_context.js"); | const SearchContext = require("../search_context.js"); | ||||||
| const noteCache = require('../../note_cache/note_cache.js'); | const noteCache = require('../../note_cache/note_cache.js'); | ||||||
| const noteCacheService = require('../../note_cache/note_cache_service.js'); | const noteCacheService = require('../../note_cache/note_cache_service.js'); | ||||||
| const hoistedNoteService = require('../../hoisted_note.js'); |  | ||||||
| const utils = require('../../utils.js'); | const utils = require('../../utils.js'); | ||||||
|  | const cls = require('../../cls.js'); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param {Expression} expression |  * @param {Expression} expression | ||||||
|  * @return {SearchResult[]} |  * @return {SearchResult[]} | ||||||
|  */ |  */ | ||||||
| function findNotesWithExpression(expression) { | function findNotesWithExpression(expression) { | ||||||
|     const hoistedNote = noteCache.notes[hoistedNoteService.getHoistedNoteId()]; |     const hoistedNote = noteCache.notes[cls.getHoistedNoteId()]; | ||||||
|     let allNotes = (hoistedNote && hoistedNote.noteId !== 'root') |     let allNotes = (hoistedNote && hoistedNote.noteId !== 'root') | ||||||
|         ? hoistedNote.subtreeNotes |         ? hoistedNote.subtreeNotes | ||||||
|         : Object.values(noteCache.notes); |         : Object.values(noteCache.notes); | ||||||
| @@ -35,7 +35,7 @@ function findNotesWithExpression(expression) { | |||||||
|  |  | ||||||
|     const searchResults = noteSet.notes |     const searchResults = noteSet.notes | ||||||
|         .map(note => searchContext.noteIdToNotePath[note.noteId] || noteCacheService.getSomePath(note)) |         .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)); |         .map(notePathArray => new SearchResult(notePathArray)); | ||||||
|  |  | ||||||
|     if (!noteSet.sorted) { |     if (!noteSet.sorted) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user