mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
moved tab related stuff to tab manager from app context
This commit is contained in:
parent
6d912c4897
commit
14d6372bd8
@ -56,7 +56,7 @@ window.glob.loadIncludedNote = async (noteId, el) => {
|
||||
};
|
||||
// this is required by CKEditor when uploading images
|
||||
window.glob.noteChanged = () => {
|
||||
const activeTabContext = appContext.getActiveTabContext();
|
||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||
|
||||
if (activeTabContext) {
|
||||
activeTabContext.noteChanged();
|
||||
@ -65,7 +65,7 @@ window.glob.noteChanged = () => {
|
||||
window.glob.refreshTree = treeService.reload;
|
||||
|
||||
// required for ESLint plugin
|
||||
window.glob.getActiveTabNote = () => appContext.getActiveTabNote();
|
||||
window.glob.getActiveTabNote = () => appContext.tabManager.getActiveTabNote();
|
||||
window.glob.requireLibrary = libraryLoader.requireLibrary;
|
||||
window.glob.ESLINT = libraryLoader.ESLINT;
|
||||
|
||||
|
@ -92,7 +92,7 @@ function AttributesModel() {
|
||||
}
|
||||
|
||||
this.loadAttributes = async function() {
|
||||
const noteId = appContext.getActiveTabNoteId();
|
||||
const noteId = appContext.tabManager.getActiveTabNoteId();
|
||||
|
||||
const attributes = await server.get('notes/' + noteId + '/attributes');
|
||||
|
||||
@ -138,7 +138,7 @@ function AttributesModel() {
|
||||
|
||||
self.updateAttributePositions();
|
||||
|
||||
const noteId = appContext.getActiveTabNoteId();
|
||||
const noteId = appContext.tabManager.getActiveTabNoteId();
|
||||
|
||||
const attributesToSave = self.ownedAttributes()
|
||||
.map(attribute => attribute())
|
||||
|
@ -22,7 +22,7 @@ export async function showDialog() {
|
||||
return false;
|
||||
}
|
||||
|
||||
appContext.getActiveTabContext().setNote(suggestion.path);
|
||||
appContext.tabManager.getActiveTabContext().setNote(suggestion.path);
|
||||
});
|
||||
|
||||
noteAutocompleteService.showRecentNotes($autoComplete);
|
||||
|
@ -31,7 +31,7 @@ export async function showDialog() {
|
||||
}
|
||||
|
||||
$dialog.on('shown.bs.modal', () => {
|
||||
const note = appContext.getActiveTabNote();
|
||||
const note = appContext.tabManager.getActiveTabNote();
|
||||
|
||||
linkMapService = new LinkMapService(note, $linkMapContainer, getOptions());
|
||||
linkMapService.render();
|
||||
|
@ -29,7 +29,7 @@ async function convertMarkdownToHtml(text) {
|
||||
}
|
||||
|
||||
export async function importMarkdownInline() {
|
||||
if (appContext.getActiveTabNoteType() !== 'text') {
|
||||
if (appContext.tabManager.getActiveTabNoteType() !== 'text') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ export async function showDialog() {
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
const activeTabContext = appContext.getActiveTabContext();
|
||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||
const {note} = activeTabContext;
|
||||
const noteComplement = await activeTabContext.getNoteComplement();
|
||||
|
||||
|
@ -25,7 +25,7 @@ let note;
|
||||
let noteRevisionId;
|
||||
|
||||
export async function showCurrentNoteRevisions() {
|
||||
await showNoteRevisionsDialog(appContext.getActiveTabNoteId());
|
||||
await showNoteRevisionsDialog(appContext.tabManager.getActiveTabNoteId());
|
||||
}
|
||||
|
||||
export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
|
||||
@ -42,7 +42,7 @@ async function loadNoteRevisions(noteId, noteRevId) {
|
||||
$list.empty();
|
||||
$content.empty();
|
||||
|
||||
note = appContext.getActiveTabNote();
|
||||
note = appContext.tabManager.getActiveTabNote();
|
||||
revisionItems = await server.get(`notes/${noteId}/revisions`);
|
||||
|
||||
for (const item of revisionItems) {
|
||||
|
@ -11,7 +11,7 @@ export function showDialog() {
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
const noteText = appContext.getActiveTabNote().content;
|
||||
const noteText = appContext.tabManager.getActiveTabNote().content;
|
||||
|
||||
$noteSource.text(formatHtml(noteText));
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export async function showDialog() {
|
||||
|
||||
await treeCache.reloadNotes([change.noteId]);
|
||||
|
||||
appContext.getActiveTabContext().setNote(change.noteId);
|
||||
appContext.tabManager.getActiveTabContext().setNote(change.noteId);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import TabContext from "./tab_context.js";
|
||||
import server from "./server.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import bundleService from "./bundle.js";
|
||||
@ -6,28 +5,15 @@ import DialogEventComponent from "./dialog_events.js";
|
||||
import Entrypoints from "./entrypoints.js";
|
||||
import options from "./options.js";
|
||||
import utils from "./utils.js";
|
||||
import treeService from "./tree.js";
|
||||
import ZoomService from "./zoom.js";
|
||||
import Layout from "../widgets/layout.js";
|
||||
import SpacedUpdate from "./spaced_update.js";
|
||||
import TabManager from "./tab_manager.js";
|
||||
|
||||
class AppContext {
|
||||
constructor(layout) {
|
||||
this.layout = layout;
|
||||
this.components = [];
|
||||
/** @type {TabContext[]} */
|
||||
this.tabContexts = [];
|
||||
this.activeTabId = null;
|
||||
|
||||
this.tabsUpdate = new SpacedUpdate(async () => {
|
||||
const openTabs = this.tabContexts
|
||||
.map(tc => tc.getTabState())
|
||||
.filter(t => !!t);
|
||||
|
||||
await server.put('options', {
|
||||
openTabs: JSON.stringify(openTabs)
|
||||
});
|
||||
});
|
||||
this.tabManager = new TabManager(this);
|
||||
this.components = [this.tabManager];
|
||||
}
|
||||
|
||||
async start() {
|
||||
@ -35,80 +21,11 @@ class AppContext {
|
||||
|
||||
this.showWidgets();
|
||||
|
||||
this.loadTabs();
|
||||
this.tabManager.loadTabs();
|
||||
|
||||
bundleService.executeStartupBundles();
|
||||
}
|
||||
|
||||
async loadTabs() {
|
||||
const openTabs = options.getJson('openTabs') || [];
|
||||
|
||||
await treeCache.initializedPromise;
|
||||
|
||||
// if there's notePath in the URL, make sure it's open and active
|
||||
// (useful, among others, for opening clipped notes from clipper)
|
||||
if (window.location.hash) {
|
||||
const notePath = window.location.hash.substr(1);
|
||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||
|
||||
if (noteId && await treeCache.noteExists(noteId)) {
|
||||
for (const tab of openTabs) {
|
||||
tab.active = false;
|
||||
}
|
||||
|
||||
const foundTab = openTabs.find(tab => noteId === treeService.getNoteIdFromNotePath(tab.notePath));
|
||||
|
||||
if (foundTab) {
|
||||
foundTab.active = true;
|
||||
}
|
||||
else {
|
||||
openTabs.push({
|
||||
notePath: notePath,
|
||||
active: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filteredTabs = [];
|
||||
|
||||
for (const openTab of openTabs) {
|
||||
const noteId = treeService.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 (utils.isMobile()) {
|
||||
// mobile frontend doesn't have tabs so show only the active tab
|
||||
filteredTabs = filteredTabs.filter(tab => tab.active);
|
||||
}
|
||||
|
||||
if (filteredTabs.length === 0) {
|
||||
filteredTabs.push({
|
||||
notePath: 'root',
|
||||
active: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!filteredTabs.find(tab => tab.active)) {
|
||||
filteredTabs[0].active = true;
|
||||
}
|
||||
|
||||
this.tabsUpdate.allowUpdateWithoutChange(() => {
|
||||
for (const tab of filteredTabs) {
|
||||
const tabContext = this.openEmptyTab();
|
||||
tabContext.setNote(tab.notePath);
|
||||
|
||||
if (tab.active) {
|
||||
this.activateTab(tabContext.tabId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showWidgets() {
|
||||
const rootContainer = this.layout.getRootWidget(this);
|
||||
|
||||
@ -145,210 +62,20 @@ class AppContext {
|
||||
}
|
||||
}
|
||||
|
||||
tabNoteSwitchedListener({tabId}) {
|
||||
if (tabId === this.activeTabId) {
|
||||
this._setCurrentNotePathToHash();
|
||||
}
|
||||
}
|
||||
|
||||
_setCurrentNotePathToHash() {
|
||||
const activeTabContext = this.getActiveTabContext();
|
||||
|
||||
if (activeTabContext && activeTabContext.notePath) {
|
||||
document.location.hash = (activeTabContext.notePath || "") + "-" + activeTabContext.tabId;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {TabContext[]} */
|
||||
getTabContexts() {
|
||||
return this.tabContexts;
|
||||
}
|
||||
|
||||
/** @returns {TabContext} */
|
||||
getTabContextById(tabId) {
|
||||
return this.tabContexts.find(tc => tc.tabId === tabId);
|
||||
}
|
||||
|
||||
/** @returns {TabContext} */
|
||||
getActiveTabContext() {
|
||||
return this.getTabContextById(this.activeTabId);
|
||||
}
|
||||
|
||||
/** @returns {string|null} */
|
||||
getActiveTabNotePath() {
|
||||
const activeContext = this.getActiveTabContext();
|
||||
return activeContext ? activeContext.notePath : null;
|
||||
}
|
||||
|
||||
/** @return {NoteShort} */
|
||||
getActiveTabNote() {
|
||||
const activeContext = this.getActiveTabContext();
|
||||
return activeContext ? activeContext.note : null;
|
||||
}
|
||||
|
||||
/** @return {string|null} */
|
||||
getActiveTabNoteId() {
|
||||
const activeNote = this.getActiveTabNote();
|
||||
|
||||
return activeNote ? activeNote.noteId : null;
|
||||
}
|
||||
|
||||
/** @return {string|null} */
|
||||
getActiveTabNoteType() {
|
||||
const activeNote = this.getActiveTabNote();
|
||||
|
||||
return activeNote ? activeNote.type : null;
|
||||
}
|
||||
|
||||
async switchToTab(tabId, notePath) {
|
||||
const tabContext = this.tabContexts.find(tc => tc.tabId === tabId)
|
||||
|| this.openEmptyTab();
|
||||
|
||||
this.activateTab(tabContext.tabId);
|
||||
await tabContext.setNote(notePath);
|
||||
}
|
||||
|
||||
getTab(newTab, state) {
|
||||
if (!this.getActiveTabContext() || newTab) {
|
||||
// if it's a new tab explicitly by user then it's in background
|
||||
const ctx = new TabContext(this, state);
|
||||
this.tabContexts.push(ctx);
|
||||
this.components.push(ctx);
|
||||
|
||||
return ctx;
|
||||
} else {
|
||||
return this.getActiveTabContext();
|
||||
}
|
||||
}
|
||||
|
||||
async openAndActivateEmptyTab() {
|
||||
const tabContext = this.openEmptyTab();
|
||||
|
||||
await this.activateTab(tabContext.tabId);
|
||||
}
|
||||
|
||||
openEmptyTab() {
|
||||
const tabContext = new TabContext(this);
|
||||
this.tabContexts.push(tabContext);
|
||||
this.components.push(tabContext);
|
||||
return tabContext;
|
||||
}
|
||||
|
||||
async activateOrOpenNote(noteId) {
|
||||
for (const tabContext of this.getTabContexts()) {
|
||||
if (tabContext.note && tabContext.note.noteId === noteId) {
|
||||
await tabContext.activate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if no tab with this note has been found we'll create new tab
|
||||
|
||||
const tabContext = this.openEmptyTab();
|
||||
await tabContext.setNote(noteId);
|
||||
}
|
||||
|
||||
hoistedNoteChangedListener({hoistedNoteId}) {
|
||||
if (hoistedNoteId === 'root') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const tc of this.tabContexts) {
|
||||
for (const tc of this.tabManager.getTabContexts()) {
|
||||
if (tc.notePath && !tc.notePath.split("/").includes(hoistedNoteId)) {
|
||||
this.removeTab(tc.tabId);
|
||||
this.tabManager.removeTab(tc.tabId);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tabContexts.length === 0) {
|
||||
this.openAndActivateEmptyTab();
|
||||
if (this.tabManager.getTabContexts().length === 0) {
|
||||
this.tabManager.openAndActivateEmptyTab();
|
||||
}
|
||||
|
||||
this.saveOpenTabs();
|
||||
}
|
||||
|
||||
openTabsChangedListener() {
|
||||
this.tabsUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
activateTab(tabId) {
|
||||
if (tabId === this.activeTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldActiveTabId = this.activeTabId;
|
||||
|
||||
this.activeTabId = tabId;
|
||||
|
||||
this.trigger('activeTabChanged', { oldActiveTabId, newActiveTabId: tabId });
|
||||
}
|
||||
|
||||
newTabListener() {
|
||||
this.openAndActivateEmptyTab();
|
||||
}
|
||||
|
||||
async removeTab(tabId) {
|
||||
const tabContextToRemove = this.tabContexts.find(tc => tc.tabId === tabId);
|
||||
|
||||
if (!tabContextToRemove) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.trigger('beforeTabRemove', {tabId}, true);
|
||||
|
||||
if (this.tabContexts.length === 1) {
|
||||
this.openAndActivateEmptyTab();
|
||||
}
|
||||
else {
|
||||
this.activateNextTabListener();
|
||||
}
|
||||
|
||||
this.tabContexts = this.tabContexts.filter(tc => tc.tabId === tabId);
|
||||
|
||||
this.trigger('tabRemoved', {tabId});
|
||||
|
||||
this.openTabsChangedListener();
|
||||
}
|
||||
|
||||
tabReorderListener({tabIdsInOrder}) {
|
||||
const order = {};
|
||||
|
||||
for (const i in tabIdsInOrder) {
|
||||
order[tabIdsInOrder[i]] = i;
|
||||
}
|
||||
|
||||
this.tabContexts.sort((a, b) => order[a.tabId] < order[b.tabId] ? -1 : 1);
|
||||
|
||||
this.openTabsChangedListener();
|
||||
}
|
||||
|
||||
activateNextTabListener() {
|
||||
const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId);
|
||||
const newActiveTabId = this.tabContexts[oldIdx === this.tabContexts.length - 1 ? 0 : oldIdx + 1].tabId;
|
||||
|
||||
this.activateTab(newActiveTabId);
|
||||
}
|
||||
|
||||
activatePreviousTabListener() {
|
||||
const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId);
|
||||
const newActiveTabId = this.tabContexts[oldIdx === 0 ? this.tabContexts.length - 1 : oldIdx - 1].tabId;
|
||||
|
||||
this.activateTab(newActiveTabId);
|
||||
}
|
||||
|
||||
closeActiveTabListener() {
|
||||
this.removeTab(this.activeTabId);
|
||||
}
|
||||
|
||||
openNewTabListener() {
|
||||
this.openAndActivateEmptyTab();
|
||||
}
|
||||
|
||||
removeAllTabsListener() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
removeAllTabsExceptForThis() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
async protectedSessionStartedListener() {
|
||||
|
@ -71,14 +71,14 @@ export default class Entrypoints extends Component {
|
||||
await treeService.expandToNote(note.noteId);
|
||||
|
||||
const tabContext = appContext.openEmptyTab();
|
||||
appContext.activateTab(tabContext.tabId);
|
||||
appContext.tabManager.activateTab(tabContext.tabId);
|
||||
await tabContext.setNote(note.noteId);
|
||||
|
||||
appContext.trigger('focusAndSelectTitle');
|
||||
}
|
||||
|
||||
toggleNoteHoistingListener() {
|
||||
const note = appContext.getActiveTabNote();
|
||||
const note = appContext.tabManager.getActiveTabNote();
|
||||
|
||||
hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => {
|
||||
if (note.noteId === hoistedNoteId) {
|
||||
|
@ -48,7 +48,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
this.activateNote = async notePath => {
|
||||
await appContext.getActiveTabContext().setNote(notePath);
|
||||
await appContext.tabManager.getActiveTabContext().setNote(notePath);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -60,7 +60,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
this.activateNewNote = async notePath => {
|
||||
await ws.waitForMaxKnownSyncId();
|
||||
|
||||
await appContext.getActiveTabContext().setNote(notePath);
|
||||
await appContext.tabManager.getActiveTabContext().setNote(notePath);
|
||||
appContext.trigger('focusAndSelectTitle');
|
||||
};
|
||||
|
||||
@ -285,7 +285,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @method
|
||||
* @returns {NoteShort} active note (loaded into right pane)
|
||||
*/
|
||||
this.getActiveTabNote = appContext.getActiveTabNote;
|
||||
this.getActiveTabNote = appContext.tabManager.getActiveTabNote;
|
||||
|
||||
/**
|
||||
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
|
||||
@ -299,7 +299,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @method
|
||||
* @returns {Promise<string|null>} returns note path of active note or null if there isn't active note
|
||||
*/
|
||||
this.getActiveTabNotePath = appContext.getActiveTabNotePath;
|
||||
this.getActiveTabNotePath = appContext.tabManager.getActiveTabNotePath;
|
||||
|
||||
/**
|
||||
* This method checks whether user navigated away from the note from which the scripts has been started.
|
||||
|
@ -65,7 +65,7 @@ ws.subscribeToMessages(async message => {
|
||||
toastService.showPersistent(toast);
|
||||
|
||||
if (message.result.importedNoteId) {
|
||||
await appContext.getActiveTabContext.setNote(message.result.importedNoteId);
|
||||
await appContext.tabManager.getActiveTabContext.setNote(message.result.importedNoteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => {
|
||||
utils.bindGlobalShortcut(shortcut, async () => {
|
||||
const treeService = (await import("./tree.js")).default;
|
||||
|
||||
appContext.getActiveTabContext().setNote(shortcutForNotes[shortcut]);
|
||||
appContext.tabManager.getActiveTabContext().setNote(shortcutForNotes[shortcut]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -78,11 +78,11 @@ function goToLink(e) {
|
||||
if (notePath) {
|
||||
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
|
||||
const tabContext = appContext.openEmptyTab();
|
||||
appContext.activateTab(tabContext.tabId);
|
||||
appContext.tabManager.activateTab(tabContext.tabId);
|
||||
tabContext.setNote(notePath);
|
||||
}
|
||||
else if (e.which === 1) {
|
||||
const activeTabContext = appContext.getActiveTabContext();
|
||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||
activeTabContext.setNote(notePath)
|
||||
}
|
||||
else {
|
||||
@ -121,7 +121,7 @@ function newTabContextMenu(e) {
|
||||
if (cmd === 'openNoteInNewTab') {
|
||||
const tabContext = appContext.openEmptyTab();
|
||||
tabContext.setNote(notePath);
|
||||
appContext.activateTab(tabContext.tabId);
|
||||
appContext.tabManager.activateTab(tabContext.tabId);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -143,7 +143,7 @@ $(document).on('mousedown', '.note-detail-text a', function (e) {
|
||||
if (notePath) {
|
||||
const tabContext = appContext.openEmptyTab();
|
||||
tabContext.setNote(notePath);
|
||||
appContext.activateTab(tabContext.tabId);
|
||||
appContext.tabManager.activateTab(tabContext.tabId);
|
||||
}
|
||||
else {
|
||||
const address = $link.attr('href');
|
||||
|
@ -8,7 +8,7 @@ const SELECTED_PATH_KEY = "data-note-path";
|
||||
async function autocompleteSource(term, cb) {
|
||||
const result = await server.get('autocomplete'
|
||||
+ '?query=' + encodeURIComponent(term)
|
||||
+ '&activeNoteId=' + appContext.getActiveTabNoteId());
|
||||
+ '&activeNoteId=' + appContext.tabManager.getActiveTabNoteId());
|
||||
|
||||
if (result.length === 0) {
|
||||
result.push({
|
||||
|
@ -25,7 +25,7 @@ async function createNote(parentNoteId, options = {}) {
|
||||
options.isProtected = false;
|
||||
}
|
||||
|
||||
if (appContext.getActiveTabNoteType() !== 'text') {
|
||||
if (appContext.tabManager.getActiveTabNoteType() !== 'text') {
|
||||
options.saveSelection = false;
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ async function createNote(parentNoteId, options = {}) {
|
||||
}
|
||||
|
||||
if (options.activate) {
|
||||
const activeTabContext = appContext.getActiveTabContext();
|
||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||
activeTabContext.setNote(note.noteId);
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ async function duplicateNote(noteId, parentNoteId) {
|
||||
|
||||
await ws.waitForMaxKnownSyncId();
|
||||
|
||||
await appContext.activateOrOpenNote(note.noteId);
|
||||
await appContext.tabManager.activateOrOpenNote(note.noteId);
|
||||
|
||||
const origNote = await treeCache.getNote(noteId);
|
||||
toastService.showMessage(`Note "${origNote.title}" has been duplicated`);
|
||||
|
@ -69,20 +69,20 @@ async function enterProtectedSessionOnServer(password) {
|
||||
}
|
||||
|
||||
async function protectNoteAndSendToServer() {
|
||||
if (!appContext.getActiveTabNote() || appContext.getActiveTabNote().isProtected) {
|
||||
if (!appContext.tabManager.getActiveTabNote() || appContext.tabManager.getActiveTabNote().isProtected) {
|
||||
return;
|
||||
}
|
||||
|
||||
await enterProtectedSession();
|
||||
|
||||
const note = appContext.getActiveTabNote();
|
||||
const note = appContext.tabManager.getActiveTabNote();
|
||||
note.isProtected = true;
|
||||
|
||||
await appContext.getActiveTabContext().saveNote();
|
||||
await appContext.tabManager.getActiveTabContext().saveNote();
|
||||
}
|
||||
|
||||
async function unprotectNoteAndSendToServer() {
|
||||
const activeNote = appContext.getActiveTabNote();
|
||||
const activeNote = appContext.tabManager.getActiveTabNote();
|
||||
|
||||
if (!activeNote.isProtected) {
|
||||
toastService.showAndLogError(`Note ${activeNote.noteId} is not protected`);
|
||||
@ -101,7 +101,7 @@ async function unprotectNoteAndSendToServer() {
|
||||
|
||||
activeNote.isProtected = false;
|
||||
|
||||
await appContext.getActiveTabContext().saveNote();
|
||||
await appContext.tabManager.getActiveTabContext().saveNote();
|
||||
}
|
||||
|
||||
async function protectSubtree(noteId, protect) {
|
||||
|
272
src/public/javascripts/services/tab_manager.js
Normal file
272
src/public/javascripts/services/tab_manager.js
Normal file
@ -0,0 +1,272 @@
|
||||
import Component from "../widgets/component.js";
|
||||
import SpacedUpdate from "./spaced_update.js";
|
||||
import server from "./server.js";
|
||||
import options from "./options.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import treeService from "./tree.js";
|
||||
import utils from "./utils.js";
|
||||
import TabContext from "./tab_context.js";
|
||||
|
||||
export default class TabManager extends Component {
|
||||
constructor(appContext) {
|
||||
super(appContext);
|
||||
|
||||
/** @type {TabContext[]} */
|
||||
this.tabContexts = [];
|
||||
this.activeTabId = null;
|
||||
|
||||
this.tabsUpdate = new SpacedUpdate(async () => {
|
||||
const openTabs = this.tabContexts
|
||||
.map(tc => tc.getTabState())
|
||||
.filter(t => !!t);
|
||||
|
||||
await server.put('options', {
|
||||
openTabs: JSON.stringify(openTabs)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async loadTabs() {
|
||||
const openTabs = options.getJson('openTabs') || [];
|
||||
|
||||
await treeCache.initializedPromise;
|
||||
|
||||
// if there's notePath in the URL, make sure it's open and active
|
||||
// (useful, among others, for opening clipped notes from clipper)
|
||||
if (window.location.hash) {
|
||||
const notePath = window.location.hash.substr(1);
|
||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||
|
||||
if (noteId && await treeCache.noteExists(noteId)) {
|
||||
for (const tab of openTabs) {
|
||||
tab.active = false;
|
||||
}
|
||||
|
||||
const foundTab = openTabs.find(tab => noteId === treeService.getNoteIdFromNotePath(tab.notePath));
|
||||
|
||||
if (foundTab) {
|
||||
foundTab.active = true;
|
||||
}
|
||||
else {
|
||||
openTabs.push({
|
||||
notePath: notePath,
|
||||
active: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filteredTabs = [];
|
||||
|
||||
for (const openTab of openTabs) {
|
||||
const noteId = treeService.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 (utils.isMobile()) {
|
||||
// mobile frontend doesn't have tabs so show only the active tab
|
||||
filteredTabs = filteredTabs.filter(tab => tab.active);
|
||||
}
|
||||
|
||||
if (filteredTabs.length === 0) {
|
||||
filteredTabs.push({
|
||||
notePath: 'root',
|
||||
active: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!filteredTabs.find(tab => tab.active)) {
|
||||
filteredTabs[0].active = true;
|
||||
}
|
||||
|
||||
this.tabsUpdate.allowUpdateWithoutChange(() => {
|
||||
for (const tab of filteredTabs) {
|
||||
const tabContext = this.openEmptyTab();
|
||||
tabContext.setNote(tab.notePath);
|
||||
|
||||
if (tab.active) {
|
||||
this.activateTab(tabContext.tabId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tabNoteSwitchedListener({tabId}) {
|
||||
if (tabId === this.activeTabId) {
|
||||
this._setCurrentNotePathToHash();
|
||||
}
|
||||
}
|
||||
|
||||
_setCurrentNotePathToHash() {
|
||||
const activeTabContext = this.getActiveTabContext();
|
||||
|
||||
if (activeTabContext && activeTabContext.notePath) {
|
||||
document.location.hash = (activeTabContext.notePath || "") + "-" + activeTabContext.tabId;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {TabContext[]} */
|
||||
getTabContexts() {
|
||||
return this.tabContexts;
|
||||
}
|
||||
|
||||
/** @returns {TabContext} */
|
||||
getTabContextById(tabId) {
|
||||
return this.tabContexts.find(tc => tc.tabId === tabId);
|
||||
}
|
||||
|
||||
/** @returns {TabContext} */
|
||||
getActiveTabContext() {
|
||||
return this.getTabContextById(this.activeTabId);
|
||||
}
|
||||
|
||||
/** @returns {string|null} */
|
||||
getActiveTabNotePath() {
|
||||
const activeContext = this.getActiveTabContext();
|
||||
return activeContext ? activeContext.notePath : null;
|
||||
}
|
||||
|
||||
/** @return {NoteShort} */
|
||||
getActiveTabNote() {
|
||||
const activeContext = this.getActiveTabContext();
|
||||
return activeContext ? activeContext.note : null;
|
||||
}
|
||||
|
||||
/** @return {string|null} */
|
||||
getActiveTabNoteId() {
|
||||
const activeNote = this.getActiveTabNote();
|
||||
|
||||
return activeNote ? activeNote.noteId : null;
|
||||
}
|
||||
|
||||
/** @return {string|null} */
|
||||
getActiveTabNoteType() {
|
||||
const activeNote = this.getActiveTabNote();
|
||||
|
||||
return activeNote ? activeNote.type : null;
|
||||
}
|
||||
|
||||
async switchToTab(tabId, notePath) {
|
||||
const tabContext = this.tabContexts.find(tc => tc.tabId === tabId)
|
||||
|| this.openEmptyTab();
|
||||
|
||||
this.activateTab(tabContext.tabId);
|
||||
await tabContext.setNote(notePath);
|
||||
}
|
||||
|
||||
async openAndActivateEmptyTab() {
|
||||
const tabContext = this.openEmptyTab();
|
||||
|
||||
await this.activateTab(tabContext.tabId);
|
||||
}
|
||||
|
||||
openEmptyTab() {
|
||||
const tabContext = new TabContext(this.appContext);
|
||||
this.tabContexts.push(tabContext);
|
||||
this.children.push(tabContext);
|
||||
return tabContext;
|
||||
}
|
||||
|
||||
async activateOrOpenNote(noteId) {
|
||||
for (const tabContext of this.getTabContexts()) {
|
||||
if (tabContext.note && tabContext.note.noteId === noteId) {
|
||||
await tabContext.activate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if no tab with this note has been found we'll create new tab
|
||||
|
||||
const tabContext = this.openEmptyTab();
|
||||
await tabContext.setNote(noteId);
|
||||
}
|
||||
|
||||
openTabsChangedListener() {
|
||||
this.tabsUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
activateTab(tabId) {
|
||||
if (tabId === this.activeTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldActiveTabId = this.activeTabId;
|
||||
|
||||
this.activeTabId = tabId;
|
||||
|
||||
this.trigger('activeTabChanged', { oldActiveTabId, newActiveTabId: tabId });
|
||||
}
|
||||
|
||||
newTabListener() {
|
||||
this.openAndActivateEmptyTab();
|
||||
}
|
||||
|
||||
async removeTab(tabId) {
|
||||
const tabContextToRemove = this.tabContexts.find(tc => tc.tabId === tabId);
|
||||
|
||||
if (!tabContextToRemove) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.trigger('beforeTabRemove', {tabId}, true);
|
||||
|
||||
if (this.tabContexts.length === 1) {
|
||||
this.openAndActivateEmptyTab();
|
||||
}
|
||||
else {
|
||||
this.activateNextTabListener();
|
||||
}
|
||||
|
||||
this.children = this.tabContexts = this.tabContexts.filter(tc => tc.tabId === tabId);
|
||||
|
||||
this.trigger('tabRemoved', {tabId});
|
||||
|
||||
this.openTabsChangedListener();
|
||||
}
|
||||
|
||||
tabReorderListener({tabIdsInOrder}) {
|
||||
const order = {};
|
||||
|
||||
for (const i in tabIdsInOrder) {
|
||||
order[tabIdsInOrder[i]] = i;
|
||||
}
|
||||
|
||||
this.tabContexts.sort((a, b) => order[a.tabId] < order[b.tabId] ? -1 : 1);
|
||||
|
||||
this.openTabsChangedListener();
|
||||
}
|
||||
|
||||
activateNextTabListener() {
|
||||
const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId);
|
||||
const newActiveTabId = this.tabContexts[oldIdx === this.tabContexts.length - 1 ? 0 : oldIdx + 1].tabId;
|
||||
|
||||
this.activateTab(newActiveTabId);
|
||||
}
|
||||
|
||||
activatePreviousTabListener() {
|
||||
const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId);
|
||||
const newActiveTabId = this.tabContexts[oldIdx === 0 ? this.tabContexts.length - 1 : oldIdx - 1].tabId;
|
||||
|
||||
this.activateTab(newActiveTabId);
|
||||
}
|
||||
|
||||
closeActiveTabListener() {
|
||||
this.removeTab(this.activeTabId);
|
||||
}
|
||||
|
||||
openNewTabListener() {
|
||||
this.openAndActivateEmptyTab();
|
||||
}
|
||||
|
||||
removeAllTabsListener() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
removeAllTabsExceptForThis() {
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -139,7 +139,7 @@ async function sortAlphabetically(noteId) {
|
||||
|
||||
ws.subscribeToMessages(message => {
|
||||
if (message.type === 'open-note') {
|
||||
appContext.activateOrOpenNote(message.noteId);
|
||||
appContext.tabManager.activateOrOpenNote(message.noteId);
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const currentWindow = require("electron").remote.getCurrentWindow();
|
||||
|
@ -103,7 +103,7 @@ class TreeContextMenu {
|
||||
|
||||
if (cmd === 'openInTab') {
|
||||
const tabContext = appContext.openEmptyTab();
|
||||
appContext.activateTab(tabContext.tabId);
|
||||
appContext.tabManager.activateTab(tabContext.tabId);
|
||||
tabContext.setNote(notePath);
|
||||
}
|
||||
else if (cmd.startsWith("insertNoteAfter")) {
|
||||
|
@ -58,7 +58,7 @@ export default class CalendarWidget extends CollapsibleWidget {
|
||||
const note = await dateNoteService.getDateNote(date);
|
||||
|
||||
if (note) {
|
||||
appContext.getActiveTabContext().setNote(note.noteId);
|
||||
appContext.tabManager.getActiveTabContext().setNote(note.noteId);
|
||||
}
|
||||
else {
|
||||
alert("Cannot find day note");
|
||||
|
@ -54,7 +54,7 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
this.$widget.on("dragleave", e => e.preventDefault());
|
||||
|
||||
this.$widget.on("drop", async e => {
|
||||
const activeNote = this.appContext.getActiveTabNote();
|
||||
const activeNote = this.appContext.tabManager.getActiveTabNote();
|
||||
|
||||
if (!activeNote) {
|
||||
return;
|
||||
|
@ -89,7 +89,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
else if (event.ctrlKey) {
|
||||
const tabContext = appContext.openEmptyTab();
|
||||
treeService.getNotePath(node).then(notePath => tabContext.setNote(notePath));
|
||||
appContext.activateTab(tabContext.tabId);
|
||||
appContext.tabManager.activateTab(tabContext.tabId);
|
||||
}
|
||||
else {
|
||||
node.setActive();
|
||||
@ -106,7 +106,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
const notePath = await treeService.getNotePath(data.node);
|
||||
|
||||
const activeTabContext = this.appContext.getActiveTabContext();
|
||||
const activeTabContext = this.appContext.tabManager.getActiveTabContext();
|
||||
await activeTabContext.setNote(notePath);
|
||||
},
|
||||
expand: (event, data) => this.setExpandedToServer(data.node.data.branchId, true),
|
||||
@ -286,7 +286,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
async scrollToActiveNoteListener() {
|
||||
const activeContext = appContext.getActiveTabContext();
|
||||
const activeContext = appContext.tabManager.getActiveTabContext();
|
||||
|
||||
if (activeContext && activeContext.notePath) {
|
||||
this.tree.setFocus();
|
||||
@ -466,7 +466,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
const notePath = await treeService.getNotePath(newActive);
|
||||
|
||||
appContext.getActiveTabContext().setNote(notePath);
|
||||
appContext.tabManager.getActiveTabContext().setNote(notePath);
|
||||
}
|
||||
|
||||
node.remove();
|
||||
@ -526,7 +526,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
const activateNotePath = appContext.getActiveTabNotePath();
|
||||
const activateNotePath = appContext.tabManager.getActiveTabNotePath();
|
||||
|
||||
if (activateNotePath) {
|
||||
const node = await this.getNodeFromPath(activateNotePath);
|
||||
|
@ -168,7 +168,7 @@ export default class SearchBoxWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
searchInSubtreeListener({noteId}) {
|
||||
noteId = noteId || appContext.getActiveTabNoteId();
|
||||
noteId = noteId || appContext.tabManager.getActiveTabNoteId();
|
||||
|
||||
this.toggle(true);
|
||||
|
||||
|
@ -68,7 +68,7 @@ export default class TabAwareWidget extends BasicWidget {
|
||||
refreshWithNote(note, notePath) {}
|
||||
|
||||
activeTabChangedListener() {
|
||||
this.tabContext = this.appContext.getActiveTabContext();
|
||||
this.tabContext = this.appContext.tabManager.getActiveTabContext();
|
||||
|
||||
this.activeTabChanged();
|
||||
}
|
||||
@ -79,7 +79,7 @@ export default class TabAwareWidget extends BasicWidget {
|
||||
|
||||
lazyLoadedListener() {
|
||||
if (!this.tabContext) { // has not been loaded yet
|
||||
this.tabContext = this.appContext.getActiveTabContext();
|
||||
this.tabContext = this.appContext.tabManager.getActiveTabContext();
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
|
@ -490,7 +490,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
this.draggabillies.push(draggabilly);
|
||||
|
||||
draggabilly.on('pointerDown', _ => {
|
||||
this.appContext.activateTab(tabEl.getAttribute('data-tab-id'));
|
||||
this.appContext.tabManager.activateTab(tabEl.getAttribute('data-tab-id'));
|
||||
});
|
||||
|
||||
draggabilly.on('dragStart', _ => {
|
||||
@ -585,7 +585,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
tabNoteSwitchedListener({tabId}) {
|
||||
const $tab = this.getTabById(tabId);
|
||||
|
||||
const {note} = this.appContext.getTabContextById(tabId);
|
||||
const {note} = this.appContext.tabManager.getTabContextById(tabId);
|
||||
|
||||
this.updateTab($tab, note);
|
||||
}
|
||||
@ -609,7 +609,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
async entitiesReloadedListener({loadResults}) {
|
||||
for (const tabContext of this.appContext.getTabContexts()) {
|
||||
for (const tabContext of this.appContext.tabManager.getTabContexts()) {
|
||||
if (loadResults.isNoteReloaded(tabContext.noteId)) {
|
||||
const $tab = this.getTabById(tabContext.tabId);
|
||||
|
||||
@ -619,7 +619,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
treeCacheReloadedListener() {
|
||||
for (const tabContext of this.appContext.getTabContexts()) {
|
||||
for (const tabContext of this.appContext.tabManager.getTabContexts()) {
|
||||
const $tab = this.getTabById(tabContext.tabId);
|
||||
|
||||
this.updateTab($tab, tabContext.note);
|
||||
|
@ -28,7 +28,7 @@ export default class EmptyTypeWidget extends TypeWidget {
|
||||
return false;
|
||||
}
|
||||
|
||||
appContext.getActiveTabContext().setNote(suggestion.path);
|
||||
appContext.tabManager.getActiveTabContext().setNote(suggestion.path);
|
||||
});
|
||||
|
||||
noteAutocompleteService.showRecentNotes(this.$autoComplete);
|
||||
|
@ -91,7 +91,7 @@ export default class TextTypeWidget extends TypeWidget {
|
||||
if (match) {
|
||||
const noteId = match[1];
|
||||
|
||||
appContext.getActiveTabContext().setNote(noteId);
|
||||
appContext.tabManager.getActiveTabContext().setNote(noteId);
|
||||
}
|
||||
else {
|
||||
window.open(src, '_blank');
|
||||
|
Loading…
x
Reference in New Issue
Block a user