2022-12-01 13:07:23 +01:00
|
|
|
import Component from "./component.js";
|
|
|
|
import SpacedUpdate from "../services/spaced_update.js";
|
|
|
|
import server from "../services/server.js";
|
|
|
|
import options from "../services/options.js";
|
|
|
|
import froca from "../services/froca.js";
|
|
|
|
import treeService from "../services/tree.js";
|
|
|
|
import utils from "../services/utils.js";
|
2021-05-22 12:35:41 +02:00
|
|
|
import NoteContext from "./note_context.js";
|
2020-04-25 23:52:13 +02:00
|
|
|
import appContext from "./app_context.js";
|
2022-02-09 21:21:17 +01:00
|
|
|
import Mutex from "../utils/mutex.js";
|
2023-04-11 21:41:55 +02:00
|
|
|
import linkService from "../services/link.js";
|
2020-02-07 21:08:55 +01:00
|
|
|
|
|
|
|
export default class TabManager extends Component {
|
2020-02-27 10:03:14 +01:00
|
|
|
constructor() {
|
|
|
|
super();
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2022-02-09 21:21:17 +01:00
|
|
|
this.mutex = new Mutex();
|
|
|
|
|
2021-05-22 13:04:08 +02:00
|
|
|
this.activeNtxId = null;
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2021-10-09 22:03:24 +02:00
|
|
|
// elements are arrays of note contexts for each tab (one main context + subcontexts [splits])
|
2021-10-09 21:20:12 +02:00
|
|
|
this.recentlyClosedTabs = [];
|
|
|
|
|
2020-02-07 21:08:55 +01:00
|
|
|
this.tabsUpdate = new SpacedUpdate(async () => {
|
2020-04-25 23:52:13 +02:00
|
|
|
if (!appContext.isMainWindow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-11 22:00:04 +02:00
|
|
|
const openNoteContexts = this.noteContexts
|
|
|
|
.map(nc => nc.getPojoState())
|
2020-02-07 21:08:55 +01:00
|
|
|
.filter(t => !!t);
|
|
|
|
|
|
|
|
await server.put('options', {
|
2023-04-11 22:00:04 +02:00
|
|
|
openNoteContexts: JSON.stringify(openNoteContexts)
|
2020-02-07 21:08:55 +01:00
|
|
|
});
|
|
|
|
});
|
2021-02-27 23:39:02 +01:00
|
|
|
|
|
|
|
appContext.addBeforeUnloadListener(this);
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
/** @type {NoteContext[]} */
|
|
|
|
get noteContexts() {
|
2020-02-09 21:13:05 +01:00
|
|
|
return this.children;
|
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
/** @type {NoteContext[]} */
|
|
|
|
get mainNoteContexts() {
|
|
|
|
return this.noteContexts.filter(nc => !nc.mainNtxId)
|
2021-05-20 23:13:34 +02:00
|
|
|
}
|
|
|
|
|
2020-04-25 23:52:13 +02:00
|
|
|
async loadTabs() {
|
2022-12-14 10:00:33 +01:00
|
|
|
try {
|
2023-04-11 22:00:04 +02:00
|
|
|
const noteContextsToOpen = appContext.isMainWindow
|
|
|
|
? (options.getJson('openNoteContexts') || [])
|
2022-12-14 10:00:33 +01:00
|
|
|
: [];
|
|
|
|
|
|
|
|
// preload all notes at once
|
|
|
|
await froca.getNotes([
|
2023-04-11 22:00:04 +02:00
|
|
|
...noteContextsToOpen.map(tab => treeService.getNoteIdFromNotePath(tab.notePath)),
|
|
|
|
...noteContextsToOpen.map(tab => tab.hoistedNoteId),
|
2022-12-14 10:00:33 +01:00
|
|
|
], true);
|
|
|
|
|
2023-04-11 22:00:04 +02:00
|
|
|
const filteredNoteContexts = noteContextsToOpen.filter(openTab => {
|
2023-04-11 21:41:55 +02:00
|
|
|
if (utils.isMobile()) { // mobile frontend doesn't have tabs so show only the active tab
|
|
|
|
return !!openTab.active;
|
|
|
|
}
|
|
|
|
|
|
|
|
const noteId = treeService.getNoteIdFromNotePath(openTab.notePath);
|
|
|
|
if (!(noteId in froca.notes)) {
|
2022-12-14 10:00:33 +01:00
|
|
|
// note doesn't exist so don't try to open tab for it
|
2023-04-11 21:41:55 +02:00
|
|
|
return false;
|
2022-12-14 10:00:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(openTab.hoistedNoteId in froca.notes)) {
|
|
|
|
openTab.hoistedNoteId = 'root';
|
|
|
|
}
|
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
return true;
|
|
|
|
});
|
2022-12-13 21:45:57 +01:00
|
|
|
|
2023-01-31 23:20:11 +01:00
|
|
|
// resolve before opened tabs can change this
|
2023-04-11 21:41:55 +02:00
|
|
|
const parsedFromUrl = treeService.parseNavigationStateFromAddress();
|
2022-12-22 23:38:57 +01:00
|
|
|
|
2023-04-11 22:00:04 +02:00
|
|
|
if (filteredNoteContexts.length === 0) {
|
2023-04-11 21:41:55 +02:00
|
|
|
parsedFromUrl.ntxId = parsedFromUrl.ntxId || NoteContext.generateNtxId(); // generate already here, so that we later know which one to activate
|
|
|
|
|
2023-04-11 22:00:04 +02:00
|
|
|
filteredNoteContexts.push({
|
2023-04-11 21:41:55 +02:00
|
|
|
notePath: parsedFromUrl.notePath || 'root',
|
|
|
|
ntxId: parsedFromUrl.ntxId,
|
2022-12-14 10:00:33 +01:00
|
|
|
active: true,
|
2023-04-11 21:41:55 +02:00
|
|
|
hoistedNoteId: parsedFromUrl.hoistedNoteId || 'root',
|
|
|
|
viewScope: parsedFromUrl.viewScope || {}
|
2022-12-14 10:00:33 +01:00
|
|
|
});
|
2023-04-11 22:00:04 +02:00
|
|
|
} else if (!filteredNoteContexts.find(tab => tab.active)) {
|
|
|
|
filteredNoteContexts[0].active = true;
|
2022-12-14 10:00:33 +01:00
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2022-12-14 10:00:33 +01:00
|
|
|
await this.tabsUpdate.allowUpdateWithoutChange(async () => {
|
2023-04-11 22:00:04 +02:00
|
|
|
for (const tab of filteredNoteContexts) {
|
2023-02-14 16:06:49 +01:00
|
|
|
await this.openContextWithNote(tab.notePath, {
|
|
|
|
activate: tab.active,
|
|
|
|
ntxId: tab.ntxId,
|
|
|
|
mainNtxId: tab.mainNtxId,
|
|
|
|
hoistedNoteId: tab.hoistedNoteId,
|
2023-04-03 23:47:24 +02:00
|
|
|
viewScope: tab.viewScope
|
2023-02-14 16:06:49 +01:00
|
|
|
});
|
2022-12-14 10:00:33 +01:00
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
});
|
|
|
|
|
2022-12-14 10:00:33 +01:00
|
|
|
// if there's notePath in the URL, make sure it's open and active
|
2022-12-22 23:38:57 +01:00
|
|
|
// (useful, for e.g. opening clipped notes from clipper or opening link in an extra window)
|
2023-04-11 21:41:55 +02:00
|
|
|
if (parsedFromUrl.notePath) {
|
|
|
|
await appContext.tabManager.switchToNoteContext(
|
|
|
|
parsedFromUrl.ntxId,
|
|
|
|
parsedFromUrl.notePath,
|
|
|
|
parsedFromUrl.viewScope,
|
|
|
|
parsedFromUrl.hoistedNoteId
|
|
|
|
);
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
2022-12-14 10:00:33 +01:00
|
|
|
}
|
|
|
|
catch (e) {
|
2023-04-11 22:00:04 +02:00
|
|
|
logError(`Loading note contexts '${options.get('openNoteContexts')}' failed: ${e.message} ${e.stack}`);
|
2022-12-13 16:57:46 +01:00
|
|
|
|
2022-12-14 10:00:33 +01:00
|
|
|
// try to recover
|
|
|
|
await this.openEmptyTab();
|
2022-12-13 16:57:46 +01:00
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:35:41 +02:00
|
|
|
noteSwitchedEvent({noteContext}) {
|
2021-05-22 12:26:45 +02:00
|
|
|
if (noteContext.isActive()) {
|
2023-04-11 21:41:55 +02:00
|
|
|
this.setCurrentNavigationStateToHash();
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
2020-02-08 21:23:42 +01:00
|
|
|
|
|
|
|
this.tabsUpdate.scheduleUpdate();
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
setCurrentNavigationStateToHash() {
|
|
|
|
const calculatedHash = this.calculateHash();
|
2020-02-09 21:13:05 +01:00
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
// update if it's the first history entry or there has been a change
|
|
|
|
if (window.history.length === 0 || calculatedHash !== window.location?.hash) {
|
2020-02-09 21:13:05 +01:00
|
|
|
// using pushState instead of directly modifying document.location because it does not trigger hashchange
|
2023-04-11 21:41:55 +02:00
|
|
|
window.history.pushState(null, "", calculatedHash);
|
2020-10-26 20:11:43 +01:00
|
|
|
}
|
2020-02-09 21:13:05 +01:00
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
const activeNoteContext = this.getActiveContext();
|
2022-01-03 19:52:24 +01:00
|
|
|
this.updateDocumentTitle(activeNoteContext);
|
2020-03-08 17:17:18 +01:00
|
|
|
|
|
|
|
this.triggerEvent('activeNoteChanged'); // trigger this even in on popstate event
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
calculateHash() {
|
|
|
|
const activeNoteContext = this.getActiveContext();
|
|
|
|
if (!activeNoteContext) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
return linkService.calculateHash({
|
|
|
|
notePath: activeNoteContext.notePath,
|
|
|
|
ntxId: activeNoteContext.ntxId,
|
|
|
|
hoistedNoteId: activeNoteContext.hoistedNoteId,
|
|
|
|
viewScope: activeNoteContext.viewScope
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-10-24 14:53:45 +02:00
|
|
|
/** @returns {NoteContext[]} */
|
2021-05-22 12:26:45 +02:00
|
|
|
getNoteContexts() {
|
|
|
|
return this.noteContexts;
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2022-06-29 22:38:35 +02:00
|
|
|
/** @returns {NoteContext[]} */
|
|
|
|
getMainNoteContexts() {
|
|
|
|
return this.noteContexts.filter(nc => nc.isMainContext());
|
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
/** @returns {NoteContext} */
|
|
|
|
getNoteContextById(ntxId) {
|
|
|
|
const noteContext = this.noteContexts.find(nc => nc.ntxId === ntxId);
|
2021-05-20 23:13:34 +02:00
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
if (!noteContext) {
|
|
|
|
throw new Error(`Cannot find noteContext id='${ntxId}'`);
|
2021-05-20 23:13:34 +02:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
return noteContext;
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
/** @returns {NoteContext} */
|
2021-05-22 12:35:41 +02:00
|
|
|
getActiveContext() {
|
2021-05-22 13:04:08 +02:00
|
|
|
return this.activeNtxId
|
|
|
|
? this.getNoteContextById(this.activeNtxId)
|
|
|
|
: null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @returns {NoteContext} */
|
|
|
|
getActiveMainContext() {
|
|
|
|
return this.activeNtxId
|
|
|
|
? this.getNoteContextById(this.activeNtxId).getMainContext()
|
2021-05-20 23:13:34 +02:00
|
|
|
: null;
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @returns {string|null} */
|
2021-05-22 12:35:41 +02:00
|
|
|
getActiveContextNotePath() {
|
|
|
|
const activeContext = this.getActiveContext();
|
2020-02-07 21:08:55 +01:00
|
|
|
return activeContext ? activeContext.notePath : null;
|
|
|
|
}
|
|
|
|
|
2023-01-03 13:35:10 +01:00
|
|
|
/** @returns {FNote} */
|
2021-05-22 12:35:41 +02:00
|
|
|
getActiveContextNote() {
|
|
|
|
const activeContext = this.getActiveContext();
|
2020-02-07 21:08:55 +01:00
|
|
|
return activeContext ? activeContext.note : null;
|
|
|
|
}
|
|
|
|
|
2021-10-24 14:53:45 +02:00
|
|
|
/** @returns {string|null} */
|
2021-05-22 12:35:41 +02:00
|
|
|
getActiveContextNoteId() {
|
|
|
|
const activeNote = this.getActiveContextNote();
|
2020-02-07 21:08:55 +01:00
|
|
|
|
|
|
|
return activeNote ? activeNote.noteId : null;
|
|
|
|
}
|
|
|
|
|
2021-10-24 14:53:45 +02:00
|
|
|
/** @returns {string|null} */
|
2021-05-22 12:35:41 +02:00
|
|
|
getActiveContextNoteType() {
|
|
|
|
const activeNote = this.getActiveContextNote();
|
2020-02-07 21:08:55 +01:00
|
|
|
|
|
|
|
return activeNote ? activeNote.type : null;
|
|
|
|
}
|
2022-09-21 21:41:51 -04:00
|
|
|
/** @returns {string|null} */
|
|
|
|
getActiveContextNoteMime() {
|
|
|
|
const activeNote = this.getActiveContextNote();
|
|
|
|
|
|
|
|
return activeNote ? activeNote.mime : null;
|
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
async switchToNoteContext(ntxId, notePath, viewScope = {}, hoistedNoteId = null) {
|
2021-05-22 12:26:45 +02:00
|
|
|
const noteContext = this.noteContexts.find(nc => nc.ntxId === ntxId)
|
2020-02-29 16:26:46 +01:00
|
|
|
|| await this.openEmptyTab();
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2022-12-13 16:57:46 +01:00
|
|
|
await this.activateNoteContext(noteContext.ntxId);
|
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
if (hoistedNoteId) {
|
|
|
|
await noteContext.setHoistedNoteId(hoistedNoteId);
|
|
|
|
}
|
|
|
|
|
2022-12-13 16:57:46 +01:00
|
|
|
if (notePath) {
|
2023-04-11 21:41:55 +02:00
|
|
|
await noteContext.setNote(notePath, { viewScope });
|
2022-12-13 16:57:46 +01:00
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async openAndActivateEmptyTab() {
|
2021-05-22 12:26:45 +02:00
|
|
|
const noteContext = await this.openEmptyTab();
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2021-05-22 12:35:41 +02:00
|
|
|
await this.activateNoteContext(noteContext.ntxId);
|
2020-02-28 11:46:35 +01:00
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
await noteContext.setEmpty();
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2022-01-10 20:44:59 +01:00
|
|
|
async openEmptyTab(ntxId = null, hoistedNoteId = 'root', mainNtxId = null) {
|
2021-05-22 12:26:45 +02:00
|
|
|
const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId);
|
2021-05-21 22:34:40 +02:00
|
|
|
|
2022-12-18 22:05:06 +01:00
|
|
|
let existingNoteContext;
|
2022-12-13 21:45:57 +01:00
|
|
|
|
|
|
|
if (utils.isMobile()) {
|
|
|
|
// kind of hacky way to enforce a single tab on mobile interface - all requests to create a new one
|
|
|
|
// are forced to reuse the existing ab instead
|
|
|
|
existingNoteContext = this.getActiveContext();
|
|
|
|
} else {
|
|
|
|
existingNoteContext = this.children.find(nc => nc.ntxId === noteContext.ntxId);
|
|
|
|
}
|
2021-05-21 22:34:40 +02:00
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
if (existingNoteContext) {
|
2022-12-13 21:45:57 +01:00
|
|
|
await existingNoteContext.setHoistedNoteId(hoistedNoteId);
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
return existingNoteContext;
|
2021-05-21 22:34:40 +02:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
this.child(noteContext);
|
2020-02-27 10:03:14 +01:00
|
|
|
|
2021-05-22 12:35:41 +02:00
|
|
|
await this.triggerEvent('newNoteContextCreated', {noteContext});
|
2020-02-27 10:03:14 +01:00
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
return noteContext;
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2022-12-19 23:19:47 +01:00
|
|
|
async openInNewTab(targetNoteId, hoistedNoteId = null) {
|
|
|
|
const noteContext = await this.openEmptyTab(null, hoistedNoteId || this.getActiveContext().hoistedNoteId);
|
|
|
|
|
|
|
|
await noteContext.setNote(targetNoteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
async openInSameTab(targetNoteId, hoistedNoteId = null) {
|
|
|
|
const activeContext = this.getActiveContext();
|
|
|
|
await activeContext.setHoistedNoteId(hoistedNoteId || activeContext.hoistedNoteId);
|
|
|
|
await activeContext.setNote(targetNoteId);
|
|
|
|
}
|
|
|
|
|
2020-11-24 23:24:05 +01:00
|
|
|
/**
|
|
|
|
* If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab.
|
|
|
|
*/
|
2023-04-03 23:47:24 +02:00
|
|
|
async openTabWithNoteWithHoisting(notePath, opts = {}) {
|
2021-05-22 12:35:41 +02:00
|
|
|
const noteContext = this.getActiveContext();
|
2020-11-24 23:24:05 +01:00
|
|
|
let hoistedNoteId = 'root';
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
if (noteContext) {
|
|
|
|
const resolvedNotePath = await treeService.resolveNotePath(notePath, noteContext.hoistedNoteId);
|
2020-11-24 23:24:05 +01:00
|
|
|
|
2022-12-21 16:11:00 +01:00
|
|
|
if (resolvedNotePath.includes(noteContext.hoistedNoteId) || resolvedNotePath.includes('_hidden')) {
|
2021-05-22 12:26:45 +02:00
|
|
|
hoistedNoteId = noteContext.hoistedNoteId;
|
2020-11-24 23:24:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-03 23:47:24 +02:00
|
|
|
opts.hoistedNoteId = hoistedNoteId;
|
|
|
|
|
|
|
|
return this.openContextWithNote(notePath, opts);
|
2020-11-24 23:24:05 +01:00
|
|
|
}
|
|
|
|
|
2023-02-14 16:06:49 +01:00
|
|
|
async openContextWithNote(notePath, opts = {}) {
|
|
|
|
const activate = !!opts.activate;
|
|
|
|
const ntxId = opts.ntxId || null;
|
|
|
|
const mainNtxId = opts.mainNtxId || null;
|
|
|
|
const hoistedNoteId = opts.hoistedNoteId || 'root';
|
2023-04-03 23:47:24 +02:00
|
|
|
const viewScope = opts.viewScope || { viewMode: "default" };
|
2023-02-14 16:06:49 +01:00
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
const noteContext = await this.openEmptyTab(ntxId, hoistedNoteId, mainNtxId);
|
2020-02-27 12:26:42 +01:00
|
|
|
|
2020-05-05 19:30:03 +02:00
|
|
|
if (notePath) {
|
2023-02-14 16:06:49 +01:00
|
|
|
await noteContext.setNote(notePath, {
|
|
|
|
// if activate is false then send normal noteSwitched event
|
|
|
|
triggerSwitchEvent: !activate,
|
2023-04-03 23:47:24 +02:00
|
|
|
viewScope: viewScope
|
2023-02-14 16:06:49 +01:00
|
|
|
});
|
2020-05-05 19:30:03 +02:00
|
|
|
}
|
2020-02-27 12:26:42 +01:00
|
|
|
|
|
|
|
if (activate) {
|
2021-05-22 12:35:41 +02:00
|
|
|
this.activateNoteContext(noteContext.ntxId, false);
|
2020-02-27 12:26:42 +01:00
|
|
|
|
2021-05-22 12:35:41 +02:00
|
|
|
await this.triggerEvent('noteSwitchedAndActivated', {
|
2021-05-22 12:26:45 +02:00
|
|
|
noteContext,
|
|
|
|
notePath: noteContext.notePath // resolved note path
|
2020-02-28 00:31:12 +01:00
|
|
|
});
|
2020-02-27 12:26:42 +01:00
|
|
|
}
|
2020-05-08 23:39:46 +02:00
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
return noteContext;
|
2020-02-27 12:26:42 +01:00
|
|
|
}
|
|
|
|
|
2020-02-07 21:08:55 +01:00
|
|
|
async activateOrOpenNote(noteId) {
|
2021-05-22 12:26:45 +02:00
|
|
|
for (const noteContext of this.getNoteContexts()) {
|
|
|
|
if (noteContext.note && noteContext.note.noteId === noteId) {
|
2021-05-22 12:35:41 +02:00
|
|
|
this.activateNoteContext(noteContext.ntxId);
|
2020-04-05 15:35:01 +02:00
|
|
|
|
2020-02-07 21:08:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if no tab with this note has been found we'll create new tab
|
|
|
|
|
2023-02-14 16:06:49 +01:00
|
|
|
await this.openContextWithNote(noteId, { activate: true });
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-05-24 22:29:49 +02:00
|
|
|
async activateNoteContext(ntxId, triggerEvent = true) {
|
2021-05-22 13:04:08 +02:00
|
|
|
if (ntxId === this.activeNtxId) {
|
2020-02-07 21:08:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-22 13:04:08 +02:00
|
|
|
this.activeNtxId = ntxId;
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2020-02-27 12:26:42 +01:00
|
|
|
if (triggerEvent) {
|
2021-05-24 22:29:49 +02:00
|
|
|
await this.triggerEvent('activeContextChanged', {
|
2021-05-22 12:26:45 +02:00
|
|
|
noteContext: this.getNoteContextById(ntxId)
|
2020-03-07 13:40:46 +01:00
|
|
|
});
|
2020-02-27 12:26:42 +01:00
|
|
|
}
|
2020-02-08 21:23:42 +01:00
|
|
|
|
|
|
|
this.tabsUpdate.scheduleUpdate();
|
2020-05-22 19:30:21 +02:00
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
this.setCurrentNavigationStateToHash();
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2022-06-29 22:38:35 +02:00
|
|
|
/**
|
|
|
|
* @param ntxId
|
|
|
|
* @returns {Promise<boolean>} true if note context has been removed, false otherwise
|
|
|
|
*/
|
2021-05-22 12:35:41 +02:00
|
|
|
async removeNoteContext(ntxId) {
|
2022-02-09 21:21:17 +01:00
|
|
|
// removing note context is async process which can take some time, if users presses CTRL-W quickly, two
|
|
|
|
// close events could interleave which would then lead to attempting to activate already removed context.
|
2022-06-29 22:38:35 +02:00
|
|
|
return await this.mutex.runExclusively(async () => {
|
|
|
|
let noteContextToRemove;
|
|
|
|
|
|
|
|
try {
|
|
|
|
noteContextToRemove = this.getNoteContextById(ntxId);
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
// note context not found
|
|
|
|
return false;
|
|
|
|
}
|
2022-02-09 21:21:17 +01:00
|
|
|
|
|
|
|
if (noteContextToRemove.isMainContext()) {
|
|
|
|
const mainNoteContexts = this.getNoteContexts().filter(nc => nc.isMainContext());
|
|
|
|
|
|
|
|
if (mainNoteContexts.length === 1) {
|
2022-12-10 14:35:58 +01:00
|
|
|
if (noteContextToRemove.isEmpty()) {
|
|
|
|
// this is already the empty note context, no point in closing it and replacing with another
|
|
|
|
// empty tab
|
|
|
|
return false;
|
|
|
|
}
|
2022-06-02 22:28:25 +02:00
|
|
|
|
2022-12-10 14:35:58 +01:00
|
|
|
await this.openEmptyTab();
|
2022-02-09 21:21:17 +01:00
|
|
|
}
|
2021-10-28 22:27:21 +02:00
|
|
|
}
|
2020-05-22 19:30:21 +02:00
|
|
|
|
2022-02-09 21:21:17 +01:00
|
|
|
// close dangling autocompletes after closing the tab
|
|
|
|
$(".aa-input").autocomplete("close");
|
2021-05-20 23:13:34 +02:00
|
|
|
|
2022-02-09 21:21:17 +01:00
|
|
|
const noteContextsToRemove = noteContextToRemove.getSubContexts();
|
|
|
|
const ntxIdsToRemove = noteContextsToRemove.map(nc => nc.ntxId);
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2022-06-02 22:28:25 +02:00
|
|
|
await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove });
|
2020-03-07 13:57:31 +01:00
|
|
|
|
2022-02-09 21:21:17 +01:00
|
|
|
if (!noteContextToRemove.isMainContext()) {
|
2023-04-13 21:16:10 +08:00
|
|
|
const siblings = noteContextToRemove.getMainContext().getSubContexts();
|
|
|
|
const idx = siblings.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId);
|
|
|
|
const contextToActivateIdx = idx === siblings.length - 1 ? idx - 1 : idx + 1;
|
|
|
|
const contextToActivate = siblings[contextToActivateIdx];
|
|
|
|
|
|
|
|
await this.activateNoteContext(contextToActivate.ntxId);
|
2020-03-07 13:57:31 +01:00
|
|
|
}
|
2022-02-09 21:21:17 +01:00
|
|
|
else if (this.mainNoteContexts.length <= 1) {
|
|
|
|
await this.openAndActivateEmptyTab();
|
|
|
|
}
|
|
|
|
else if (ntxIdsToRemove.includes(this.activeNtxId)) {
|
|
|
|
const idx = this.mainNoteContexts.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId);
|
|
|
|
|
|
|
|
if (idx === this.mainNoteContexts.length - 1) {
|
|
|
|
await this.activatePreviousTabCommand();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
await this.activateNextTabCommand();
|
|
|
|
}
|
2020-03-07 13:57:31 +01:00
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2022-06-02 22:28:25 +02:00
|
|
|
this.removeNoteContexts(noteContextsToRemove);
|
2022-06-29 22:38:35 +02:00
|
|
|
|
|
|
|
return true;
|
2022-06-02 22:28:25 +02:00
|
|
|
});
|
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2022-06-02 22:28:25 +02:00
|
|
|
removeNoteContexts(noteContextsToRemove) {
|
|
|
|
const ntxIdsToRemove = noteContextsToRemove.map(nc => nc.ntxId);
|
|
|
|
|
|
|
|
this.children = this.children.filter(nc => !ntxIdsToRemove.includes(nc.ntxId));
|
|
|
|
|
2022-12-10 14:35:58 +01:00
|
|
|
this.addToRecentlyClosedTabs(noteContextsToRemove);
|
2022-06-02 22:28:25 +02:00
|
|
|
|
|
|
|
this.triggerEvent('noteContextRemoved', {ntxIds: ntxIdsToRemove});
|
|
|
|
|
|
|
|
this.tabsUpdate.scheduleUpdate();
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2022-12-10 14:35:58 +01:00
|
|
|
addToRecentlyClosedTabs(noteContexts) {
|
|
|
|
if (noteContexts.length === 1 && noteContexts[0].isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-24 13:15:19 +01:00
|
|
|
this.recentlyClosedTabs.push(noteContexts);
|
2022-12-10 14:35:58 +01:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
tabReorderEvent({ntxIdsInOrder}) {
|
2020-02-07 21:08:55 +01:00
|
|
|
const order = {};
|
|
|
|
|
2021-05-24 21:05:44 +02:00
|
|
|
let i = 0;
|
|
|
|
|
2021-05-24 21:43:24 +02:00
|
|
|
for (const ntxId of ntxIdsInOrder) {
|
|
|
|
for (const noteContext of this.getNoteContextById(ntxId).getSubContexts()) {
|
2021-05-24 21:05:44 +02:00
|
|
|
order[noteContext.ntxId] = i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.children.sort((a, b) => order[a.ntxId] < order[b.ntxId] ? -1 : 1);
|
|
|
|
|
|
|
|
this.tabsUpdate.scheduleUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
noteContextReorderEvent({ntxIdsInOrder}) {
|
|
|
|
const order = {};
|
|
|
|
let i = 0;
|
|
|
|
|
2021-10-27 21:32:03 +02:00
|
|
|
for (const ntxId of ntxIdsInOrder) {
|
2021-05-24 21:05:44 +02:00
|
|
|
order[ntxId] = i++;
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-05-22 12:26:45 +02:00
|
|
|
this.children.sort((a, b) => order[a.ntxId] < order[b.ntxId] ? -1 : 1);
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2020-02-08 21:23:42 +01:00
|
|
|
this.tabsUpdate.scheduleUpdate();
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-07-05 15:50:21 +02:00
|
|
|
async activateNextTabCommand() {
|
2021-07-24 22:51:12 +02:00
|
|
|
const activeMainNtxId = this.getActiveMainContext().ntxId;
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2021-07-24 22:51:12 +02:00
|
|
|
const oldIdx = this.mainNoteContexts.findIndex(nc => nc.ntxId === activeMainNtxId);
|
|
|
|
const newActiveNtxId = this.mainNoteContexts[oldIdx === this.mainNoteContexts.length - 1 ? 0 : oldIdx + 1].ntxId;
|
2021-07-20 13:29:11 +02:00
|
|
|
|
|
|
|
await this.activateNoteContext(newActiveNtxId);
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-07-05 15:50:21 +02:00
|
|
|
async activatePreviousTabCommand() {
|
2021-07-24 22:51:12 +02:00
|
|
|
const activeMainNtxId = this.getActiveMainContext().ntxId;
|
2021-07-20 13:29:11 +02:00
|
|
|
|
2021-07-24 22:51:12 +02:00
|
|
|
const oldIdx = this.mainNoteContexts.findIndex(nc => nc.ntxId === activeMainNtxId);
|
|
|
|
const newActiveNtxId = this.mainNoteContexts[oldIdx === 0 ? this.mainNoteContexts.length - 1 : oldIdx - 1].ntxId;
|
2020-02-07 21:08:55 +01:00
|
|
|
|
2021-07-20 13:29:11 +02:00
|
|
|
await this.activateNoteContext(newActiveNtxId);
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-07-05 15:50:21 +02:00
|
|
|
async closeActiveTabCommand() {
|
|
|
|
await this.removeNoteContext(this.activeNtxId);
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2020-02-16 19:23:49 +01:00
|
|
|
beforeUnloadEvent() {
|
2020-02-08 20:53:07 +01:00
|
|
|
this.tabsUpdate.updateNowIfNecessary();
|
2021-02-27 23:39:02 +01:00
|
|
|
|
|
|
|
return true; // don't block closing the tab, this metadata is not that important
|
2020-02-08 20:53:07 +01:00
|
|
|
}
|
|
|
|
|
2020-02-16 19:54:11 +01:00
|
|
|
openNewTabCommand() {
|
2020-02-07 21:08:55 +01:00
|
|
|
this.openAndActivateEmptyTab();
|
|
|
|
}
|
|
|
|
|
2023-01-08 13:58:51 +01:00
|
|
|
async closeAllTabsCommand() {
|
2021-10-03 23:01:22 +02:00
|
|
|
for (const ntxIdToRemove of this.mainNoteContexts.map(nc => nc.ntxId)) {
|
2021-05-22 12:35:41 +02:00
|
|
|
await this.removeNoteContext(ntxIdToRemove);
|
2020-02-09 21:13:05 +01:00
|
|
|
}
|
2020-02-07 21:08:55 +01:00
|
|
|
}
|
|
|
|
|
2023-01-08 13:58:51 +01:00
|
|
|
async closeOtherTabsCommand({ntxId}) {
|
2021-10-03 23:01:22 +02:00
|
|
|
for (const ntxIdToRemove of this.mainNoteContexts.map(nc => nc.ntxId)) {
|
2021-05-22 12:26:45 +02:00
|
|
|
if (ntxIdToRemove !== ntxId) {
|
2021-05-22 12:35:41 +02:00
|
|
|
await this.removeNoteContext(ntxIdToRemove);
|
2020-02-09 21:13:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-08 13:58:51 +01:00
|
|
|
async closeTabCommand({ntxId}) {
|
|
|
|
await this.removeNoteContext(ntxId);
|
|
|
|
}
|
|
|
|
|
2022-06-29 22:38:35 +02:00
|
|
|
async moveTabToNewWindowCommand({ntxId}) {
|
2021-05-22 12:26:45 +02:00
|
|
|
const {notePath, hoistedNoteId} = this.getNoteContextById(ntxId);
|
2020-04-26 23:11:52 +02:00
|
|
|
|
2022-06-29 22:38:35 +02:00
|
|
|
const removed = await this.removeNoteContext(ntxId);
|
2020-04-26 23:11:52 +02:00
|
|
|
|
2022-06-29 22:38:35 +02:00
|
|
|
if (removed) {
|
|
|
|
this.triggerCommand('openInWindow', {notePath, hoistedNoteId});
|
|
|
|
}
|
2020-04-26 23:11:52 +02:00
|
|
|
}
|
2020-11-24 22:32:22 +01:00
|
|
|
|
2021-10-09 21:20:12 +02:00
|
|
|
async reopenLastTabCommand() {
|
2022-12-10 14:35:58 +01:00
|
|
|
let closeLastEmptyTab = null;
|
|
|
|
|
|
|
|
await this.mutex.runExclusively(async () => {
|
|
|
|
if (this.recentlyClosedTabs.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.noteContexts.length === 1 && this.noteContexts[0].isEmpty()) {
|
|
|
|
// new empty tab is created after closing the last tab, this reverses the empty tab creation
|
|
|
|
closeLastEmptyTab = this.noteContexts[0];
|
|
|
|
}
|
|
|
|
|
2021-10-09 22:03:24 +02:00
|
|
|
const noteContexts = this.recentlyClosedTabs.pop();
|
|
|
|
|
|
|
|
for (const noteContext of noteContexts) {
|
|
|
|
this.child(noteContext);
|
|
|
|
|
|
|
|
await this.triggerEvent('newNoteContextCreated', {noteContext});
|
|
|
|
}
|
|
|
|
|
|
|
|
const noteContextToActivate = noteContexts.length === 1
|
|
|
|
? noteContexts[0]
|
|
|
|
: noteContexts.find(nc => nc.isMainContext());
|
|
|
|
|
2022-12-10 14:35:58 +01:00
|
|
|
await this.activateNoteContext(noteContextToActivate.ntxId);
|
2021-10-09 21:20:12 +02:00
|
|
|
|
|
|
|
await this.triggerEvent('noteSwitched', {
|
2021-10-09 22:03:24 +02:00
|
|
|
noteContext: noteContextToActivate,
|
|
|
|
notePath: noteContextToActivate.notePath
|
2021-10-09 21:20:12 +02:00
|
|
|
});
|
2022-12-10 14:35:58 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (closeLastEmptyTab) {
|
|
|
|
await this.removeNoteContext(closeLastEmptyTab.ntxId);
|
2021-10-09 21:20:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 22:32:22 +01:00
|
|
|
hoistedNoteChangedEvent() {
|
|
|
|
this.tabsUpdate.scheduleUpdate();
|
|
|
|
}
|
2022-01-03 19:52:24 +01:00
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
async updateDocumentTitle(activeNoteContext) {
|
2022-01-03 19:52:24 +01:00
|
|
|
const titleFragments = [
|
2022-07-05 22:40:41 +02:00
|
|
|
// it helps to navigate in history if note title is included in the title
|
2023-04-11 21:41:55 +02:00
|
|
|
await activeNoteContext.getNavigationTitle(),
|
2022-01-03 19:52:24 +01:00
|
|
|
"Trilium Notes"
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
document.title = titleFragments.join(" - ");
|
|
|
|
}
|
|
|
|
|
2023-04-11 21:41:55 +02:00
|
|
|
async entitiesReloadedEvent({loadResults}) {
|
2022-01-03 19:52:24 +01:00
|
|
|
const activeContext = this.getActiveContext();
|
|
|
|
|
|
|
|
if (activeContext && loadResults.isNoteReloaded(activeContext.noteId)) {
|
2023-04-11 21:41:55 +02:00
|
|
|
await this.updateDocumentTitle(activeContext);
|
2022-01-03 19:52:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-05-22 19:30:21 +02:00
|
|
|
}
|