store "openTabs" session

This commit is contained in:
zadam 2019-05-10 21:43:40 +02:00
parent 89d4be504d
commit be68391c37
10 changed files with 118 additions and 45 deletions

View 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';

View File

@ -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 });
} }
}); });

View File

@ -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
}; };

View File

@ -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) {

View File

@ -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);
} }

View File

@ -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);

View File

@ -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 = {

View File

@ -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

View File

@ -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 = {

View File

@ -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);