diff --git a/src/public/app/services/app_context.js b/src/public/app/components/app_context.js similarity index 78% rename from src/public/app/services/app_context.js rename to src/public/app/components/app_context.js index 510f1eb52..8b3cbf2aa 100644 --- a/src/public/app/services/app_context.js +++ b/src/public/app/components/app_context.js @@ -1,24 +1,26 @@ -import froca from "./froca.js"; -import bundleService from "./bundle.js"; +import froca from "../services/froca.js"; +import bundleService from "../services/bundle.js"; import RootCommandExecutor from "./root_command_executor.js"; import Entrypoints from "./entrypoints.js"; -import options from "./options.js"; -import utils from "./utils.js"; -import zoomService from "./zoom.js"; +import options from "../services/options.js"; +import utils from "../services/utils.js"; +import zoomComponent from "./zoom.js"; import TabManager from "./tab_manager.js"; -import treeService from "./tree.js"; -import Component from "../widgets/component.js"; -import keyboardActionsService from "./keyboard_actions.js"; -import MobileScreenSwitcherExecutor from "../widgets/mobile_widgets/mobile_screen_switcher.js"; +import treeService from "../services/tree.js"; +import Component from "./component.js"; +import keyboardActionsService from "../services/keyboard_actions.js"; +import MobileScreenSwitcherExecutor from "./mobile_screen_switcher.js"; import MainTreeExecutors from "./main_tree_executors.js"; -import toast from "./toast.js"; +import toast from "../services/toast.js"; +import ShortcutComponent from "./shortcut_component.js"; class AppContext extends Component { constructor(isMainWindow) { super(); this.isMainWindow = isMainWindow; - this.executors = []; + // non-widget/layout components needed for the application + this.components = []; this.beforeUnloadListeners = []; } @@ -27,7 +29,9 @@ class AppContext extends Component { } async start() { - this.showWidgets(); + this.initComponents(); + + this.renderWidgets(); await Promise.all([froca.initializedPromise, options.initializedPromise]); @@ -36,7 +40,31 @@ class AppContext extends Component { setTimeout(() => bundleService.executeStartupBundles(), 2000); } - showWidgets() { + initComponents() { + this.tabManager = new TabManager(); + + this.components = [ + this.tabManager, + new RootCommandExecutor(), + new Entrypoints(), + new MainTreeExecutors(), + new ShortcutComponent() + ]; + + if (utils.isMobile()) { + this.components.push(new MobileScreenSwitcherExecutor()); + } + + for (const component of this.components) { + this.child(component); + } + + if (utils.isElectron()) { + this.child(zoomComponent); + } + } + + renderWidgets() { const rootWidget = this.layout.getRootWidget(this); const $renderedWidget = rootWidget.render(); @@ -52,29 +80,8 @@ class AppContext extends Component { component.triggerCommand(commandName, {$el: $(this)}); }); - this.tabManager = new TabManager(); - - this.executors = [ - this.tabManager, - new RootCommandExecutor(), - new Entrypoints(), - new MainTreeExecutors() - ]; - - if (utils.isMobile()) { - this.executors.push(new MobileScreenSwitcherExecutor()); - } - this.child(rootWidget); - for (const executor of this.executors) { - this.child(executor); - } - - if (utils.isElectron()) { - this.child(zoomService); - } - this.triggerEvent('initialRenderComplete'); } @@ -85,7 +92,7 @@ class AppContext extends Component { /** @returns {Promise} */ triggerCommand(name, data = {}) { - for (const executor of this.executors) { + for (const executor of this.components) { const fun = executor[name + "Command"]; if (fun) { diff --git a/src/public/app/widgets/component.js b/src/public/app/components/component.js similarity index 100% rename from src/public/app/widgets/component.js rename to src/public/app/components/component.js diff --git a/src/public/app/services/entrypoints.js b/src/public/app/components/entrypoints.js similarity index 93% rename from src/public/app/services/entrypoints.js rename to src/public/app/components/entrypoints.js index c9eaa867c..a93d972e9 100644 --- a/src/public/app/services/entrypoints.js +++ b/src/public/app/components/entrypoints.js @@ -1,13 +1,13 @@ -import utils from "./utils.js"; -import dateNoteService from "./date_notes.js"; -import protectedSessionHolder from './protected_session_holder.js'; -import server from "./server.js"; +import utils from "../services/utils.js"; +import dateNoteService from "../services/date_notes.js"; +import protectedSessionHolder from '../services/protected_session_holder.js'; +import server from "../services/server.js"; import appContext from "./app_context.js"; -import Component from "../widgets/component.js"; -import toastService from "./toast.js"; -import ws from "./ws.js"; -import bundleService from "./bundle.js"; -import froca from "./froca.js"; +import Component from "./component.js"; +import toastService from "../services/toast.js"; +import ws from "../services/ws.js"; +import bundleService from "../services/bundle.js"; +import froca from "../services/froca.js"; export default class Entrypoints extends Component { constructor() { diff --git a/src/public/app/services/main_tree_executors.js b/src/public/app/components/main_tree_executors.js similarity index 90% rename from src/public/app/services/main_tree_executors.js rename to src/public/app/components/main_tree_executors.js index 239dba8b9..d12d308dc 100644 --- a/src/public/app/services/main_tree_executors.js +++ b/src/public/app/components/main_tree_executors.js @@ -1,8 +1,8 @@ import appContext from "./app_context.js"; -import noteCreateService from "./note_create.js"; -import treeService from "./tree.js"; -import hoistedNoteService from "./hoisted_note.js"; -import Component from "../widgets/component.js"; +import noteCreateService from "../services/note_create.js"; +import treeService from "../services/tree.js"; +import hoistedNoteService from "../services/hoisted_note.js"; +import Component from "./component.js"; /** * This class contains command executors which logically belong to the NoteTree widget, but for better user experience diff --git a/src/public/app/widgets/mobile_widgets/mobile_screen_switcher.js b/src/public/app/components/mobile_screen_switcher.js similarity index 90% rename from src/public/app/widgets/mobile_widgets/mobile_screen_switcher.js rename to src/public/app/components/mobile_screen_switcher.js index f8ef3723e..22808a620 100644 --- a/src/public/app/widgets/mobile_widgets/mobile_screen_switcher.js +++ b/src/public/app/components/mobile_screen_switcher.js @@ -1,4 +1,4 @@ -import Component from "../component.js"; +import Component from "./component.js"; export default class MobileScreenSwitcherExecutor extends Component { setActiveScreenCommand({screen}) { @@ -12,4 +12,4 @@ export default class MobileScreenSwitcherExecutor extends Component { initialRenderCompleteEvent() { this.setActiveScreenCommand({screen: 'tree'}); } -} \ No newline at end of file +} diff --git a/src/public/app/services/note_context.js b/src/public/app/components/note_context.js similarity index 94% rename from src/public/app/services/note_context.js rename to src/public/app/components/note_context.js index 9025884ac..7a05ad499 100644 --- a/src/public/app/services/note_context.js +++ b/src/public/app/components/note_context.js @@ -1,12 +1,12 @@ -import protectedSessionHolder from "./protected_session_holder.js"; -import server from "./server.js"; -import utils from "./utils.js"; +import protectedSessionHolder from "../services/protected_session_holder.js"; +import server from "../services/server.js"; +import utils from "../services/utils.js"; import appContext from "./app_context.js"; -import treeService from "./tree.js"; -import Component from "../widgets/component.js"; -import froca from "./froca.js"; -import hoistedNoteService from "./hoisted_note.js"; -import options from "./options.js"; +import treeService from "../services/tree.js"; +import Component from "./component.js"; +import froca from "../services/froca.js"; +import hoistedNoteService from "../services/hoisted_note.js"; +import options from "../services/options.js"; class NoteContext extends Component { /** diff --git a/src/public/app/services/root_command_executor.js b/src/public/app/components/root_command_executor.js similarity index 90% rename from src/public/app/services/root_command_executor.js rename to src/public/app/components/root_command_executor.js index 764e34e8d..0e96e70c5 100644 --- a/src/public/app/services/root_command_executor.js +++ b/src/public/app/components/root_command_executor.js @@ -1,11 +1,11 @@ -import Component from "../widgets/component.js"; +import Component from "./component.js"; import appContext from "./app_context.js"; import dateNoteService from "../services/date_notes.js"; import treeService from "../services/tree.js"; -import openService from "./open.js"; -import protectedSessionService from "./protected_session.js"; -import options from "./options.js"; -import froca from "./froca.js"; +import openService from "../services/open.js"; +import protectedSessionService from "../services/protected_session.js"; +import options from "../services/options.js"; +import froca from "../services/froca.js"; export default class RootCommandExecutor extends Component { editReadOnlyNoteCommand() { @@ -46,7 +46,7 @@ export default class RootCommandExecutor extends Component { openNoteExternallyCommand() { const noteId = appContext.tabManager.getActiveContextNoteId(); const mime = appContext.tabManager.getActiveContextNoteMime() - + if (noteId) { openService.openNoteExternally(noteId, mime); } @@ -72,7 +72,7 @@ export default class RootCommandExecutor extends Component { options.toggle('leftPaneVisible'); } - async showLaunchBarShortcutsCommand() { + async showLaunchBarSubtreeCommand() { await appContext.tabManager.openContextWithNote('lb_root', true, null, 'lb_root'); } diff --git a/src/public/app/components/shortcut_component.js b/src/public/app/components/shortcut_component.js new file mode 100644 index 000000000..9df62d7bb --- /dev/null +++ b/src/public/app/components/shortcut_component.js @@ -0,0 +1,40 @@ +import appContext from "./app_context.js"; +import shortcutService from "../services/shortcuts.js"; +import server from "../services/server.js"; +import Component from "./component.js"; +import froca from "../services/froca.js"; + +export default class ShortcutComponent extends Component { + constructor() { + super(); + + server.get('keyboard-shortcuts-for-notes').then(shortcutAttributes => { + for (const attr of shortcutAttributes) { + this.bindNoteShortcutHandler(attr); + } + }); + } + + bindNoteShortcutHandler(label) { + const handler = () => appContext.tabManager.getActiveContext().setNote(label.noteId); + const namespace = label.attributeId; + + if (label.isDeleted) { + shortcutService.removeGlobalShortcut(namespace); + } else { + shortcutService.bindGlobalShortcut(label.value, handler, namespace); + } + } + + async entitiesReloadedEvent({loadResults}) { + for (const attr of loadResults.getAttributes()) { + if (attr.type === 'label' && attr.name === 'keyboardShortcut') { + const note = await froca.getNote(attr.noteId); + // launcher shortcuts are handled specifically + if (note && note.type !== 'launcher') { + this.bindNoteShortcutHandler(attr); + } + } + } + } +} diff --git a/src/public/app/services/tab_manager.js b/src/public/app/components/tab_manager.js similarity index 98% rename from src/public/app/services/tab_manager.js rename to src/public/app/components/tab_manager.js index f5fcf2279..30c1e0d4e 100644 --- a/src/public/app/services/tab_manager.js +++ b/src/public/app/components/tab_manager.js @@ -1,10 +1,10 @@ -import Component from "../widgets/component.js"; -import SpacedUpdate from "./spaced_update.js"; -import server from "./server.js"; -import options from "./options.js"; -import froca from "./froca.js"; -import treeService from "./tree.js"; -import utils from "./utils.js"; +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"; import NoteContext from "./note_context.js"; import appContext from "./app_context.js"; import Mutex from "../utils/mutex.js"; diff --git a/src/public/app/services/zoom.js b/src/public/app/components/zoom.js similarity index 90% rename from src/public/app/services/zoom.js rename to src/public/app/components/zoom.js index e435ac970..89ffdf965 100644 --- a/src/public/app/services/zoom.js +++ b/src/public/app/components/zoom.js @@ -1,11 +1,11 @@ -import options from "./options.js"; -import Component from "../widgets/component.js"; +import options from "../services/options.js"; +import Component from "./component.js"; import utils from "../services/utils.js"; const MIN_ZOOM = 0.5; const MAX_ZOOM = 2.0; -class ZoomService extends Component { +class ZoomComponent extends Component { constructor() { super(); @@ -59,6 +59,6 @@ class ZoomService extends Component { } } -const zoomService = new ZoomService(); +const zoomService = new ZoomComponent(); export default zoomService; diff --git a/src/public/app/desktop.js b/src/public/app/desktop.js index f1f7da792..21403bbb8 100644 --- a/src/public/app/desktop.js +++ b/src/public/app/desktop.js @@ -1,4 +1,4 @@ -import appContext from "./services/app_context.js"; +import appContext from "./components/app_context.js"; import utils from './services/utils.js'; import noteTooltipService from './services/note_tooltip.js'; import bundleService from "./services/bundle.js"; @@ -7,7 +7,7 @@ import macInit from './services/mac_init.js'; import contextMenu from "./menus/context_menu.js"; import DesktopLayout from "./layouts/desktop_layout.js"; import glob from "./services/glob.js"; -import zoomService from './services/zoom.js'; +import zoomService from './components/zoom.js'; bundleService.getWidgetBundlesByParent().then(widgetBundles => { appContext.setLayout(new DesktopLayout(widgetBundles)); diff --git a/src/public/app/doc_notes/launchbar_command_launcher.html b/src/public/app/doc_notes/launchbar_command_launcher.html new file mode 100644 index 000000000..68eb051a2 --- /dev/null +++ b/src/public/app/doc_notes/launchbar_command_launcher.html @@ -0,0 +1 @@ +
Keyboard shortcut for this launcher action can be configured in Options -> Shortcuts.
diff --git a/src/public/app/doc_notes/launchbar_note_shortcut.html b/src/public/app/doc_notes/launchbar_note_launcher.html similarity index 100% rename from src/public/app/doc_notes/launchbar_note_shortcut.html rename to src/public/app/doc_notes/launchbar_note_launcher.html diff --git a/src/public/app/doc_notes/launchbar_script_shortcut.html b/src/public/app/doc_notes/launchbar_script_launcher.html similarity index 100% rename from src/public/app/doc_notes/launchbar_script_shortcut.html rename to src/public/app/doc_notes/launchbar_script_launcher.html diff --git a/src/public/app/doc_notes/launchbar_widget_shortcut.html b/src/public/app/doc_notes/launchbar_widget_launcher.html similarity index 100% rename from src/public/app/doc_notes/launchbar_widget_shortcut.html rename to src/public/app/doc_notes/launchbar_widget_launcher.html diff --git a/src/public/app/entities/note_short.js b/src/public/app/entities/note_short.js index 757c4b776..d1580ab41 100644 --- a/src/public/app/entities/note_short.js +++ b/src/public/app/entities/note_short.js @@ -21,7 +21,7 @@ const NOTE_TYPE_ICONS = { "mermaid": "bx bx-selection", "canvas": "bx bx-pen", "web-view": "bx bx-globe-alt", - "shortcut": "bx bx-link", + "launcher": "bx bx-link", "doc": "bx bxs-file-doc" }; @@ -827,7 +827,7 @@ class NoteShort { } isLaunchBarConfig() { - return this.type === 'shortcut' || this.noteId.startsWith("lb_"); + return this.type === 'launcher' || this.noteId.startsWith("lb_"); } } diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 07a0b1fef..2b3463cb1 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -72,7 +72,7 @@ import OptionsDialog from "../widgets/dialogs/options.js"; import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js"; import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js"; import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js"; -import ShortcutContainer from "../widgets/containers/shortcut_container.js"; +import LauncherContainer from "../widgets/containers/launcher_container.js"; import NoteRevisionsButton from "../widgets/buttons/note_revisions_button.js"; import EditableCodeButtonsWidget from "../widgets/type_widgets/editable_code_buttons.js"; import ApiLogWidget from "../widgets/api_log.js"; @@ -104,7 +104,7 @@ export default class DesktopLayout { .id("launcher-pane") .css("width", "53px") .child(new GlobalMenuWidget()) - .child(new ShortcutContainer()) + .child(new LauncherContainer()) .child(new LeftPaneToggleWidget()) ) .child(new LeftPaneContainer() diff --git a/src/public/app/menus/shortcut_context_menu.js b/src/public/app/menus/launcher_context_menu.js similarity index 60% rename from src/public/app/menus/shortcut_context_menu.js rename to src/public/app/menus/launcher_context_menu.js index ba78003ee..4635f8f23 100644 --- a/src/public/app/menus/shortcut_context_menu.js +++ b/src/public/app/menus/launcher_context_menu.js @@ -4,7 +4,7 @@ import contextMenu from "./context_menu.js"; import dialogService from "../services/dialog.js"; import server from "../services/server.js"; -export default class ShortcutContextMenu { +export default class LauncherContextMenu { /** * @param {NoteTreeWidget} treeWidget * @param {FancytreeNode} node @@ -27,38 +27,38 @@ export default class ShortcutContextMenu { const note = await froca.getNote(this.node.data.noteId); const parentNoteId = this.node.getParent().data.noteId; - const isVisibleRoot = note.noteId === 'lb_visibleshortcuts'; - const isAvailableRoot = note.noteId === 'lb_availableshortcuts'; - const isVisibleItem = parentNoteId === 'lb_visibleshortcuts'; - const isAvailableItem = parentNoteId === 'lb_availableshortcuts'; + const isVisibleRoot = note.noteId === 'lb_visiblelaunchers'; + const isAvailableRoot = note.noteId === 'lb_availablelaunchers'; + const isVisibleItem = parentNoteId === 'lb_visiblelaunchers'; + const isAvailableItem = parentNoteId === 'lb_availablelaunchers'; const isItem = isVisibleItem || isAvailableItem; const canBeDeleted = !note.noteId.startsWith("lb_"); const canBeReset = note.noteId.startsWith("lb_"); return [ - (isVisibleRoot || isAvailableRoot) ? { title: 'Add note shortcut', command: 'addNoteShortcut', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add script shortcut', command: 'addScriptShortcut', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add widget shortcut', command: 'addWidgetShortcut', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerShortcut', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add a script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add a custom widget', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null, (isVisibleRoot || isAvailableRoot) ? { title: "----" } : null, { title: 'Delete ', command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted }, - { title: 'Reset', command: "resetShortcut", uiIcon: "bx bx-empty", enabled: canBeReset}, + { title: 'Reset', command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset}, { title: "----" }, - isAvailableItem ? { title: 'Move to visible shortcuts', command: "moveShortcutToVisible", uiIcon: "bx bx-show", enabled: true } : null, - isVisibleItem ? { title: 'Move to available shortcuts', command: "moveShortcutToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, - { title: `Duplicate shortcut `, command: "duplicateSubtree", uiIcon: "bx bx-empty", + isAvailableItem ? { title: 'Move to visible launchers', command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null, + isVisibleItem ? { title: 'Move to available launchers', command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, + { title: `Duplicate launcher `, command: "duplicateSubtree", uiIcon: "bx bx-empty", enabled: isItem } ].filter(row => row !== null); } async selectMenuItemHandler({command}) { - if (command === 'resetShortcut') { + if (command === 'resetLauncher') { const confirmed = await dialogService.confirm(`Do you really want to reset "${this.node.title}"? - All data / settings in this shortcut (and its children) will be lost - and the shortcut will be returned to its original location.`); + All data / settings in this note (and its children) will be lost + and the launcher will be returned to its original location.`); if (confirmed) { - await server.post(`special-notes/shortcuts/${this.node.data.noteId}/reset`); + await server.post(`special-notes/launchers/${this.node.data.noteId}/reset`); } return; diff --git a/src/public/app/menus/link_context_menu.js b/src/public/app/menus/link_context_menu.js index f586e1e82..76debd781 100644 --- a/src/public/app/menus/link_context_menu.js +++ b/src/public/app/menus/link_context_menu.js @@ -1,5 +1,5 @@ import contextMenu from "./context_menu.js"; -import appContext from "../services/app_context.js"; +import appContext from "../components/app_context.js"; function openContextMenu(notePath, e) { contextMenu.show({ diff --git a/src/public/app/menus/tree_context_menu.js b/src/public/app/menus/tree_context_menu.js index f491be9e7..98ebebadc 100644 --- a/src/public/app/menus/tree_context_menu.js +++ b/src/public/app/menus/tree_context_menu.js @@ -3,7 +3,7 @@ import froca from "../services/froca.js"; import clipboard from '../services/clipboard.js'; import noteCreateService from "../services/note_create.js"; import contextMenu from "./context_menu.js"; -import appContext from "../services/app_context.js"; +import appContext from "../components/app_context.js"; import noteTypesService from "../services/note_types.js"; export default class TreeContextMenu { diff --git a/src/public/app/mobile.js b/src/public/app/mobile.js index 1e7533168..1e2a366fa 100644 --- a/src/public/app/mobile.js +++ b/src/public/app/mobile.js @@ -1,8 +1,8 @@ -import appContext from "./services/app_context.js"; +import appContext from "./components/app_context.js"; import MobileLayout from "./layouts/mobile_layout.js"; import glob from "./services/glob.js"; glob.setupGlobs(); appContext.setLayout(new MobileLayout()); -appContext.start(); \ No newline at end of file +appContext.start(); diff --git a/src/public/app/services/branches.js b/src/public/app/services/branches.js index a1a9bb4d1..0df8e454c 100644 --- a/src/public/app/services/branches.js +++ b/src/public/app/services/branches.js @@ -4,13 +4,13 @@ import toastService from "./toast.js"; import froca from "./froca.js"; import hoistedNoteService from "./hoisted_note.js"; import ws from "./ws.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; async function moveBeforeBranch(branchIdsToMove, beforeBranchId) { branchIdsToMove = filterRootNote(branchIdsToMove); branchIdsToMove = filterSearchBranches(branchIdsToMove); - if (['root', 'lb_root', 'lb_availableshortcuts', 'lb_visibleshortcuts'].includes(beforeBranchId)) { + if (['root', 'lb_root', 'lb_availablelaunchers', 'lb_visiblelaunchers'].includes(beforeBranchId)) { toastService.showError('Cannot move notes here.'); return; } @@ -35,8 +35,8 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) { 'root', hoistedNoteService.getHoistedNoteId(), 'lb_root', - 'lb_availableshortcuts', - 'lb_visibleshortcuts' + 'lb_availablelaunchers', + 'lb_visiblelaunchers' ]; if (forbiddenNoteIds.includes(afterNote.noteId)) { diff --git a/src/public/app/services/dialog.js b/src/public/app/services/dialog.js index 9a401e52f..325a65146 100644 --- a/src/public/app/services/dialog.js +++ b/src/public/app/services/dialog.js @@ -1,4 +1,4 @@ -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; async function info(message) { return new Promise(res => diff --git a/src/public/app/services/file_watcher.js b/src/public/app/services/file_watcher.js index b42eeb365..74d135b73 100644 --- a/src/public/app/services/file_watcher.js +++ b/src/public/app/services/file_watcher.js @@ -1,5 +1,5 @@ import ws from "./ws.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; const fileModificationStatus = {}; diff --git a/src/public/app/services/froca.js b/src/public/app/services/froca.js index e06386772..34ff49c59 100644 --- a/src/public/app/services/froca.js +++ b/src/public/app/services/froca.js @@ -2,7 +2,7 @@ import Branch from "../entities/branch.js"; import NoteShort from "../entities/note_short.js"; import Attribute from "../entities/attribute.js"; import server from "./server.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import NoteComplement from "../entities/note_complement.js"; /** diff --git a/src/public/app/services/froca_updater.js b/src/public/app/services/froca_updater.js index 17347704b..3e130d89b 100644 --- a/src/public/app/services/froca_updater.js +++ b/src/public/app/services/froca_updater.js @@ -76,7 +76,7 @@ async function processEntityChanges(entityChanges) { noteAttributeCache.invalidate(); } - const appContext = (await import("./app_context.js")).default; + const appContext = (await import("../components/app_context.js")).default; await appContext.triggerEvent('entitiesReloaded', {loadResults}); } } diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index 0b40b0333..fd0368e31 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -9,10 +9,11 @@ import dateNotesService from './date_notes.js'; import searchService from './search.js'; import CollapsibleWidget from '../widgets/collapsible_widget.js'; import ws from "./ws.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import BasicWidget from "../widgets/basic_widget.js"; import SpacedUpdate from "./spaced_update.js"; +import shortcutService from "./shortcuts.js"; /** * This is the main frontend API interface for scripts. It's published in the local "api" object. @@ -508,8 +509,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain * @method * @param {string} keyboardShortcut - e.g. "ctrl+shift+a" * @param {function} handler + * @param {string} [namespace] - specify namespace of the handler for the cases where call for bind may be repeated. + * If a handler with this ID exists, it's replaced by the new handler. */ - this.bindGlobalShortcut = utils.bindGlobalShortcut; + this.bindGlobalShortcut = shortcutService.bindGlobalShortcut; /** * Trilium runs in backend and frontend process, when something is changed on the backend from script, diff --git a/src/public/app/services/glob.js b/src/public/app/services/glob.js index 780818f7e..94153fed9 100644 --- a/src/public/app/services/glob.js +++ b/src/public/app/services/glob.js @@ -1,5 +1,5 @@ import utils from "./utils.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import server from "./server.js"; import libraryLoader from "./library_loader.js"; import ws from "./ws.js"; diff --git a/src/public/app/services/hoisted_note.js b/src/public/app/services/hoisted_note.js index 361276c86..dd8d63c35 100644 --- a/src/public/app/services/hoisted_note.js +++ b/src/public/app/services/hoisted_note.js @@ -1,4 +1,4 @@ -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import treeService from "./tree.js"; import dialogService from "./dialog.js"; import froca from "./froca.js"; diff --git a/src/public/app/services/import.js b/src/public/app/services/import.js index db9dad903..83211b0bf 100644 --- a/src/public/app/services/import.js +++ b/src/public/app/services/import.js @@ -2,7 +2,7 @@ import toastService from "./toast.js"; import server from "./server.js"; import ws from "./ws.js"; import utils from "./utils.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; export async function uploadFiles(parentNoteId, files, options) { if (files.length === 0) { diff --git a/src/public/app/services/keyboard_actions.js b/src/public/app/services/keyboard_actions.js index fecebca3f..585cd934a 100644 --- a/src/public/app/services/keyboard_actions.js +++ b/src/public/app/services/keyboard_actions.js @@ -1,6 +1,6 @@ import server from "./server.js"; -import utils from "./utils.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; +import shortcutService from "./shortcuts.js"; const keyboardActionRepo = {}; @@ -31,7 +31,7 @@ async function setupActionsForElement(scope, $el, component) { for (const action of actions) { for (const shortcut of action.effectiveShortcuts) { - utils.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId})); + shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId})); } } } @@ -39,37 +39,11 @@ async function setupActionsForElement(scope, $el, component) { getActionsForScope("window").then(actions => { for (const action of actions) { for (const shortcut of action.effectiveShortcuts) { - utils.bindGlobalShortcut(shortcut, () => appContext.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId})); + shortcutService.bindGlobalShortcut(shortcut, () => appContext.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId})); } } }); -server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => { - for (const shortcut in shortcutForNotes) { - utils.bindGlobalShortcut(shortcut, async () => { - appContext.tabManager.getActiveContext().setNote(shortcutForNotes[shortcut]); - }); - } -}); - -function setElementActionHandler($el, actionName, handler) { - keyboardActionsLoaded.then(() => { - const action = keyboardActionRepo[actionName]; - - if (!action) { - throw new Error(`Cannot find keyboard action '${actionName}'`); - } - - // not setting action.handler since this is not global - - for (const shortcut of action.effectiveShortcuts) { - if (shortcut) { - utils.bindElShortcut($el, shortcut, handler); - } - } - }); -} - async function getAction(actionName, silent = false) { await keyboardActionsLoaded; @@ -116,10 +90,8 @@ function updateDisplayedShortcuts($container) { } export default { - setElementActionHandler, updateDisplayedShortcuts, setupActionsForElement, getActions, - getActionsForScope, - getAction + getActionsForScope }; diff --git a/src/public/app/services/link.js b/src/public/app/services/link.js index d586cc678..95065ee33 100644 --- a/src/public/app/services/link.js +++ b/src/public/app/services/link.js @@ -1,6 +1,6 @@ import treeService from './tree.js'; import linkContextMenuService from "../menus/link_context_menu.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import froca from "./froca.js"; import utils from "./utils.js"; diff --git a/src/public/app/services/mac_init.js b/src/public/app/services/mac_init.js index 624c4858f..10fba8cbb 100644 --- a/src/public/app/services/mac_init.js +++ b/src/public/app/services/mac_init.js @@ -2,15 +2,16 @@ * Mac specific initialization */ import utils from "./utils.js"; +import shortcutService from "./shortcuts.js"; function init() { if (utils.isElectron() && utils.isMac()) { - utils.bindGlobalShortcut('meta+c', () => exec("copy")); - utils.bindGlobalShortcut('meta+v', () => exec('paste')); - utils.bindGlobalShortcut('meta+x', () => exec('cut')); - utils.bindGlobalShortcut('meta+a', () => exec('selectAll')); - utils.bindGlobalShortcut('meta+z', () => exec('undo')); - utils.bindGlobalShortcut('meta+y', () => exec('redo')); + shortcutService.bindGlobalShortcut('meta+c', () => exec("copy")); + shortcutService.bindGlobalShortcut('meta+v', () => exec('paste')); + shortcutService.bindGlobalShortcut('meta+x', () => exec('cut')); + shortcutService.bindGlobalShortcut('meta+a', () => exec('selectAll')); + shortcutService.bindGlobalShortcut('meta+z', () => exec('undo')); + shortcutService.bindGlobalShortcut('meta+y', () => exec('redo')); } } @@ -22,4 +23,4 @@ function exec(cmd) { export default { init -} \ No newline at end of file +} diff --git a/src/public/app/services/note_autocomplete.js b/src/public/app/services/note_autocomplete.js index c84a41dd3..a0dd30fa9 100644 --- a/src/public/app/services/note_autocomplete.js +++ b/src/public/app/services/note_autocomplete.js @@ -1,5 +1,5 @@ import server from "./server.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import utils from './utils.js'; import noteCreateService from './note_create.js'; import treeService from './tree.js'; diff --git a/src/public/app/services/note_create.js b/src/public/app/services/note_create.js index 39eced7f4..f246abae1 100644 --- a/src/public/app/services/note_create.js +++ b/src/public/app/services/note_create.js @@ -1,4 +1,4 @@ -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import utils from "./utils.js"; import protectedSessionHolder from "./protected_session_holder.js"; import server from "./server.js"; diff --git a/src/public/app/services/protected_session.js b/src/public/app/services/protected_session.js index 13d80b502..ac0d6e18d 100644 --- a/src/public/app/services/protected_session.js +++ b/src/public/app/services/protected_session.js @@ -2,7 +2,7 @@ import server from './server.js'; import protectedSessionHolder from './protected_session_holder.js'; import toastService from "./toast.js"; import ws from "./ws.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; import froca from "./froca.js"; import utils from "./utils.js"; import options from "./options.js"; diff --git a/src/public/app/services/server.js b/src/public/app/services/server.js index ff2bc14a7..e18663c61 100644 --- a/src/public/app/services/server.js +++ b/src/public/app/services/server.js @@ -3,7 +3,7 @@ import utils from './utils.js'; const REQUEST_LOGGING_ENABLED = false; async function getHeaders(headers) { - const appContext = (await import('./app_context.js')).default; + const appContext = (await import('../components/app_context.js')).default; const activeNoteContext = appContext.tabManager ? appContext.tabManager.getActiveContext() : null; // headers need to be lowercase because node.js automatically converts them to lower case diff --git a/src/public/app/services/shortcuts.js b/src/public/app/services/shortcuts.js new file mode 100644 index 000000000..7e6ea56a8 --- /dev/null +++ b/src/public/app/services/shortcuts.js @@ -0,0 +1,57 @@ +import utils from "./utils.js"; + +function removeGlobalShortcut(namespace) { + bindGlobalShortcut('', null, namespace); +} + +function bindGlobalShortcut(keyboardShortcut, handler, namespace = null) { + bindElShortcut($(document), keyboardShortcut, handler, namespace); +} + +function bindElShortcut($el, keyboardShortcut, handler, namespace = null) { + if (utils.isDesktop()) { + keyboardShortcut = normalizeShortcut(keyboardShortcut); + + let eventName = 'keydown'; + + if (namespace) { + eventName += "." + namespace; + + // if there's a namespace then we replace the existing event handler with the new one + $el.off(eventName); + } + + // method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted) + if (keyboardShortcut) { + $el.bind(eventName, keyboardShortcut, e => { + handler(e); + + e.preventDefault(); + e.stopPropagation(); + }); + } + } +} + +/** + * Normalize to the form expected by the jquery.hotkeys.js + */ +function normalizeShortcut(shortcut) { + if (!shortcut) { + return shortcut; + } + + return shortcut + .toLowerCase() + .replace("enter", "return") + .replace("delete", "del") + .replace("ctrl+alt", "alt+ctrl") + .replace("meta+alt", "alt+meta"); // alt needs to be first; +} + +export default { + bindGlobalShortcut, + bindElShortcut, + removeGlobalShortcut, + normalizeShortcut +} diff --git a/src/public/app/services/tree.js b/src/public/app/services/tree.js index 6657c864c..d68d689c6 100644 --- a/src/public/app/services/tree.js +++ b/src/public/app/services/tree.js @@ -3,7 +3,7 @@ import utils from './utils.js'; import server from './server.js'; import froca from './froca.js'; import hoistedNoteService from '../services/hoisted_note.js'; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; /** * @return {string|null} diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index 21f79ceb7..6d2fe7c2e 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -132,35 +132,6 @@ function randomString(len) { return text; } -function bindGlobalShortcut(keyboardShortcut, handler) { - bindElShortcut($(document), keyboardShortcut, handler); -} - -function bindElShortcut($el, keyboardShortcut, handler) { - if (isDesktop()) { - keyboardShortcut = normalizeShortcut(keyboardShortcut); - - $el.bind('keydown', keyboardShortcut, e => { - handler(e); - - e.preventDefault(); - e.stopPropagation(); - }); - } -} - -/** - * Normalize to the form expected by the jquery.hotkeys.js - */ -function normalizeShortcut(shortcut) { - return shortcut - .toLowerCase() - .replace("enter", "return") - .replace("delete", "del") - .replace("ctrl+alt", "alt+ctrl") - .replace("meta+alt", "alt+meta"); // alt needs to be first; -} - function isMobile() { return window.device === "mobile" // window.device is not available in setup @@ -387,8 +358,6 @@ export default { formatLabel, toObject, randomString, - bindGlobalShortcut, - bindElShortcut, isMobile, isDesktop, setCookie, @@ -402,7 +371,6 @@ export default { focusSavedElement, isHtmlEmpty, clearBrowserCache, - normalizeShortcut, copySelectionToClipboard, dynamicRequire, timeLimit, diff --git a/src/public/app/services/ws.js b/src/public/app/services/ws.js index 6dd34c1c6..4ae29c014 100644 --- a/src/public/app/services/ws.js +++ b/src/public/app/services/ws.js @@ -3,7 +3,7 @@ import toastService from "./toast.js"; import server from "./server.js"; import options from "./options.js"; import frocaUpdater from "./froca_updater.js"; -import appContext from "./app_context.js"; +import appContext from "../components/app_context.js"; const messageHandlers = []; diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index 027fa5429..84d0cf057 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -8,6 +8,7 @@ import promotedAttributeDefinitionParser from '../../services/promoted_attribute import NoteContextAwareWidget from "../note_context_aware_widget.js"; import SpacedUpdate from "../../services/spaced_update.js"; import utils from "../../services/utils.js"; +import shortcutService from "../../services/shortcuts.js"; const TPL = `