mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
store "openTabs" session
This commit is contained in:
parent
89d4be504d
commit
be68391c37
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 (notePath) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
noteDetailService.loadNoteDetail(notePath.split("/").pop(), true);
|
noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
treeService.activateNote(notePath);
|
treeService.activateNote(notePath);
|
||||||
@ -117,7 +117,7 @@ function tabContextMenu(e) {
|
|||||||
},
|
},
|
||||||
selectContextMenuItem: (e, cmd) => {
|
selectContextMenuItem: (e, cmd) => {
|
||||||
if (cmd === 'openNoteInNewTab') {
|
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)
|
// if it's a ctrl-click, then we open on new tab, otherwise normal flow (CKEditor opens link-editing dialog)
|
||||||
e.preventDefault();
|
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 treeService from './tree.js';
|
||||||
import TabContext from './note_context.js';
|
import TabContext from './tab_context.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import messagingService from "./messaging.js";
|
import messagingService from "./messaging.js";
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
@ -54,7 +54,7 @@ async function reloadAllTabs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openInTab(noteId) {
|
async function openInTab(noteId) {
|
||||||
await loadNoteDetail(noteId, true);
|
await loadNoteDetail(noteId, { newTab: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchToNote(notePath) {
|
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 noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
const loadedNote = await loadNote(noteId);
|
const loadedNote = await loadNote(noteId);
|
||||||
let ctx;
|
let ctx;
|
||||||
@ -203,13 +206,10 @@ async function loadNoteDetail(notePath, newTab = false) {
|
|||||||
|
|
||||||
await loadNoteDetailToContext(ctx, loadedNote, notePath);
|
await loadNoteDetailToContext(ctx, loadedNote, notePath);
|
||||||
|
|
||||||
if (!chromeTabs.activeTabEl) {
|
if (activate) {
|
||||||
// will also trigger showTab via event
|
// will also trigger showTab via event
|
||||||
chromeTabs.setCurrentTab(ctx.tab);
|
chromeTabs.setCurrentTab(ctx.tab);
|
||||||
}
|
}
|
||||||
else if (!newTab) {
|
|
||||||
await showTab(ctx.tabId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadNote(noteId) {
|
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 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
|
// 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
|
$(window).on('beforeunload', () => { saveNotesIfChanged(); }); // don't convert to short form, handler doesn't like returned promise
|
||||||
@ -355,5 +398,6 @@ export default {
|
|||||||
onNoteChange,
|
onNoteChange,
|
||||||
addDetailLoadedListener,
|
addDetailLoadedListener,
|
||||||
getActiveContext,
|
getActiveContext,
|
||||||
getActiveComponent
|
getActiveComponent,
|
||||||
|
clearOpenTabsTask
|
||||||
};
|
};
|
@ -74,7 +74,7 @@ class TabContext {
|
|||||||
this.$unprotectButton = this.$tabContent.find(".unprotect-button");
|
this.$unprotectButton = this.$tabContent.find(".unprotect-button");
|
||||||
this.$unprotectButton.click(protectedSessionService.unprotectNoteAndSendToServer);
|
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) {
|
setNote(note, notePath) {
|
@ -1,5 +1,4 @@
|
|||||||
import contextMenuWidget from './context_menu.js';
|
import contextMenuWidget from './context_menu.js';
|
||||||
import treeContextMenuService from './tree_context_menu.js';
|
|
||||||
import dragAndDropSetup from './drag_and_drop.js';
|
import dragAndDropSetup from './drag_and_drop.js';
|
||||||
import linkService from './link.js';
|
import linkService from './link.js';
|
||||||
import messagingService from './messaging.js';
|
import messagingService from './messaging.js';
|
||||||
@ -16,6 +15,7 @@ import Branch from '../entities/branch.js';
|
|||||||
import NoteShort from '../entities/note_short.js';
|
import NoteShort from '../entities/note_short.js';
|
||||||
import hoistedNoteService from '../services/hoisted_note.js';
|
import hoistedNoteService from '../services/hoisted_note.js';
|
||||||
import confirmDialog from "../dialogs/confirm.js";
|
import confirmDialog from "../dialogs/confirm.js";
|
||||||
|
import optionsInit from "../services/options_init.js";
|
||||||
import TreeContextMenu from "./tree_context_menu.js";
|
import TreeContextMenu from "./tree_context_menu.js";
|
||||||
|
|
||||||
const $tree = $("#tree");
|
const $tree = $("#tree");
|
||||||
@ -25,8 +25,6 @@ const $scrollToActiveNoteButton = $("#scroll-to-active-note-button");
|
|||||||
const $notePathList = $("#note-path-list");
|
const $notePathList = $("#note-path-list");
|
||||||
const $notePathCount = $("#note-path-count");
|
const $notePathCount = $("#note-path-count");
|
||||||
|
|
||||||
let startNotePath = null;
|
|
||||||
|
|
||||||
// focused & not active node can happen during multiselection where the node is selected but not activated
|
// focused & not active node can happen during multiselection where the node is selected but not activated
|
||||||
// (its content is not displayed in the detail)
|
// (its content is not displayed in the detail)
|
||||||
function getFocusedNode() {
|
function getFocusedNode() {
|
||||||
@ -360,29 +358,45 @@ function clearSelectedNodes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function treeInitialized() {
|
async function treeInitialized() {
|
||||||
// - is used in mobile to indicate that we don't want to activate any note after load
|
let openTabs = [];
|
||||||
if (startNotePath === '-') {
|
|
||||||
return;
|
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)) {
|
for (const openTab of openTabs) {
|
||||||
// note doesn't exist so don't try to activate it
|
const noteId = treeUtils.getNoteIdFromNotePath(openTab.notePath);
|
||||||
startNotePath = null;
|
|
||||||
|
if (await treeCache.noteExists(noteId)) {
|
||||||
|
// note doesn't exist so don't try to open tab for it
|
||||||
|
filteredTabs.push(openTab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startNotePath) {
|
if (filteredTabs.length === 0) {
|
||||||
// this is weird but it looks like even though init event has been called, but we the tree still
|
filteredTabs.push({
|
||||||
// can't find nodes for given path which causes double loading of data. Little timeout fixes this.
|
notePath: 'root',
|
||||||
setTimeout(async () => {
|
active: true
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
let ignoreNextActivationNoteId = null;
|
||||||
@ -406,7 +420,7 @@ function initFancyTree(tree) {
|
|||||||
node.setSelected(!node.isSelected());
|
node.setSelected(!node.isSelected());
|
||||||
}
|
}
|
||||||
else if (event.ctrlKey) {
|
else if (event.ctrlKey) {
|
||||||
noteDetailService.loadNoteDetail(node.data.noteId, true);
|
noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.setActive();
|
node.setActive();
|
||||||
@ -532,11 +546,6 @@ function getHashValueFromAddress() {
|
|||||||
|
|
||||||
async function loadTreeCache() {
|
async function loadTreeCache() {
|
||||||
const resp = await server.get('tree');
|
const resp = await server.get('tree');
|
||||||
startNotePath = resp.startNotePath;
|
|
||||||
|
|
||||||
if (isNotePathInAddress()) {
|
|
||||||
startNotePath = getHashValueFromAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
treeCache.load(resp.notes, resp.branches, resp.relations);
|
treeCache.load(resp.notes, resp.branches, resp.relations);
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,22 @@ const log = require('../../services/log');
|
|||||||
const attributes = require('../../services/attributes');
|
const attributes = require('../../services/attributes');
|
||||||
|
|
||||||
// options allowed to be updated directly in options dialog
|
// options allowed to be updated directly in options dialog
|
||||||
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval',
|
const ALLOWED_OPTIONS = [
|
||||||
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId', 'mainFontSize', 'treeFontSize', 'detailFontSize'];
|
'protectedSessionTimeout',
|
||||||
|
'noteRevisionSnapshotTimeInterval',
|
||||||
|
'zoomFactor',
|
||||||
|
'theme',
|
||||||
|
'syncServerHost',
|
||||||
|
'syncServerTimeout',
|
||||||
|
'syncProxy',
|
||||||
|
'leftPaneMinWidth',
|
||||||
|
'leftPaneWidthPercent',
|
||||||
|
'hoistedNoteId',
|
||||||
|
'mainFontSize',
|
||||||
|
'treeFontSize',
|
||||||
|
'detailFontSize',
|
||||||
|
'openTabs'
|
||||||
|
];
|
||||||
|
|
||||||
async function getOptions() {
|
async function getOptions() {
|
||||||
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
|
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
|
||||||
|
@ -11,8 +11,6 @@ async function addRecentNote(req) {
|
|||||||
branchId: branchId,
|
branchId: branchId,
|
||||||
notePath: notePath
|
notePath: notePath
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
await optionService.setOption('startNotePath', notePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -76,7 +76,6 @@ async function getTree() {
|
|||||||
const relations = await getRelations(noteIds);
|
const relations = await getRelations(noteIds);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startNotePath: (await optionService.getOption('startNotePath')) || 'root',
|
|
||||||
branches,
|
branches,
|
||||||
notes,
|
notes,
|
||||||
relations
|
relations
|
||||||
|
@ -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 = 133;
|
const APP_DB_VERSION = 134;
|
||||||
const SYNC_VERSION = 8;
|
const SYNC_VERSION = 8;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -29,7 +29,12 @@ async function initSyncedOptions(username, password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initNotSyncedOptions(initialized, startNotePath = 'root', syncServerHost = '', syncProxy = '') {
|
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('hoistedNoteId', 'root', false);
|
||||||
await optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false);
|
await optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false);
|
||||||
await optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false);
|
await optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user