diff --git a/config-sample.ini b/config-sample.ini index 36d546ebe..20c747197 100644 --- a/config-sample.ini +++ b/config-sample.ini @@ -8,7 +8,7 @@ instanceName= [Network] # host setting is relevant only for web deployments - set the host on which the server will listen # host=0.0.0.0 -# port setting is relevant only for web deployments, desktop builds run on random free port +# port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable) port=8080 # true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure). https=false diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index ece9f9885..b93de56a6 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -3,7 +3,7 @@ import contextMenu from './services/tree_context_menu.js'; import link from './services/link.js'; import ws from './services/ws.js'; import noteDetailService from './services/note_detail.js'; -import noteType from './services/note_type.js'; +import noteType from './widgets/note_type.js'; import protectedSessionService from './services/protected_session.js'; import protectedSessionHolder from './services/protected_session_holder.js'; import searchNotesService from './services/search_notes.js'; @@ -21,7 +21,7 @@ import bundle from "./services/bundle.js"; import treeCache from "./services/tree_cache.js"; import libraryLoader from "./services/library_loader.js"; import hoistedNoteService from './services/hoisted_note.js'; -import noteTypeService from './services/note_type.js'; +import noteTypeService from './widgets/note_type.js'; import linkService from './services/link.js'; import noteAutocompleteService from './services/note_autocomplete.js'; import macInit from './services/mac_init.js'; diff --git a/src/public/javascripts/services/app_context.js b/src/public/javascripts/services/app_context.js index c24173698..336b0a357 100644 --- a/src/public/javascripts/services/app_context.js +++ b/src/public/javascripts/services/app_context.js @@ -2,13 +2,12 @@ import GlobalButtonsWidget from "../widgets/global_buttons.js"; import SearchBoxWidget from "../widgets/search_box.js"; import SearchResultsWidget from "../widgets/search_results.js"; import NoteTreeWidget from "../widgets/note_tree.js"; -import tabRow from "./tab_row.js"; import treeService from "./tree.js"; import noteDetailService from "./note_detail.js"; import TabContext from "./tab_context.js"; import server from "./server.js"; import keyboardActionService from "./keyboard_actions.js"; -import contextMenuService from "./context_menu.js"; +import TabRowWidget from "./tab_row.js"; class AppContext { constructor() { @@ -16,6 +15,30 @@ class AppContext { /** @type {TabContext[]} */ this.tabContexts = []; this.tabsChangedTaskId = null; + /** @type {TabRowWidget} */ + this.tabRow = null; + } + + showWidgets() { + const $leftPane = $("#left-pane"); + + this.tabRow = new TabRowWidget(this); + $("#tab-row-container").append(this.tabRow.render()); + + this.noteTreeWidget = new NoteTreeWidget(this); + + this.widgets = [ + new GlobalButtonsWidget(this), + new SearchBoxWidget(this), + new SearchResultsWidget(this), + this.noteTreeWidget + ]; + + for (const widget of this.widgets) { + const $widget = widget.render(); + + $leftPane.append($widget); + } } trigger(name, data) { @@ -31,7 +54,7 @@ class AppContext { /** @returns {TabContext} */ getActiveTabContext() { - const activeTabEl = tabRow.activeTabEl; + const activeTabEl = this.tabRow.activeTabEl; if (!activeTabEl) { return null; @@ -115,25 +138,6 @@ class AppContext { } } - showWidgets() { - const $leftPane = $("#left-pane"); - - this.noteTreeWidget = new NoteTreeWidget(this); - - this.widgets = [ - new GlobalButtonsWidget(this), - new SearchBoxWidget(this), - new SearchResultsWidget(this), - this.noteTreeWidget - ]; - - for (const widget of this.widgets) { - const $widget = widget.render(); - - $leftPane.append($widget); - } - } - /** * @return {NoteTreeWidget} */ @@ -144,7 +148,7 @@ class AppContext { 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(tabRow, state); + const ctx = new TabContext(this.tabRow, state); this.tabContexts.push(ctx); return ctx; @@ -174,16 +178,16 @@ class AppContext { } async openEmptyTab() { - const ctx = new TabContext(tabRow); + const ctx = new TabContext(this.tabRow); this.tabContexts.push(ctx); - await tabRow.activateTab(ctx.$tab[0]); + await this.tabRow.activateTab(ctx.$tab[0]); } async filterTabs(noteId) { for (const tc of this.tabContexts) { if (tc.notePath && !tc.notePath.split("/").includes(noteId)) { - await tabRow.removeTab(tc.$tab[0]); + await this.tabRow.removeTab(tc.$tab[0]); } } @@ -197,7 +201,7 @@ class AppContext { async saveOpenTabs() { const openTabs = []; - for (const tabEl of tabRow.tabEls) { + for (const tabEl of this.tabRow.tabEls) { const tabId = tabEl.getAttribute('data-tab-id'); const tabContext = appContext.getTabContexts().find(tc => tc.tabId === tabId); @@ -229,81 +233,63 @@ class AppContext { this.tabsChangedTaskId = setTimeout(() => this.saveOpenTabs(), 1000); } + + newTabListener() { + this.openEmptyTab(); + } + + async activeTabChangedListener({tabEl}) { + const tabId = tabEl.getAttribute('data-tab-id'); + + await this.showTab(tabId); + } + + async tabRemoveListener({tabEl}) { + const tabId = tabEl.getAttribute('data-tab-id'); + + this.tabContexts.filter(nc => nc.tabId === tabId) + .forEach(tc => tc.remove()); + + this.tabContexts = this.tabContexts.filter(nc => nc.tabId !== tabId); + + if (this.tabContexts.length === 0) { + this.openEmptyTab(); + } + + this.openTabsChanged(); + } + + tabReorderListener() { + this.openTabsChanged(); + } } const appContext = new AppContext(); -tabRow.addListener('newTab', () => appContext.openEmptyTab()); - -tabRow.addListener('activeTabChange', async ({ detail }) => { - const tabId = detail.tabEl.getAttribute('data-tab-id'); - - await appContext.showTab(tabId); -}); - -tabRow.addListener('tabRemove', async ({ detail }) => { - const tabId = detail.tabEl.getAttribute('data-tab-id'); - - appContext.tabContexts.filter(nc => nc.tabId === tabId) - .forEach(tc => tc.remove()); - - appContext.tabContexts = appContext.tabContexts.filter(nc => nc.tabId !== tabId); - - if (appContext.tabContexts.length === 0) { - appContext.openEmptyTab(); - } -}); - -tabRow.addListener('activeTabChange', () => appContext.openTabsChanged()); -tabRow.addListener('tabRemove', () => appContext.openTabsChanged()); -tabRow.addListener('tabReorder', () => appContext.openTabsChanged()); - keyboardActionService.setGlobalActionHandler('OpenNewTab', () => { appContext.openEmptyTab(); }); keyboardActionService.setGlobalActionHandler('CloseActiveTab', () => { - if (tabRow.activeTabEl) { - tabRow.removeTab(tabRow.activeTabEl); + if (this.tabRow.activeTabEl) { + this.tabRow.removeTab(this.tabRow.activeTabEl); } }); keyboardActionService.setGlobalActionHandler('ActivateNextTab', () => { - const nextTab = tabRow.nextTabEl; + const nextTab = this.tabRow.nextTabEl; if (nextTab) { - tabRow.activateTab(nextTab); + this.tabRow.activateTab(nextTab); } }); keyboardActionService.setGlobalActionHandler('ActivatePreviousTab', () => { - const prevTab = tabRow.previousTabEl; + const prevTab = this.tabRow.previousTabEl; if (prevTab) { - tabRow.activateTab(prevTab); + this.tabRow.activateTab(prevTab); } }); -$(tabRow.el).on('contextmenu', '.note-tab', e => { - e.preventDefault(); - - const tab = $(e.target).closest(".note-tab"); - - contextMenuService.initContextMenu(e, { - getContextMenuItems: () => { - return [ - {title: "Close all tabs", cmd: "removeAllTabs", uiIcon: "empty"}, - {title: "Close all tabs except for this", cmd: "removeAllTabsExceptForThis", uiIcon: "empty"} - ]; - }, - selectContextMenuItem: (e, cmd) => { - if (cmd === 'removeAllTabs') { - tabRow.removeAllTabs(); - } else if (cmd === 'removeAllTabsExceptForThis') { - tabRow.removeAllTabsExceptForThis(tab[0]); - } - } - }); -}); - export default appContext; \ No newline at end of file diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 4f7af4d60..4730492d3 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -4,10 +4,8 @@ import server from './server.js'; import ws from "./ws.js"; import treeCache from "./tree_cache.js"; import NoteFull from "../entities/note_full.js"; -import contextMenuService from "./context_menu.js"; import treeUtils from "./tree_utils.js"; import tabRow from "./tab_row.js"; -import keyboardActionService from "./keyboard_actions.js"; import appContext from "./app_context.js"; const $tabContentsContainer = $("#note-tab-container"); diff --git a/src/public/javascripts/services/tab_context.js b/src/public/javascripts/services/tab_context.js index 794e8f80a..8cee17575 100644 --- a/src/public/javascripts/services/tab_context.js +++ b/src/public/javascripts/services/tab_context.js @@ -3,13 +3,8 @@ import protectedSessionHolder from "./protected_session_holder.js"; import server from "./server.js"; import bundleService from "./bundle.js"; import Attributes from "./attributes.js"; -import treeUtils from "./tree_utils.js"; import utils from "./utils.js"; -import NoteTypeContext from "./note_type.js"; -import noteDetailService from "./note_detail.js"; -import protectedSessionService from "./protected_session.js"; import optionsService from "./options.js"; -import linkService from "./link.js"; import Sidebar from "./sidebar.js"; import appContext from "./app_context.js"; @@ -36,7 +31,7 @@ optionsService.addLoadListener(options => { class TabContext { /** - * @param {TabRow} tabRow + * @param {TabRowWidget} tabRow * @param {object} state */ constructor(tabRow, state = {}) { @@ -61,13 +56,8 @@ class TabContext { $tabContentsContainer.append(this.$tabContent); - this.$noteTitle = this.$tabContent.find(".note-title"); - this.$noteTitleRow = this.$tabContent.find(".note-title-row"); - this.$notePathList = this.$tabContent.find(".note-path-list"); - this.$notePathCount = this.$tabContent.find(".note-path-count"); this.$noteDetailComponents = this.$tabContent.find(".note-detail-component"); this.$scriptArea = this.$tabContent.find(".note-detail-script-area"); - this.$savedIndicator = this.$tabContent.find(".saved-indicator"); this.noteChangeDisabled = false; this.isNoteChanged = false; this.attributes = new Attributes(this); @@ -78,41 +68,10 @@ class TabContext { }; this.sidebar = new Sidebar(this, sidebarState); - this.noteType = new NoteTypeContext(this); } this.components = {}; - this.$noteTitle.on('input', () => { - if (!this.note) { - return; - } - - this.noteChanged(); - - this.note.title = this.$noteTitle.val(); - - this.tabRow.updateTab(this.$tab[0], {title: this.note.title}); - treeService.setNoteTitle(this.note.noteId, this.note.title); - - this.setTitleBar(); - }); - - if (utils.isDesktop()) { - // keyboard plugin is not loaded in mobile - utils.bindElShortcut(this.$noteTitle, 'return', () => { - this.getComponent().focus(); - - return false; // to not propagate the enter into the editor (causes issues with codemirror) - }); - } - - this.$protectButton = this.$tabContent.find(".protect-button"); - this.$protectButton.on('click', protectedSessionService.protectNoteAndSendToServer); - - this.$unprotectButton = this.$tabContent.find(".unprotect-button"); - this.$unprotectButton.on('click', protectedSessionService.unprotectNoteAndSendToServer); - await this.initComponent(); } @@ -156,8 +115,6 @@ class TabContext { this.noteChangeDisabled = true; try { - this.$noteTitle.val(this.note.title); - await this.renderComponent(); } finally { this.noteChangeDisabled = false; @@ -177,12 +134,6 @@ class TabContext { } }, 5000); - this.showPaths(); - - if (utils.isDesktop()) { - this.noteType.update(); - } - bundleService.executeRelationBundles(this.note, 'runOnNoteView', this); // after loading new note make sure editor is scrolled to the top @@ -281,12 +232,7 @@ class TabContext { this.$tabContent.addClass(utils.getNoteTypeClass(this.note.type)); this.$tabContent.addClass(utils.getMimeTypeClass(this.note.mime)); - this.$noteTitleRow.show(); // might be hidden from empty detail this.$tabContent.toggleClass("protected", this.note.isProtected); - this.$protectButton.toggleClass("active", this.note.isProtected); - this.$protectButton.prop("disabled", this.note.isProtected); - this.$unprotectButton.toggleClass("active", !this.note.isProtected); - this.$unprotectButton.prop("disabled", !this.note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()); } getComponent() { @@ -349,12 +295,11 @@ class TabContext { protectedSessionHolder.touchProtectedSession(); } - this.$savedIndicator.fadeIn(); + // FIXME trigger "noteSaved" event so that title indicator is triggered + this.eventReceived('noteSaved'); // run async bundleService.executeRelationBundles(this.note, 'runOnNoteChange', this); - - this.eventReceived('noteSaved'); } async saveNoteIfChanged() { @@ -372,65 +317,10 @@ class TabContext { this.isNoteChanged = true; + // FIMXE: trigger noteChanged event this.$savedIndicator.fadeOut(); } - async addPath(notePath, isCurrent) { - const title = await treeUtils.getNotePathTitle(notePath); - - const noteLink = await linkService.createNoteLink(notePath, {title}); - - noteLink - .addClass("no-tooltip-preview") - .addClass("dropdown-item"); - - if (isCurrent) { - noteLink.addClass("current"); - } - - this.$notePathList.append(noteLink); - } - - async showPaths() { - if (this.note.noteId === 'root') { - // root doesn't have any parent, but it's still technically 1 path - - this.$notePathCount.html("1 path"); - - this.$notePathList.empty(); - - await this.addPath('root', true); - } - else { - const parents = await this.note.getParentNotes(); - - this.$notePathCount.html(parents.length + " path" + (parents.length > 1 ? "s" : "")); - this.$notePathList.empty(); - - const pathSegments = this.notePath.split("/"); - const activeNoteParentNoteId = pathSegments[pathSegments.length - 2]; // we know this is not root so there must be a parent - - for (const parentNote of parents) { - const parentNotePath = await treeService.getSomeNotePath(parentNote); - // this is to avoid having root notes leading '/' - const notePath = parentNotePath ? (parentNotePath + '/' + this.note.noteId) : this.note.noteId; - const isCurrent = activeNoteParentNoteId === parentNote.noteId; - - await this.addPath(notePath, isCurrent); - } - - const cloneLink = $("