mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	store "openTabs" session
This commit is contained in:
		
							
								
								
									
										4
									
								
								db/migrations/0134__create_openTabs_option.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								db/migrations/0134__create_openTabs_option.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| INSERT INTO options (name, value, utcDateCreated, utcDateModified, isSynced) | ||||
|     SELECT 'openTabs', '[{"notePath":"' || value || '","active": true}]', '2019-05-01T18:31:00.874Z', '2019-05-01T18:31:00.874Z', 0 FROM options WHERE name = 'startNotePath'; | ||||
|  | ||||
| DELETE FROM options WHERE name = 'startNotePath'; | ||||
| @@ -51,7 +51,7 @@ function goToLink(e) { | ||||
|  | ||||
|     if (notePath) { | ||||
|         if (e.ctrlKey) { | ||||
|             noteDetailService.loadNoteDetail(notePath.split("/").pop(), true); | ||||
|             noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); | ||||
|         } | ||||
|         else { | ||||
|             treeService.activateNote(notePath); | ||||
| @@ -117,7 +117,7 @@ function tabContextMenu(e) { | ||||
|         }, | ||||
|         selectContextMenuItem: (e, cmd) => { | ||||
|             if (cmd === 'openNoteInNewTab') { | ||||
|                 noteDetailService.loadNoteDetail(notePath.split("/").pop(), true); | ||||
|                 noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| @@ -138,7 +138,7 @@ $(document).on('click', '.note-detail-text a', function (e) { | ||||
|         // if it's a ctrl-click, then we open on new tab, otherwise normal flow (CKEditor opens link-editing dialog) | ||||
|         e.preventDefault(); | ||||
|  | ||||
|         noteDetailService.loadNoteDetail(notePath.split("/").pop(), true); | ||||
|         noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import treeService from './tree.js'; | ||||
| import TabContext from './note_context.js'; | ||||
| import TabContext from './tab_context.js'; | ||||
| import server from './server.js'; | ||||
| import messagingService from "./messaging.js"; | ||||
| import infoService from "./info.js"; | ||||
| @@ -54,7 +54,7 @@ async function reloadAllTabs() { | ||||
| } | ||||
|  | ||||
| async function openInTab(noteId) { | ||||
|     await loadNoteDetail(noteId, true); | ||||
|     await loadNoteDetail(noteId, { newTab: true }); | ||||
| } | ||||
|  | ||||
| async function switchToNote(notePath) { | ||||
| @@ -178,7 +178,10 @@ async function loadNoteDetailToContext(ctx, note, notePath) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function loadNoteDetail(notePath, newTab = false) { | ||||
| async function loadNoteDetail(notePath, options) { | ||||
|     const newTab = !!options.newTab; | ||||
|     const activate = !!options.activate; | ||||
|  | ||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|     const loadedNote = await loadNote(noteId); | ||||
|     let ctx; | ||||
| @@ -203,13 +206,10 @@ async function loadNoteDetail(notePath, newTab = false) { | ||||
|  | ||||
|     await loadNoteDetailToContext(ctx, loadedNote, notePath); | ||||
|  | ||||
|     if (!chromeTabs.activeTabEl) { | ||||
|     if (activate) { | ||||
|         // will also trigger showTab via event | ||||
|         chromeTabs.setCurrentTab(ctx.tab); | ||||
|     } | ||||
|     else if (!newTab) { | ||||
|         await showTab(ctx.tabId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function loadNote(noteId) { | ||||
| @@ -332,6 +332,49 @@ if (utils.isElectron()) { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| chromeTabsEl.addEventListener('activeTabChange', openTabsChanged); | ||||
| chromeTabsEl.addEventListener('tabAdd', openTabsChanged); | ||||
| chromeTabsEl.addEventListener('tabRemove', openTabsChanged); | ||||
| chromeTabsEl.addEventListener('tabReorder', openTabsChanged); | ||||
|  | ||||
| let tabsChangedTaskId = null; | ||||
|  | ||||
| function clearOpenTabsTask() { | ||||
|     if (tabsChangedTaskId) { | ||||
|         clearTimeout(tabsChangedTaskId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function openTabsChanged() { | ||||
|     // we don't want to send too many requests with tab changes so we always schedule task to do this in 3 seconds, | ||||
|     // but if there's any change in between, we cancel the old one and schedule new one | ||||
|     // so effectively we kind of wait until user stopped e.g. quickly switching tabs | ||||
|     clearOpenTabsTask(); | ||||
|  | ||||
|     tabsChangedTaskId = setTimeout(saveOpenTabs, 3000); | ||||
| } | ||||
|  | ||||
| async function saveOpenTabs() { | ||||
|     const activeTabEl = chromeTabs.activeTabEl; | ||||
|     const openTabs = []; | ||||
|  | ||||
|     for (const tabEl of chromeTabs.tabEls) { | ||||
|         const tabId = parseInt(tabEl.getAttribute('data-tab-id')); | ||||
|         const tabContext = tabContexts.find(tc => tc.tabId === tabId); | ||||
|  | ||||
|         if (tabContext) { | ||||
|             openTabs.push({ | ||||
|                 notePath: tabContext.notePath, | ||||
|                 active: activeTabEl === tabEl | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     await server.put('options', { | ||||
|         openTabs: JSON.stringify(openTabs) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| // this makes sure that when user e.g. reloads the page or navigates away from the page, the note's content is saved | ||||
| // this sends the request asynchronously and doesn't wait for result | ||||
| $(window).on('beforeunload', () => { saveNotesIfChanged(); }); // don't convert to short form, handler doesn't like returned promise | ||||
| @@ -355,5 +398,6 @@ export default { | ||||
|     onNoteChange, | ||||
|     addDetailLoadedListener, | ||||
|     getActiveContext, | ||||
|     getActiveComponent | ||||
|     getActiveComponent, | ||||
|     clearOpenTabsTask | ||||
| }; | ||||
| @@ -74,7 +74,7 @@ class TabContext { | ||||
|         this.$unprotectButton = this.$tabContent.find(".unprotect-button"); | ||||
|         this.$unprotectButton.click(protectedSessionService.unprotectNoteAndSendToServer); | ||||
| 
 | ||||
|         console.log(`Created note tab ${this.tabId} for ${this.noteId}`); | ||||
|         console.log(`Created note tab ${this.tabId}`); | ||||
|     } | ||||
| 
 | ||||
|     setNote(note, notePath) { | ||||
| @@ -1,5 +1,4 @@ | ||||
| import contextMenuWidget from './context_menu.js'; | ||||
| import treeContextMenuService from './tree_context_menu.js'; | ||||
| import dragAndDropSetup from './drag_and_drop.js'; | ||||
| import linkService from './link.js'; | ||||
| import messagingService from './messaging.js'; | ||||
| @@ -16,6 +15,7 @@ import Branch from '../entities/branch.js'; | ||||
| import NoteShort from '../entities/note_short.js'; | ||||
| import hoistedNoteService from '../services/hoisted_note.js'; | ||||
| import confirmDialog from "../dialogs/confirm.js"; | ||||
| import optionsInit from "../services/options_init.js"; | ||||
| import TreeContextMenu from "./tree_context_menu.js"; | ||||
|  | ||||
| const $tree = $("#tree"); | ||||
| @@ -25,8 +25,6 @@ const $scrollToActiveNoteButton = $("#scroll-to-active-note-button"); | ||||
| const $notePathList = $("#note-path-list"); | ||||
| const $notePathCount = $("#note-path-count"); | ||||
|  | ||||
| let startNotePath = null; | ||||
|  | ||||
| // focused & not active node can happen during multiselection where the node is selected but not activated | ||||
| // (its content is not displayed in the detail) | ||||
| function getFocusedNode() { | ||||
| @@ -360,29 +358,45 @@ function clearSelectedNodes() { | ||||
| } | ||||
|  | ||||
| async function treeInitialized() { | ||||
|     // - is used in mobile to indicate that we don't want to activate any note after load | ||||
|     if (startNotePath === '-') { | ||||
|         return; | ||||
|     let openTabs = []; | ||||
|  | ||||
|     try { | ||||
|         const options = await optionsInit.optionsReady; | ||||
|  | ||||
|         openTabs = JSON.parse(options.openTabs); | ||||
|     } | ||||
|     catch (e) { | ||||
|         messagingService.logError("Cannot retrieve open tabs: " + e.stack); | ||||
|     } | ||||
|  | ||||
|     const noteId = treeUtils.getNoteIdFromNotePath(startNotePath); | ||||
|     const filteredTabs = []; | ||||
|  | ||||
|     if (!await treeCache.noteExists(noteId)) { | ||||
|         // note doesn't exist so don't try to activate it | ||||
|         startNotePath = null; | ||||
|     for (const openTab of openTabs) { | ||||
|         const noteId = treeUtils.getNoteIdFromNotePath(openTab.notePath); | ||||
|  | ||||
|         if (await treeCache.noteExists(noteId)) { | ||||
|             // note doesn't exist so don't try to open tab for it | ||||
|             filteredTabs.push(openTab); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (startNotePath) { | ||||
|         // this is weird but it looks like even though init event has been called, but we the tree still | ||||
|         // can't find nodes for given path which causes double loading of data. Little timeout fixes this. | ||||
|         setTimeout(async () => { | ||||
|             const node = await activateNote(startNotePath); | ||||
|  | ||||
|             // looks like this this doesn't work when triggered immediatelly after activating node | ||||
|             // so waiting a second helps | ||||
|             setTimeout(() => node.makeVisible({scrollIntoView: true}), 1000); | ||||
|         }, 100); | ||||
|     if (filteredTabs.length === 0) { | ||||
|         filteredTabs.push({ | ||||
|             notePath: 'root', | ||||
|             active: true | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     for (const tab of filteredTabs) { | ||||
|         await noteDetailService.loadNoteDetail(tab.notePath, { | ||||
|             newTab: true, | ||||
|             activate: tab.active | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // previous opening triggered task to save tab changes but these are bogus changes (this is init) | ||||
|     // so we'll cancel it | ||||
|     noteDetailService.clearOpenTabsTask(); | ||||
| } | ||||
|  | ||||
| let ignoreNextActivationNoteId = null; | ||||
| @@ -406,7 +420,7 @@ function initFancyTree(tree) { | ||||
|                     node.setSelected(!node.isSelected()); | ||||
|                 } | ||||
|                 else if (event.ctrlKey) { | ||||
|                     noteDetailService.loadNoteDetail(node.data.noteId, true); | ||||
|                     noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true }); | ||||
|                 } | ||||
|                 else { | ||||
|                     node.setActive(); | ||||
| @@ -532,11 +546,6 @@ function getHashValueFromAddress() { | ||||
|  | ||||
| async function loadTreeCache() { | ||||
|     const resp = await server.get('tree'); | ||||
|     startNotePath = resp.startNotePath; | ||||
|  | ||||
|     if (isNotePathInAddress()) { | ||||
|         startNotePath = getHashValueFromAddress(); | ||||
|     } | ||||
|  | ||||
|     treeCache.load(resp.notes, resp.branches, resp.relations); | ||||
| } | ||||
|   | ||||
| @@ -5,8 +5,22 @@ const log = require('../../services/log'); | ||||
| const attributes = require('../../services/attributes'); | ||||
|  | ||||
| // options allowed to be updated directly in options dialog | ||||
| const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval', | ||||
|     'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId', 'mainFontSize', 'treeFontSize', 'detailFontSize']; | ||||
| const ALLOWED_OPTIONS = [ | ||||
|     'protectedSessionTimeout', | ||||
|     'noteRevisionSnapshotTimeInterval', | ||||
|     'zoomFactor', | ||||
|     'theme', | ||||
|     'syncServerHost', | ||||
|     'syncServerTimeout', | ||||
|     'syncProxy', | ||||
|     'leftPaneMinWidth', | ||||
|     'leftPaneWidthPercent', | ||||
|     'hoistedNoteId', | ||||
|     'mainFontSize', | ||||
|     'treeFontSize', | ||||
|     'detailFontSize', | ||||
|     'openTabs' | ||||
| ]; | ||||
|  | ||||
| async function getOptions() { | ||||
|     return await optionService.getOptionsMap(ALLOWED_OPTIONS); | ||||
|   | ||||
| @@ -11,8 +11,6 @@ async function addRecentNote(req) { | ||||
|         branchId: branchId, | ||||
|         notePath: notePath | ||||
|     }).save(); | ||||
|  | ||||
|     await optionService.setOption('startNotePath', notePath); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -76,7 +76,6 @@ async function getTree() { | ||||
|     const relations = await getRelations(noteIds); | ||||
|  | ||||
|     return { | ||||
|         startNotePath: (await optionService.getOption('startNotePath')) || 'root', | ||||
|         branches, | ||||
|         notes, | ||||
|         relations | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||
|  | ||||
| const APP_DB_VERSION = 133; | ||||
| const APP_DB_VERSION = 134; | ||||
| const SYNC_VERSION = 8; | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -29,7 +29,12 @@ async function initSyncedOptions(username, password) { | ||||
| } | ||||
|  | ||||
| async function initNotSyncedOptions(initialized, startNotePath = 'root', syncServerHost = '', syncProxy = '') { | ||||
|     await optionService.createOption('startNotePath', startNotePath, false); | ||||
|     await optionService.createOption('openTabs', JSON.stringify([ | ||||
|         { | ||||
|             notePath: startNotePath, | ||||
|             active: 1 | ||||
|         } | ||||
|     ]), false); | ||||
|     await optionService.createOption('hoistedNoteId', 'root', false); | ||||
|     await optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false); | ||||
|     await optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user