chore(core): integrate options, options_init & keyboard_actions

This commit is contained in:
Elian Doran 2026-01-06 13:20:42 +02:00
parent bd45c32251
commit 26d299aa44
No known key found for this signature in database
11 changed files with 1342 additions and 1323 deletions

View File

@ -1,21 +1,11 @@
import path from "path";
import build from "./build.js";
import packageJson from "../../package.json" with { type: "json" };
import dataDir from "./data_dir.js";
import { AppInfo } from "@triliumnext/commons";
import { app_info as coreAppInfo } from "@triliumnext/core";
import path from "path";
const APP_DB_VERSION = 233;
const SYNC_VERSION = 36;
const CLIPPER_PROTOCOL_VERSION = "1.0";
import dataDir from "./data_dir.js";
export default {
appVersion: packageJson.version,
dbVersion: APP_DB_VERSION,
...coreAppInfo,
nodeVersion: process.version,
syncVersion: SYNC_VERSION,
buildDate: build.buildDate,
buildRevision: build.buildRevision,
dataDirectory: path.resolve(dataDir.TRILIUM_DATA_DIR),
clipperProtocolVersion: CLIPPER_PROTOCOL_VERSION,
utcDateTime: new Date().toISOString()
} satisfies AppInfo;

View File

@ -1,882 +1,2 @@
"use strict";
import optionService from "./options.js";
import log from "./log.js";
import { isElectron, isMac } from "./utils.js";
import type { ActionKeyboardShortcut, KeyboardShortcut } from "@triliumnext/commons";
import { t } from "i18next";
function getDefaultKeyboardActions() {
if (!t("keyboard_actions.note-navigation")) {
throw new Error("Keyboard actions loaded before translations.");
}
const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [
{
separator: t("keyboard_actions.note-navigation")
},
{
actionName: "backInNoteHistory",
friendlyName: t("keyboard_action_names.back-in-note-history"),
iconClass: "bx bxs-chevron-left",
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
defaultShortcuts: isMac ? ["CommandOrControl+["] : ["Alt+Left"],
description: t("keyboard_actions.back-in-note-history"),
scope: "window"
},
{
actionName: "forwardInNoteHistory",
friendlyName: t("keyboard_action_names.forward-in-note-history"),
iconClass: "bx bxs-chevron-right",
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
defaultShortcuts: isMac ? ["CommandOrControl+]"] : ["Alt+Right"],
description: t("keyboard_actions.forward-in-note-history"),
scope: "window"
},
{
actionName: "jumpToNote",
friendlyName: t("keyboard_action_names.jump-to-note"),
defaultShortcuts: ["CommandOrControl+J"],
description: t("keyboard_actions.open-jump-to-note-dialog"),
scope: "window",
ignoreFromCommandPalette: true
},
{
actionName: "openTodayNote",
friendlyName: t("hidden-subtree.open-today-journal-note-title"),
iconClass: "bx bx-calendar",
defaultShortcuts: [],
description: t("hidden-subtree.open-today-journal-note-title"),
scope: "window"
},
{
actionName: "commandPalette",
friendlyName: t("keyboard_action_names.command-palette"),
defaultShortcuts: ["CommandOrControl+Shift+J"],
description: t("keyboard_actions.open-command-palette"),
scope: "window",
ignoreFromCommandPalette: true
},
{
actionName: "scrollToActiveNote",
friendlyName: t("keyboard_action_names.scroll-to-active-note"),
defaultShortcuts: ["CommandOrControl+."],
iconClass: "bx bx-current-location",
description: t("keyboard_actions.scroll-to-active-note"),
scope: "window"
},
{
actionName: "quickSearch",
friendlyName: t("keyboard_action_names.quick-search"),
iconClass: "bx bx-search",
defaultShortcuts: ["CommandOrControl+S"],
description: t("keyboard_actions.quick-search"),
scope: "window"
},
{
actionName: "searchInSubtree",
friendlyName: t("keyboard_action_names.search-in-subtree"),
defaultShortcuts: ["CommandOrControl+Shift+S"],
iconClass: "bx bx-search-alt",
description: t("keyboard_actions.search-in-subtree"),
scope: "note-tree"
},
{
actionName: "expandSubtree",
friendlyName: t("keyboard_action_names.expand-subtree"),
defaultShortcuts: [],
iconClass: "bx bx-layer-plus",
description: t("keyboard_actions.expand-subtree"),
scope: "note-tree"
},
{
actionName: "collapseTree",
friendlyName: t("keyboard_action_names.collapse-tree"),
defaultShortcuts: ["Alt+C"],
iconClass: "bx bx-layer-minus",
description: t("keyboard_actions.collapse-tree"),
scope: "window"
},
{
actionName: "collapseSubtree",
friendlyName: t("keyboard_action_names.collapse-subtree"),
iconClass: "bx bxs-layer-minus",
defaultShortcuts: ["Alt+-"],
description: t("keyboard_actions.collapse-subtree"),
scope: "note-tree"
},
{
actionName: "sortChildNotes",
friendlyName: t("keyboard_action_names.sort-child-notes"),
iconClass: "bx bx-sort-down",
defaultShortcuts: ["Alt+S"],
description: t("keyboard_actions.sort-child-notes"),
scope: "note-tree"
},
{
separator: t("keyboard_actions.creating-and-moving-notes")
},
{
actionName: "createNoteAfter",
friendlyName: t("keyboard_action_names.create-note-after"),
iconClass: "bx bx-plus",
defaultShortcuts: ["CommandOrControl+O"],
description: t("keyboard_actions.create-note-after"),
scope: "window"
},
{
actionName: "createNoteInto",
friendlyName: t("keyboard_action_names.create-note-into"),
iconClass: "bx bx-plus",
defaultShortcuts: ["CommandOrControl+P"],
description: t("keyboard_actions.create-note-into"),
scope: "window"
},
{
actionName: "createNoteIntoInbox",
friendlyName: t("keyboard_action_names.create-note-into-inbox"),
iconClass: "bx bxs-inbox",
defaultShortcuts: ["global:CommandOrControl+Alt+P"],
description: t("keyboard_actions.create-note-into-inbox"),
scope: "window"
},
{
actionName: "deleteNotes",
friendlyName: t("keyboard_action_names.delete-notes"),
iconClass: "bx bx-trash",
defaultShortcuts: ["Delete"],
description: t("keyboard_actions.delete-note"),
scope: "note-tree"
},
{
actionName: "moveNoteUp",
friendlyName: t("keyboard_action_names.move-note-up"),
iconClass: "bx bx-up-arrow-alt",
defaultShortcuts: isMac ? ["Alt+Up"] : ["CommandOrControl+Up"],
description: t("keyboard_actions.move-note-up"),
scope: "note-tree"
},
{
actionName: "moveNoteDown",
friendlyName: t("keyboard_action_names.move-note-down"),
iconClass: "bx bx-down-arrow-alt",
defaultShortcuts: isMac ? ["Alt+Down"] : ["CommandOrControl+Down"],
description: t("keyboard_actions.move-note-down"),
scope: "note-tree"
},
{
actionName: "moveNoteUpInHierarchy",
friendlyName: t("keyboard_action_names.move-note-up-in-hierarchy"),
iconClass: "bx bx-arrow-from-bottom",
defaultShortcuts: isMac ? ["Alt+Left"] : ["CommandOrControl+Left"],
description: t("keyboard_actions.move-note-up-in-hierarchy"),
scope: "note-tree"
},
{
actionName: "moveNoteDownInHierarchy",
friendlyName: t("keyboard_action_names.move-note-down-in-hierarchy"),
iconClass: "bx bx-arrow-from-top",
defaultShortcuts: isMac ? ["Alt+Right"] : ["CommandOrControl+Right"],
description: t("keyboard_actions.move-note-down-in-hierarchy"),
scope: "note-tree"
},
{
actionName: "editNoteTitle",
friendlyName: t("keyboard_action_names.edit-note-title"),
iconClass: "bx bx-rename",
defaultShortcuts: ["Enter"],
description: t("keyboard_actions.edit-note-title"),
scope: "note-tree"
},
{
actionName: "editBranchPrefix",
friendlyName: t("keyboard_action_names.edit-branch-prefix"),
iconClass: "bx bx-rename",
defaultShortcuts: ["F2"],
description: t("keyboard_actions.edit-branch-prefix"),
scope: "note-tree"
},
{
actionName: "cloneNotesTo",
friendlyName: t("keyboard_action_names.clone-notes-to"),
iconClass: "bx bx-duplicate",
defaultShortcuts: ["CommandOrControl+Shift+C"],
description: t("keyboard_actions.clone-notes-to"),
scope: "window"
},
{
actionName: "moveNotesTo",
friendlyName: t("keyboard_action_names.move-notes-to"),
iconClass: "bx bx-transfer",
defaultShortcuts: ["CommandOrControl+Shift+X"],
description: t("keyboard_actions.move-notes-to"),
scope: "window"
},
{
separator: t("keyboard_actions.note-clipboard")
},
{
actionName: "copyNotesToClipboard",
friendlyName: t("keyboard_action_names.copy-notes-to-clipboard"),
iconClass: "bx bx-copy",
defaultShortcuts: ["CommandOrControl+C"],
description: t("keyboard_actions.copy-notes-to-clipboard"),
scope: "note-tree"
},
{
actionName: "pasteNotesFromClipboard",
friendlyName: t("keyboard_action_names.paste-notes-from-clipboard"),
iconClass: "bx bx-paste",
defaultShortcuts: ["CommandOrControl+V"],
description: t("keyboard_actions.paste-notes-from-clipboard"),
scope: "note-tree"
},
{
actionName: "cutNotesToClipboard",
friendlyName: t("keyboard_action_names.cut-notes-to-clipboard"),
iconClass: "bx bx-cut",
defaultShortcuts: ["CommandOrControl+X"],
description: t("keyboard_actions.cut-notes-to-clipboard"),
scope: "note-tree"
},
{
actionName: "selectAllNotesInParent",
friendlyName: t("keyboard_action_names.select-all-notes-in-parent"),
iconClass: "bx bx-select-multiple",
defaultShortcuts: ["CommandOrControl+A"],
description: t("keyboard_actions.select-all-notes-in-parent"),
scope: "note-tree"
},
{
actionName: "addNoteAboveToSelection",
friendlyName: t("keyboard_action_names.add-note-above-to-selection"),
defaultShortcuts: ["Shift+Up"],
description: t("keyboard_actions.add-note-above-to-the-selection"),
scope: "note-tree",
ignoreFromCommandPalette: true
},
{
actionName: "addNoteBelowToSelection",
friendlyName: t("keyboard_action_names.add-note-below-to-selection"),
defaultShortcuts: ["Shift+Down"],
description: t("keyboard_actions.add-note-below-to-selection"),
scope: "note-tree",
ignoreFromCommandPalette: true
},
{
actionName: "duplicateSubtree",
friendlyName: t("keyboard_action_names.duplicate-subtree"),
iconClass: "bx bx-outline",
defaultShortcuts: [],
description: t("keyboard_actions.duplicate-subtree"),
scope: "note-tree"
},
{
separator: t("keyboard_actions.tabs-and-windows")
},
{
actionName: "openNewTab",
friendlyName: t("keyboard_action_names.open-new-tab"),
iconClass: "bx bx-plus",
defaultShortcuts: isElectron ? ["CommandOrControl+T"] : [],
description: t("keyboard_actions.open-new-tab"),
scope: "window"
},
{
actionName: "closeActiveTab",
friendlyName: t("keyboard_action_names.close-active-tab"),
iconClass: "bx bx-minus",
defaultShortcuts: isElectron ? ["CommandOrControl+W"] : [],
description: t("keyboard_actions.close-active-tab"),
scope: "window"
},
{
actionName: "reopenLastTab",
friendlyName: t("keyboard_action_names.reopen-last-tab"),
iconClass: "bx bx-undo",
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+T"] : [],
isElectronOnly: true,
description: t("keyboard_actions.reopen-last-tab"),
scope: "window"
},
{
actionName: "activateNextTab",
friendlyName: t("keyboard_action_names.activate-next-tab"),
iconClass: "bx bx-skip-next",
defaultShortcuts: isElectron ? ["CommandOrControl+Tab", "CommandOrControl+PageDown"] : [],
description: t("keyboard_actions.activate-next-tab"),
scope: "window"
},
{
actionName: "activatePreviousTab",
friendlyName: t("keyboard_action_names.activate-previous-tab"),
iconClass: "bx bx-skip-previous",
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+Tab", "CommandOrControl+PageUp"] : [],
description: t("keyboard_actions.activate-previous-tab"),
scope: "window"
},
{
actionName: "openNewWindow",
friendlyName: t("keyboard_action_names.open-new-window"),
iconClass: "bx bx-window-open",
defaultShortcuts: [],
description: t("keyboard_actions.open-new-window"),
scope: "window"
},
{
actionName: "toggleTray",
friendlyName: t("keyboard_action_names.toggle-system-tray-icon"),
iconClass: "bx bx-show",
defaultShortcuts: [],
isElectronOnly: true,
description: t("keyboard_actions.toggle-tray"),
scope: "window"
},
{
actionName: "toggleZenMode",
friendlyName: t("keyboard_action_names.toggle-zen-mode"),
iconClass: "bx bxs-yin-yang",
defaultShortcuts: ["F9"],
description: t("keyboard_actions.toggle-zen-mode"),
scope: "window"
},
{
actionName: "firstTab",
friendlyName: t("keyboard_action_names.switch-to-first-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+1"],
description: t("keyboard_actions.first-tab"),
scope: "window"
},
{
actionName: "secondTab",
friendlyName: t("keyboard_action_names.switch-to-second-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+2"],
description: t("keyboard_actions.second-tab"),
scope: "window"
},
{
actionName: "thirdTab",
friendlyName: t("keyboard_action_names.switch-to-third-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+3"],
description: t("keyboard_actions.third-tab"),
scope: "window"
},
{
actionName: "fourthTab",
friendlyName: t("keyboard_action_names.switch-to-fourth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+4"],
description: t("keyboard_actions.fourth-tab"),
scope: "window"
},
{
actionName: "fifthTab",
friendlyName: t("keyboard_action_names.switch-to-fifth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+5"],
description: t("keyboard_actions.fifth-tab"),
scope: "window"
},
{
actionName: "sixthTab",
friendlyName: t("keyboard_action_names.switch-to-sixth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+6"],
description: t("keyboard_actions.sixth-tab"),
scope: "window"
},
{
actionName: "seventhTab",
friendlyName: t("keyboard_action_names.switch-to-seventh-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+7"],
description: t("keyboard_actions.seventh-tab"),
scope: "window"
},
{
actionName: "eigthTab",
friendlyName: t("keyboard_action_names.switch-to-eighth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+8"],
description: t("keyboard_actions.eight-tab"),
scope: "window"
},
{
actionName: "ninthTab",
friendlyName: t("keyboard_action_names.switch-to-ninth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+9"],
description: t("keyboard_actions.ninth-tab"),
scope: "window"
},
{
actionName: "lastTab",
friendlyName: t("keyboard_action_names.switch-to-last-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+0"],
description: t("keyboard_actions.last-tab"),
scope: "window"
},
{
separator: t("keyboard_actions.dialogs")
},
{
friendlyName: t("keyboard_action_names.show-note-source"),
actionName: "showNoteSource",
iconClass: "bx bx-code",
defaultShortcuts: [],
description: t("keyboard_actions.show-note-source"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-options"),
actionName: "showOptions",
iconClass: "bx bx-cog",
defaultShortcuts: [],
description: t("keyboard_actions.show-options"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-revisions"),
actionName: "showRevisions",
iconClass: "bx bx-history",
defaultShortcuts: [],
description: t("keyboard_actions.show-revisions"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-recent-changes"),
actionName: "showRecentChanges",
iconClass: "bx bx-history",
defaultShortcuts: [],
description: t("keyboard_actions.show-recent-changes"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-sql-console"),
actionName: "showSQLConsole",
iconClass: "bx bx-data",
defaultShortcuts: ["Alt+O"],
description: t("keyboard_actions.show-sql-console"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-backend-log"),
actionName: "showBackendLog",
iconClass: "bx bx-detail",
defaultShortcuts: [],
description: t("keyboard_actions.show-backend-log"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-help"),
actionName: "showHelp",
iconClass: "bx bx-help-circle",
defaultShortcuts: ["F1"],
description: t("keyboard_actions.show-help"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-cheatsheet"),
actionName: "showCheatsheet",
iconClass: "bx bxs-keyboard",
defaultShortcuts: ["Shift+F1"],
description: t("keyboard_actions.show-cheatsheet"),
scope: "window"
},
{
separator: t("keyboard_actions.text-note-operations")
},
{
friendlyName: t("keyboard_action_names.add-link-to-text"),
actionName: "addLinkToText",
iconClass: "bx bx-link",
defaultShortcuts: ["CommandOrControl+L"],
description: t("keyboard_actions.add-link-to-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.follow-link-under-cursor"),
actionName: "followLinkUnderCursor",
iconClass: "bx bx-link-external",
defaultShortcuts: ["CommandOrControl+Enter"],
description: t("keyboard_actions.follow-link-under-cursor"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.insert-date-and-time-to-text"),
actionName: "insertDateTimeToText",
iconClass: "bx bx-calendar-event",
defaultShortcuts: ["Alt+T"],
description: t("keyboard_actions.insert-date-and-time-to-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.paste-markdown-into-text"),
actionName: "pasteMarkdownIntoText",
iconClass: "bx bxl-markdown",
defaultShortcuts: [],
description: t("keyboard_actions.paste-markdown-into-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.cut-into-note"),
actionName: "cutIntoNote",
iconClass: "bx bx-cut",
defaultShortcuts: [],
description: t("keyboard_actions.cut-into-note"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.add-include-note-to-text"),
actionName: "addIncludeNoteToText",
iconClass: "bx bx-note",
defaultShortcuts: [],
description: t("keyboard_actions.add-include-note-to-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.edit-read-only-note"),
actionName: "editReadOnlyNote",
iconClass: "bx bx-edit-alt",
defaultShortcuts: [],
description: t("keyboard_actions.edit-readonly-note"),
scope: "window"
},
{
separator: t("keyboard_actions.attributes-labels-and-relations")
},
{
friendlyName: t("keyboard_action_names.add-new-label"),
actionName: "addNewLabel",
iconClass: "bx bx-hash",
defaultShortcuts: ["Alt+L"],
description: t("keyboard_actions.add-new-label"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.add-new-relation"),
actionName: "addNewRelation",
iconClass: "bx bx-transfer",
defaultShortcuts: ["Alt+R"],
description: t("keyboard_actions.create-new-relation"),
scope: "window"
},
{
separator: t("keyboard_actions.ribbon-tabs")
},
{
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-classic-editor"),
actionName: "toggleRibbonTabClassicEditor",
iconClass: "bx bx-text",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-classic-editor-toolbar"),
scope: "window"
},
{
actionName: "toggleRibbonTabBasicProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-basic-properties"),
iconClass: "bx bx-slider",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-basic-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabBookProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-book-properties"),
iconClass: "bx bx-book",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-book-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabFileProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-file-properties"),
iconClass: "bx bx-file",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-file-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabImageProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-image-properties"),
iconClass: "bx bx-image",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-image-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabOwnedAttributes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-owned-attributes"),
iconClass: "bx bx-list-check",
defaultShortcuts: ["Alt+A"],
description: t("keyboard_actions.toggle-owned-attributes"),
scope: "window"
},
{
actionName: "toggleRibbonTabInheritedAttributes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-inherited-attributes"),
iconClass: "bx bx-list-plus",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-inherited-attributes"),
scope: "window"
},
// TODO: Remove or change since promoted attributes have been changed.
{
actionName: "toggleRibbonTabPromotedAttributes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-promoted-attributes"),
iconClass: "bx bx-star",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-promoted-attributes"),
scope: "window"
},
{
actionName: "toggleRibbonTabNoteMap",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-note-map"),
iconClass: "bx bxs-network-chart",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-link-map"),
scope: "window"
},
{
actionName: "toggleRibbonTabNoteInfo",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-note-info"),
iconClass: "bx bx-info-circle",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-note-info"),
scope: "window"
},
{
actionName: "toggleRibbonTabNotePaths",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-note-paths"),
iconClass: "bx bx-collection",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-note-paths"),
scope: "window"
},
{
actionName: "toggleRibbonTabSimilarNotes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-similar-notes"),
iconClass: "bx bx-bar-chart",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-similar-notes"),
scope: "window"
},
{
separator: t("keyboard_actions.other")
},
{
actionName: "toggleRightPane",
friendlyName: t("keyboard_action_names.toggle-right-pane"),
iconClass: "bx bx-dock-right",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-right-pane"),
scope: "window"
},
{
actionName: "printActiveNote",
friendlyName: t("keyboard_action_names.print-active-note"),
iconClass: "bx bx-printer",
defaultShortcuts: [],
description: t("keyboard_actions.print-active-note"),
scope: "window"
},
{
actionName: "exportAsPdf",
friendlyName: t("keyboard_action_names.export-active-note-as-pdf"),
iconClass: "bx bxs-file-pdf",
defaultShortcuts: [],
description: t("keyboard_actions.export-as-pdf"),
scope: "window"
},
{
actionName: "openNoteExternally",
friendlyName: t("keyboard_action_names.open-note-externally"),
iconClass: "bx bx-file-find",
defaultShortcuts: [],
description: t("keyboard_actions.open-note-externally"),
scope: "window"
},
{
actionName: "renderActiveNote",
friendlyName: t("keyboard_action_names.render-active-note"),
iconClass: "bx bx-refresh",
defaultShortcuts: [],
description: t("keyboard_actions.render-active-note"),
scope: "window"
},
{
actionName: "runActiveNote",
friendlyName: t("keyboard_action_names.run-active-note"),
iconClass: "bx bx-play",
defaultShortcuts: ["CommandOrControl+Enter"],
description: t("keyboard_actions.run-active-note"),
scope: "code-detail"
},
{
actionName: "toggleNoteHoisting",
friendlyName: t("keyboard_action_names.toggle-note-hoisting"),
iconClass: "bx bx-chevrons-up",
defaultShortcuts: ["Alt+H"],
description: t("keyboard_actions.toggle-note-hoisting"),
scope: "window"
},
{
actionName: "unhoist",
friendlyName: t("keyboard_action_names.unhoist-note"),
iconClass: "bx bx-door-open",
defaultShortcuts: ["Alt+U"],
description: t("keyboard_actions.unhoist"),
scope: "window"
},
{
actionName: "reloadFrontendApp",
friendlyName: t("keyboard_action_names.reload-frontend-app"),
iconClass: "bx bx-refresh",
defaultShortcuts: ["F5", "CommandOrControl+R"],
description: t("keyboard_actions.reload-frontend-app"),
scope: "window"
},
{
actionName: "openDevTools",
friendlyName: t("keyboard_action_names.open-developer-tools"),
iconClass: "bx bx-bug-alt",
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+I"] : [],
isElectronOnly: true,
description: t("keyboard_actions.open-dev-tools"),
scope: "window"
},
{
actionName: "findInText",
friendlyName: t("keyboard_action_names.find-in-text"),
iconClass: "bx bx-search",
defaultShortcuts: isElectron ? ["CommandOrControl+F"] : [],
description: t("keyboard_actions.find-in-text"),
scope: "window"
},
{
actionName: "toggleLeftPane",
friendlyName: t("keyboard_action_names.toggle-left-pane"),
iconClass: "bx bx-sidebar",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-left-note-tree-panel"),
scope: "window"
},
{
actionName: "toggleFullscreen",
friendlyName: t("keyboard_action_names.toggle-full-screen"),
iconClass: "bx bx-fullscreen",
defaultShortcuts: ["F11"],
description: t("keyboard_actions.toggle-full-screen"),
scope: "window"
},
{
actionName: "zoomOut",
friendlyName: t("keyboard_action_names.zoom-out"),
iconClass: "bx bx-zoom-out",
defaultShortcuts: isElectron ? ["CommandOrControl+-"] : [],
isElectronOnly: true,
description: t("keyboard_actions.zoom-out"),
scope: "window"
},
{
actionName: "zoomIn",
friendlyName: t("keyboard_action_names.zoom-in"),
iconClass: "bx bx-zoom-in",
description: t("keyboard_actions.zoom-in"),
defaultShortcuts: isElectron ? ["CommandOrControl+="] : [],
isElectronOnly: true,
scope: "window"
},
{
actionName: "zoomReset",
friendlyName: t("keyboard_action_names.reset-zoom-level"),
iconClass: "bx bx-search-alt",
description: t("keyboard_actions.reset-zoom-level"),
defaultShortcuts: isElectron ? ["CommandOrControl+0"] : [],
isElectronOnly: true,
scope: "window"
},
{
actionName: "copyWithoutFormatting",
friendlyName: t("keyboard_action_names.copy-without-formatting"),
iconClass: "bx bx-copy-alt",
defaultShortcuts: ["CommandOrControl+Alt+C"],
description: t("keyboard_actions.copy-without-formatting"),
scope: "text-detail"
},
{
actionName: "forceSaveRevision",
friendlyName: t("keyboard_action_names.force-save-revision"),
iconClass: "bx bx-save",
defaultShortcuts: [],
description: t("keyboard_actions.force-save-revision"),
scope: "window"
}
];
/*
* Apply macOS-specific tweaks.
*/
const platformModifier = isMac ? "Meta" : "Ctrl";
for (const action of DEFAULT_KEYBOARD_ACTIONS) {
if ("defaultShortcuts" in action && action.defaultShortcuts) {
action.defaultShortcuts = action.defaultShortcuts.map((shortcut) => shortcut.replace("CommandOrControl", platformModifier));
}
}
return DEFAULT_KEYBOARD_ACTIONS;
}
function getKeyboardActions() {
const actions: KeyboardShortcut[] = JSON.parse(JSON.stringify(getDefaultKeyboardActions()));
for (const action of actions) {
if ("effectiveShortcuts" in action && action.effectiveShortcuts) {
action.effectiveShortcuts = action.defaultShortcuts ? action.defaultShortcuts.slice() : [];
}
}
for (const option of optionService.getOptions()) {
if (option.name.startsWith("keyboardShortcuts")) {
let actionName = option.name.substring(17);
actionName = actionName.charAt(0).toLowerCase() + actionName.slice(1);
const action = actions.find((ea) => "actionName" in ea && ea.actionName === actionName) as ActionKeyboardShortcut;
if (action) {
try {
action.effectiveShortcuts = JSON.parse(option.value);
} catch (e) {
log.error(`Could not parse shortcuts for action ${actionName}`);
}
} else {
log.info(`Keyboard action ${actionName} found in database, but not in action definition.`);
}
}
}
return actions;
}
export default {
getDefaultKeyboardActions,
getKeyboardActions
};
import { keyboard_actions } from "@triliumnext/core";
export default keyboard_actions;

View File

@ -1,145 +1,2 @@
/**
* @module
*
* Options are key-value pairs that are used to store information such as user preferences (for example
* the current theme, sync server information), but also information about the state of the application.
*
* Although options internally are represented as strings, their value can be interpreted as a number or
* boolean by calling the appropriate methods from this service (e.g. {@link #getOptionInt}).\
*
* Generally options are shared across multiple instances of the application via the sync mechanism,
* however it is possible to have options that are local to an instance. For example, the user can select
* a theme on a device and it will not affect other devices.
*/
import becca from "../becca/becca.js";
import BOption from "../becca/entities/boption.js";
import type { OptionRow } from "@triliumnext/commons";
import type { FilterOptionsByType, OptionDefinitions, OptionMap, OptionNames } from "@triliumnext/commons";
import sql from "./sql.js";
function getOptionOrNull(name: OptionNames): string | null {
let option;
if (becca.loaded) {
option = becca.getOption(name);
} else {
// e.g. in initial sync becca is not loaded because DB is not initialized
try {
option = sql.getRow<OptionRow>("SELECT * FROM options WHERE name = ?", [name]);
} catch (e: unknown) {
// DB is not initialized.
return null;
}
}
return option ? option.value : null;
}
function getOption(name: OptionNames) {
const val = getOptionOrNull(name);
if (val === null) {
throw new Error(`Option '${name}' doesn't exist`);
}
return val;
}
function getOptionInt(name: FilterOptionsByType<number>, defaultValue?: number): number {
const val = getOption(name);
const intVal = parseInt(val);
if (isNaN(intVal)) {
if (defaultValue === undefined) {
throw new Error(`Could not parse '${val}' into integer for option '${name}'`);
} else {
return defaultValue;
}
}
return intVal;
}
function getOptionBool(name: FilterOptionsByType<boolean>): boolean {
const val = getOption(name);
if (typeof val !== "string" || !["true", "false"].includes(val)) {
throw new Error(`Could not parse '${val}' into boolean for option '${name}'`);
}
return val === "true";
}
function setOption<T extends OptionNames>(name: T, value: string | OptionDefinitions[T]) {
const option = becca.getOption(name);
if (option) {
option.value = value as string;
option.save();
} else {
createOption(name, value, false);
}
// Clear current AI provider when AI-related options change
const aiOptions = [
'aiSelectedProvider', 'openaiApiKey', 'openaiBaseUrl', 'openaiDefaultModel',
'anthropicApiKey', 'anthropicBaseUrl', 'anthropicDefaultModel',
'ollamaBaseUrl', 'ollamaDefaultModel'
];
if (aiOptions.includes(name)) {
// Import dynamically to avoid circular dependencies
setImmediate(async () => {
try {
const aiServiceManager = (await import('./llm/ai_service_manager.js')).default;
aiServiceManager.getInstance().clearCurrentProvider();
console.log(`Cleared AI provider after ${name} option changed`);
} catch (error) {
console.log(`Could not clear AI provider: ${error}`);
}
});
}
}
/**
* Creates a new option in the database, with the given name, value and whether it should be synced.
*
* @param name the name of the option to be created.
* @param value the value of the option, as a string. It can then be interpreted as other types such as a number of boolean.
* @param isSynced `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme).
*/
function createOption<T extends OptionNames>(name: T, value: string | OptionDefinitions[T], isSynced: boolean) {
new BOption({
name: name,
value: value as string,
isSynced: isSynced
}).save();
}
function getOptions() {
return Object.values(becca.options);
}
function getOptionMap() {
const map: Record<string, string> = {};
for (const option of Object.values(becca.options)) {
map[option.name] = option.value;
}
return map as OptionMap;
}
export default {
getOption,
getOptionInt,
getOptionBool,
setOption,
createOption,
getOptions,
getOptionMap,
getOptionOrNull
};
import { options } from "@triliumnext/core";
export default options;

View File

@ -1,280 +1,2 @@
import { type KeyboardShortcutWithRequiredActionName, type OptionMap, type OptionNames, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
import appInfo from "./app_info.js";
import dateUtils from "./date_utils.js";
import keyboardActions from "./keyboard_actions.js";
import log from "./log.js";
import optionService from "./options.js";
import { isWindows, randomSecureToken } from "./utils.js";
function initDocumentOptions() {
optionService.createOption("documentId", randomSecureToken(16), false);
optionService.createOption("documentSecret", randomSecureToken(16), false);
}
/**
* Contains additional options to be initialized for a new database, containing the information entered by the user.
*/
interface NotSyncedOpts {
syncServerHost?: string;
syncProxy?: string;
}
/**
* Represents a correspondence between an option and its default value, to be initialized when the database is missing that particular option (after a migration from an older version, or when creating a new database).
*/
interface DefaultOption {
name: OptionNames;
/**
* The value to initialize the option with, if the option is not already present in the database.
*
* If a function is passed Gin instead, the function is called if the option does not exist (with access to the current options) and the return value is used instead. Useful to migrate a new option with a value depending on some other option that might be initialized.
*/
value: string | ((options: OptionMap) => string);
isSynced: boolean;
}
/**
* Initializes the default options for new databases only.
*
* @param initialized `true` if the database has been fully initialized (i.e. a new database was created), or `false` if the database is created for sync.
* @param opts additional options to be initialized, for example the sync configuration.
*/
async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) {
optionService.createOption(
"openNoteContexts",
JSON.stringify([
{
notePath: "root",
active: true
}
]),
false
);
optionService.createOption("lastDailyBackupDate", dateUtils.utcNowDateTime(), false);
optionService.createOption("lastWeeklyBackupDate", dateUtils.utcNowDateTime(), false);
optionService.createOption("lastMonthlyBackupDate", dateUtils.utcNowDateTime(), false);
optionService.createOption("dbVersion", appInfo.dbVersion.toString(), false);
optionService.createOption("initialized", initialized ? "true" : "false", false);
optionService.createOption("lastSyncedPull", "0", false);
optionService.createOption("lastSyncedPush", "0", false);
optionService.createOption("theme", "next", false);
optionService.createOption("textNoteEditorType", "ckeditor-classic", true);
optionService.createOption("syncServerHost", opts.syncServerHost || "", false);
optionService.createOption("syncServerTimeout", "120000", false);
optionService.createOption("syncProxy", opts.syncProxy || "", false);
}
/**
* Contains all the default options that must be initialized on new and existing databases (at startup). The value can also be determined based on other options, provided they have already been initialized.
*/
const defaultOptions: DefaultOption[] = [
{ name: "revisionSnapshotTimeInterval", value: "600", isSynced: true },
{ name: "revisionSnapshotTimeIntervalTimeScale", value: "60", isSynced: true }, // default to Minutes
{ name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true },
{ name: "protectedSessionTimeout", value: "600", isSynced: true },
{ name: "protectedSessionTimeoutTimeScale", value: "60", isSynced: true },
{ name: "zoomFactor", value: isWindows ? "0.9" : "1.0", isSynced: false },
{ name: "overrideThemeFonts", value: "false", isSynced: false },
{ name: "mainFontFamily", value: "theme", isSynced: false },
{ name: "mainFontSize", value: "100", isSynced: false },
{ name: "treeFontFamily", value: "theme", isSynced: false },
{ name: "treeFontSize", value: "100", isSynced: false },
{ name: "detailFontFamily", value: "theme", isSynced: false },
{ name: "detailFontSize", value: "110", isSynced: false },
{ name: "monospaceFontFamily", value: "theme", isSynced: false },
{ name: "monospaceFontSize", value: "110", isSynced: false },
{ name: "spellCheckEnabled", value: "true", isSynced: false },
{ name: "spellCheckLanguageCode", value: "en-US", isSynced: false },
{ name: "imageMaxWidthHeight", value: "2000", isSynced: true },
{ name: "imageJpegQuality", value: "75", isSynced: true },
{ name: "autoFixConsistencyIssues", value: "true", isSynced: false },
{ name: "vimKeymapEnabled", value: "false", isSynced: false },
{ name: "codeLineWrapEnabled", value: "true", isSynced: false },
{
name: "codeNotesMimeTypes",
value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-elixir","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml","text/x-sh","application/typescript"]',
isSynced: true
},
{ name: "leftPaneWidth", value: "25", isSynced: false },
{ name: "leftPaneVisible", value: "true", isSynced: false },
{ name: "rightPaneWidth", value: "25", isSynced: false },
{ name: "rightPaneVisible", value: "true", isSynced: false },
{ name: "rightPaneCollapsedItems", value: "[]", isSynced: false },
{ name: "nativeTitleBarVisible", value: "false", isSynced: false },
{ name: "eraseEntitiesAfterTimeInSeconds", value: "604800", isSynced: true }, // default is 7 days
{ name: "eraseEntitiesAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
{ name: "hideArchivedNotes_main", value: "false", isSynced: false },
{ name: "debugModeEnabled", value: "false", isSynced: false },
{ name: "headingStyle", value: "underline", isSynced: true },
{ name: "autoCollapseNoteTree", value: "true", isSynced: true },
{ name: "autoReadonlySizeText", value: "32000", isSynced: false },
{ name: "autoReadonlySizeCode", value: "64000", isSynced: false },
{ name: "dailyBackupEnabled", value: "true", isSynced: false },
{ name: "weeklyBackupEnabled", value: "true", isSynced: false },
{ name: "monthlyBackupEnabled", value: "true", isSynced: false },
{ name: "maxContentWidth", value: "1200", isSynced: false },
{ name: "centerContent", value: "false", isSynced: false },
{ name: "compressImages", value: "true", isSynced: true },
{ name: "downloadImagesAutomatically", value: "true", isSynced: true },
{ name: "minTocHeadings", value: "5", isSynced: true },
{ name: "highlightsList", value: '["underline","color","bgColor"]', isSynced: true },
{ name: "checkForUpdates", value: "true", isSynced: true },
{ name: "disableTray", value: "false", isSynced: false },
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
{ name: "logRetentionDays", value: "90", isSynced: false }, // default 90 days
{ name: "customSearchEngineName", value: "DuckDuckGo", isSynced: true },
{ name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true },
{ name: "editedNotesOpenInRibbon", value: "true", isSynced: true },
{ name: "mfaEnabled", value: "false", isSynced: false },
{ name: "mfaMethod", value: "totp", isSynced: false },
{ name: "encryptedRecoveryCodes", value: "false", isSynced: false },
{ name: "userSubjectIdentifierSaved", value: "false", isSynced: false },
// Appearance
{ name: "splitEditorOrientation", value: "horizontal", isSynced: true },
{
name: "codeNoteTheme",
value: (optionsMap) => {
switch (optionsMap.theme) {
case "light":
case "next-light":
return "default:vs-code-light";
case "dark":
case "next-dark":
default:
return "default:vs-code-dark";
}
},
isSynced: false
},
{ name: "motionEnabled", value: "true", isSynced: false },
{ name: "shadowsEnabled", value: "true", isSynced: false },
{ name: "backdropEffectsEnabled", value: "true", isSynced: false },
{ name: "smoothScrollEnabled", value: "true", isSynced: false },
{ name: "newLayout", value: "true", isSynced: true },
// Internationalization
{ name: "locale", value: "en", isSynced: true },
{ name: "formattingLocale", value: "", isSynced: true }, // no value means auto-detect
{ name: "firstDayOfWeek", value: "1", isSynced: true },
{ name: "firstWeekOfYear", value: "0", isSynced: true },
{ name: "minDaysInFirstWeek", value: "4", isSynced: true },
{ name: "languages", value: "[]", isSynced: true },
// Code block configuration
{
name: "codeBlockTheme",
value: (optionsMap) => {
if (optionsMap.theme === "light") {
return "default:stackoverflow-light";
}
return "default:stackoverflow-dark";
},
isSynced: false
},
{ name: "codeBlockWordWrap", value: "false", isSynced: true },
// Text note configuration
{ name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true },
{ name: "textNoteEditorMultilineToolbar", value: "false", isSynced: true },
{ name: "textNoteEmojiCompletionEnabled", value: "true", isSynced: true },
{ name: "textNoteCompletionEnabled", value: "true", isSynced: true },
{ name: "textNoteSlashCommandsEnabled", value: "true", isSynced: true },
// HTML import configuration
{ name: "layoutOrientation", value: "vertical", isSynced: false },
{ name: "backgroundEffects", value: "true", isSynced: false },
{
name: "allowedHtmlTags",
value: JSON.stringify(SANITIZER_DEFAULT_ALLOWED_TAGS),
isSynced: true
},
// Share settings
{ name: "redirectBareDomain", value: "false", isSynced: true },
{ name: "showLoginInShareTheme", value: "false", isSynced: true },
// AI Options
{ name: "aiEnabled", value: "false", isSynced: true },
{ name: "openaiApiKey", value: "", isSynced: false },
{ name: "openaiDefaultModel", value: "", isSynced: true },
{ name: "openaiBaseUrl", value: "https://api.openai.com/v1", isSynced: true },
{ name: "anthropicApiKey", value: "", isSynced: false },
{ name: "anthropicDefaultModel", value: "", isSynced: true },
{ name: "voyageApiKey", value: "", isSynced: false },
{ name: "anthropicBaseUrl", value: "https://api.anthropic.com/v1", isSynced: true },
{ name: "ollamaEnabled", value: "false", isSynced: true },
{ name: "ollamaDefaultModel", value: "", isSynced: true },
{ name: "ollamaBaseUrl", value: "http://localhost:11434", isSynced: true },
{ name: "aiTemperature", value: "0.7", isSynced: true },
{ name: "aiSystemPrompt", value: "", isSynced: true },
{ name: "aiSelectedProvider", value: "openai", isSynced: true },
{
name: "seenCallToActions",
value: JSON.stringify([
"new_layout", "background_effects", "next_theme"
]),
isSynced: true
},
{ name: "experimentalFeatures", value: "[]", isSynced: true }
];
/**
* Initializes the options, by checking which options from {@link #allDefaultOptions()} are missing and registering them. It will also check some environment variables such as safe mode, to make any necessary adjustments.
*
* This method is called regardless of whether a new database is created, or an existing database is used.
*/
function initStartupOptions() {
const optionsMap = optionService.getOptionMap();
const allDefaultOptions = defaultOptions.concat(getKeyboardDefaultOptions());
for (const { name, value, isSynced } of allDefaultOptions) {
if (!(name in optionsMap)) {
let resolvedValue;
if (typeof value === "function") {
resolvedValue = value(optionsMap);
} else {
resolvedValue = value;
}
optionService.createOption(name, resolvedValue, isSynced);
log.info(`Created option "${name}" with default value "${resolvedValue}"`);
}
}
if (process.env.TRILIUM_START_NOTE_ID || process.env.TRILIUM_SAFE_MODE) {
optionService.setOption(
"openNoteContexts",
JSON.stringify([
{
notePath: process.env.TRILIUM_START_NOTE_ID || "root",
active: true
}
])
);
}
}
function getKeyboardDefaultOptions() {
return (keyboardActions.getDefaultKeyboardActions().filter((ka) => "actionName" in ka) as KeyboardShortcutWithRequiredActionName[]).map((ka) => ({
name: `keyboardShortcuts${ka.actionName.charAt(0).toUpperCase()}${ka.actionName.slice(1)}`,
value: JSON.stringify(ka.defaultShortcuts),
isSynced: false
})) as DefaultOption[];
}
export default {
initDocumentOptions,
initNotSyncedOptions,
initStartupOptions
};
import { options_init } from "@triliumnext/core";
export default options_init;

View File

@ -11,11 +11,11 @@ type Response = {
export interface AppInfo {
appVersion: string;
dbVersion: number;
nodeVersion: string;
nodeVersion?: string;
syncVersion: number;
buildDate: string;
buildRevision: string;
dataDirectory: string;
dataDirectory?: string;
clipperProtocolVersion: string;
/** for timezone inference */
utcDateTime: string;

View File

@ -10,9 +10,14 @@ export * as protected_session from "./services/protected_session";
export { default as data_encryption } from "./services/encryption/data_encryption"
export * as binary_utils from "./services/utils/binary";
export * as utils from "./services/utils/index";
export * from "./services/build";
export { default as date_utils } from "./services/utils/date";
export { default as events } from "./services/events";
export { default as blob } from "./services/blob";
export { default as options } from "./services/options";
export { default as options_init } from "./services/options_init";
export { default as app_info } from "./services/app_info";
export { default as keyboard_actions } from "./services/keyboard_actions";
export { getContext, type ExecutionContext } from "./services/context";
export * from "./errors";
export type { CryptoProvider } from "./services/encryption/crypto";

View File

@ -0,0 +1,17 @@
import build from "./build.js";
import packageJson from "../../package.json" with { type: "json" };
import { AppInfo } from "@triliumnext/commons";
const APP_DB_VERSION = 233;
const SYNC_VERSION = 36;
const CLIPPER_PROTOCOL_VERSION = "1.0";
export default {
appVersion: packageJson.version,
dbVersion: APP_DB_VERSION,
syncVersion: SYNC_VERSION,
buildDate: build.buildDate,
buildRevision: build.buildRevision,
clipperProtocolVersion: CLIPPER_PROTOCOL_VERSION,
utcDateTime: new Date().toISOString()
} satisfies AppInfo;

View File

@ -0,0 +1,882 @@
"use strict";
import optionService from "./options.js";
import log from "./log.js";
import { isElectron, isMac } from "./utils.js";
import type { ActionKeyboardShortcut, KeyboardShortcut } from "@triliumnext/commons";
import { t } from "i18next";
function getDefaultKeyboardActions() {
if (!t("keyboard_actions.note-navigation")) {
throw new Error("Keyboard actions loaded before translations.");
}
const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [
{
separator: t("keyboard_actions.note-navigation")
},
{
actionName: "backInNoteHistory",
friendlyName: t("keyboard_action_names.back-in-note-history"),
iconClass: "bx bxs-chevron-left",
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
defaultShortcuts: isMac ? ["CommandOrControl+["] : ["Alt+Left"],
description: t("keyboard_actions.back-in-note-history"),
scope: "window"
},
{
actionName: "forwardInNoteHistory",
friendlyName: t("keyboard_action_names.forward-in-note-history"),
iconClass: "bx bxs-chevron-right",
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
defaultShortcuts: isMac ? ["CommandOrControl+]"] : ["Alt+Right"],
description: t("keyboard_actions.forward-in-note-history"),
scope: "window"
},
{
actionName: "jumpToNote",
friendlyName: t("keyboard_action_names.jump-to-note"),
defaultShortcuts: ["CommandOrControl+J"],
description: t("keyboard_actions.open-jump-to-note-dialog"),
scope: "window",
ignoreFromCommandPalette: true
},
{
actionName: "openTodayNote",
friendlyName: t("hidden-subtree.open-today-journal-note-title"),
iconClass: "bx bx-calendar",
defaultShortcuts: [],
description: t("hidden-subtree.open-today-journal-note-title"),
scope: "window"
},
{
actionName: "commandPalette",
friendlyName: t("keyboard_action_names.command-palette"),
defaultShortcuts: ["CommandOrControl+Shift+J"],
description: t("keyboard_actions.open-command-palette"),
scope: "window",
ignoreFromCommandPalette: true
},
{
actionName: "scrollToActiveNote",
friendlyName: t("keyboard_action_names.scroll-to-active-note"),
defaultShortcuts: ["CommandOrControl+."],
iconClass: "bx bx-current-location",
description: t("keyboard_actions.scroll-to-active-note"),
scope: "window"
},
{
actionName: "quickSearch",
friendlyName: t("keyboard_action_names.quick-search"),
iconClass: "bx bx-search",
defaultShortcuts: ["CommandOrControl+S"],
description: t("keyboard_actions.quick-search"),
scope: "window"
},
{
actionName: "searchInSubtree",
friendlyName: t("keyboard_action_names.search-in-subtree"),
defaultShortcuts: ["CommandOrControl+Shift+S"],
iconClass: "bx bx-search-alt",
description: t("keyboard_actions.search-in-subtree"),
scope: "note-tree"
},
{
actionName: "expandSubtree",
friendlyName: t("keyboard_action_names.expand-subtree"),
defaultShortcuts: [],
iconClass: "bx bx-layer-plus",
description: t("keyboard_actions.expand-subtree"),
scope: "note-tree"
},
{
actionName: "collapseTree",
friendlyName: t("keyboard_action_names.collapse-tree"),
defaultShortcuts: ["Alt+C"],
iconClass: "bx bx-layer-minus",
description: t("keyboard_actions.collapse-tree"),
scope: "window"
},
{
actionName: "collapseSubtree",
friendlyName: t("keyboard_action_names.collapse-subtree"),
iconClass: "bx bxs-layer-minus",
defaultShortcuts: ["Alt+-"],
description: t("keyboard_actions.collapse-subtree"),
scope: "note-tree"
},
{
actionName: "sortChildNotes",
friendlyName: t("keyboard_action_names.sort-child-notes"),
iconClass: "bx bx-sort-down",
defaultShortcuts: ["Alt+S"],
description: t("keyboard_actions.sort-child-notes"),
scope: "note-tree"
},
{
separator: t("keyboard_actions.creating-and-moving-notes")
},
{
actionName: "createNoteAfter",
friendlyName: t("keyboard_action_names.create-note-after"),
iconClass: "bx bx-plus",
defaultShortcuts: ["CommandOrControl+O"],
description: t("keyboard_actions.create-note-after"),
scope: "window"
},
{
actionName: "createNoteInto",
friendlyName: t("keyboard_action_names.create-note-into"),
iconClass: "bx bx-plus",
defaultShortcuts: ["CommandOrControl+P"],
description: t("keyboard_actions.create-note-into"),
scope: "window"
},
{
actionName: "createNoteIntoInbox",
friendlyName: t("keyboard_action_names.create-note-into-inbox"),
iconClass: "bx bxs-inbox",
defaultShortcuts: ["global:CommandOrControl+Alt+P"],
description: t("keyboard_actions.create-note-into-inbox"),
scope: "window"
},
{
actionName: "deleteNotes",
friendlyName: t("keyboard_action_names.delete-notes"),
iconClass: "bx bx-trash",
defaultShortcuts: ["Delete"],
description: t("keyboard_actions.delete-note"),
scope: "note-tree"
},
{
actionName: "moveNoteUp",
friendlyName: t("keyboard_action_names.move-note-up"),
iconClass: "bx bx-up-arrow-alt",
defaultShortcuts: isMac ? ["Alt+Up"] : ["CommandOrControl+Up"],
description: t("keyboard_actions.move-note-up"),
scope: "note-tree"
},
{
actionName: "moveNoteDown",
friendlyName: t("keyboard_action_names.move-note-down"),
iconClass: "bx bx-down-arrow-alt",
defaultShortcuts: isMac ? ["Alt+Down"] : ["CommandOrControl+Down"],
description: t("keyboard_actions.move-note-down"),
scope: "note-tree"
},
{
actionName: "moveNoteUpInHierarchy",
friendlyName: t("keyboard_action_names.move-note-up-in-hierarchy"),
iconClass: "bx bx-arrow-from-bottom",
defaultShortcuts: isMac ? ["Alt+Left"] : ["CommandOrControl+Left"],
description: t("keyboard_actions.move-note-up-in-hierarchy"),
scope: "note-tree"
},
{
actionName: "moveNoteDownInHierarchy",
friendlyName: t("keyboard_action_names.move-note-down-in-hierarchy"),
iconClass: "bx bx-arrow-from-top",
defaultShortcuts: isMac ? ["Alt+Right"] : ["CommandOrControl+Right"],
description: t("keyboard_actions.move-note-down-in-hierarchy"),
scope: "note-tree"
},
{
actionName: "editNoteTitle",
friendlyName: t("keyboard_action_names.edit-note-title"),
iconClass: "bx bx-rename",
defaultShortcuts: ["Enter"],
description: t("keyboard_actions.edit-note-title"),
scope: "note-tree"
},
{
actionName: "editBranchPrefix",
friendlyName: t("keyboard_action_names.edit-branch-prefix"),
iconClass: "bx bx-rename",
defaultShortcuts: ["F2"],
description: t("keyboard_actions.edit-branch-prefix"),
scope: "note-tree"
},
{
actionName: "cloneNotesTo",
friendlyName: t("keyboard_action_names.clone-notes-to"),
iconClass: "bx bx-duplicate",
defaultShortcuts: ["CommandOrControl+Shift+C"],
description: t("keyboard_actions.clone-notes-to"),
scope: "window"
},
{
actionName: "moveNotesTo",
friendlyName: t("keyboard_action_names.move-notes-to"),
iconClass: "bx bx-transfer",
defaultShortcuts: ["CommandOrControl+Shift+X"],
description: t("keyboard_actions.move-notes-to"),
scope: "window"
},
{
separator: t("keyboard_actions.note-clipboard")
},
{
actionName: "copyNotesToClipboard",
friendlyName: t("keyboard_action_names.copy-notes-to-clipboard"),
iconClass: "bx bx-copy",
defaultShortcuts: ["CommandOrControl+C"],
description: t("keyboard_actions.copy-notes-to-clipboard"),
scope: "note-tree"
},
{
actionName: "pasteNotesFromClipboard",
friendlyName: t("keyboard_action_names.paste-notes-from-clipboard"),
iconClass: "bx bx-paste",
defaultShortcuts: ["CommandOrControl+V"],
description: t("keyboard_actions.paste-notes-from-clipboard"),
scope: "note-tree"
},
{
actionName: "cutNotesToClipboard",
friendlyName: t("keyboard_action_names.cut-notes-to-clipboard"),
iconClass: "bx bx-cut",
defaultShortcuts: ["CommandOrControl+X"],
description: t("keyboard_actions.cut-notes-to-clipboard"),
scope: "note-tree"
},
{
actionName: "selectAllNotesInParent",
friendlyName: t("keyboard_action_names.select-all-notes-in-parent"),
iconClass: "bx bx-select-multiple",
defaultShortcuts: ["CommandOrControl+A"],
description: t("keyboard_actions.select-all-notes-in-parent"),
scope: "note-tree"
},
{
actionName: "addNoteAboveToSelection",
friendlyName: t("keyboard_action_names.add-note-above-to-selection"),
defaultShortcuts: ["Shift+Up"],
description: t("keyboard_actions.add-note-above-to-the-selection"),
scope: "note-tree",
ignoreFromCommandPalette: true
},
{
actionName: "addNoteBelowToSelection",
friendlyName: t("keyboard_action_names.add-note-below-to-selection"),
defaultShortcuts: ["Shift+Down"],
description: t("keyboard_actions.add-note-below-to-selection"),
scope: "note-tree",
ignoreFromCommandPalette: true
},
{
actionName: "duplicateSubtree",
friendlyName: t("keyboard_action_names.duplicate-subtree"),
iconClass: "bx bx-outline",
defaultShortcuts: [],
description: t("keyboard_actions.duplicate-subtree"),
scope: "note-tree"
},
{
separator: t("keyboard_actions.tabs-and-windows")
},
{
actionName: "openNewTab",
friendlyName: t("keyboard_action_names.open-new-tab"),
iconClass: "bx bx-plus",
defaultShortcuts: isElectron ? ["CommandOrControl+T"] : [],
description: t("keyboard_actions.open-new-tab"),
scope: "window"
},
{
actionName: "closeActiveTab",
friendlyName: t("keyboard_action_names.close-active-tab"),
iconClass: "bx bx-minus",
defaultShortcuts: isElectron ? ["CommandOrControl+W"] : [],
description: t("keyboard_actions.close-active-tab"),
scope: "window"
},
{
actionName: "reopenLastTab",
friendlyName: t("keyboard_action_names.reopen-last-tab"),
iconClass: "bx bx-undo",
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+T"] : [],
isElectronOnly: true,
description: t("keyboard_actions.reopen-last-tab"),
scope: "window"
},
{
actionName: "activateNextTab",
friendlyName: t("keyboard_action_names.activate-next-tab"),
iconClass: "bx bx-skip-next",
defaultShortcuts: isElectron ? ["CommandOrControl+Tab", "CommandOrControl+PageDown"] : [],
description: t("keyboard_actions.activate-next-tab"),
scope: "window"
},
{
actionName: "activatePreviousTab",
friendlyName: t("keyboard_action_names.activate-previous-tab"),
iconClass: "bx bx-skip-previous",
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+Tab", "CommandOrControl+PageUp"] : [],
description: t("keyboard_actions.activate-previous-tab"),
scope: "window"
},
{
actionName: "openNewWindow",
friendlyName: t("keyboard_action_names.open-new-window"),
iconClass: "bx bx-window-open",
defaultShortcuts: [],
description: t("keyboard_actions.open-new-window"),
scope: "window"
},
{
actionName: "toggleTray",
friendlyName: t("keyboard_action_names.toggle-system-tray-icon"),
iconClass: "bx bx-show",
defaultShortcuts: [],
isElectronOnly: true,
description: t("keyboard_actions.toggle-tray"),
scope: "window"
},
{
actionName: "toggleZenMode",
friendlyName: t("keyboard_action_names.toggle-zen-mode"),
iconClass: "bx bxs-yin-yang",
defaultShortcuts: ["F9"],
description: t("keyboard_actions.toggle-zen-mode"),
scope: "window"
},
{
actionName: "firstTab",
friendlyName: t("keyboard_action_names.switch-to-first-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+1"],
description: t("keyboard_actions.first-tab"),
scope: "window"
},
{
actionName: "secondTab",
friendlyName: t("keyboard_action_names.switch-to-second-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+2"],
description: t("keyboard_actions.second-tab"),
scope: "window"
},
{
actionName: "thirdTab",
friendlyName: t("keyboard_action_names.switch-to-third-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+3"],
description: t("keyboard_actions.third-tab"),
scope: "window"
},
{
actionName: "fourthTab",
friendlyName: t("keyboard_action_names.switch-to-fourth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+4"],
description: t("keyboard_actions.fourth-tab"),
scope: "window"
},
{
actionName: "fifthTab",
friendlyName: t("keyboard_action_names.switch-to-fifth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+5"],
description: t("keyboard_actions.fifth-tab"),
scope: "window"
},
{
actionName: "sixthTab",
friendlyName: t("keyboard_action_names.switch-to-sixth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+6"],
description: t("keyboard_actions.sixth-tab"),
scope: "window"
},
{
actionName: "seventhTab",
friendlyName: t("keyboard_action_names.switch-to-seventh-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+7"],
description: t("keyboard_actions.seventh-tab"),
scope: "window"
},
{
actionName: "eigthTab",
friendlyName: t("keyboard_action_names.switch-to-eighth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+8"],
description: t("keyboard_actions.eight-tab"),
scope: "window"
},
{
actionName: "ninthTab",
friendlyName: t("keyboard_action_names.switch-to-ninth-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+9"],
description: t("keyboard_actions.ninth-tab"),
scope: "window"
},
{
actionName: "lastTab",
friendlyName: t("keyboard_action_names.switch-to-last-tab"),
iconClass: "bx bx-rectangle",
defaultShortcuts: ["CommandOrControl+0"],
description: t("keyboard_actions.last-tab"),
scope: "window"
},
{
separator: t("keyboard_actions.dialogs")
},
{
friendlyName: t("keyboard_action_names.show-note-source"),
actionName: "showNoteSource",
iconClass: "bx bx-code",
defaultShortcuts: [],
description: t("keyboard_actions.show-note-source"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-options"),
actionName: "showOptions",
iconClass: "bx bx-cog",
defaultShortcuts: [],
description: t("keyboard_actions.show-options"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-revisions"),
actionName: "showRevisions",
iconClass: "bx bx-history",
defaultShortcuts: [],
description: t("keyboard_actions.show-revisions"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-recent-changes"),
actionName: "showRecentChanges",
iconClass: "bx bx-history",
defaultShortcuts: [],
description: t("keyboard_actions.show-recent-changes"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-sql-console"),
actionName: "showSQLConsole",
iconClass: "bx bx-data",
defaultShortcuts: ["Alt+O"],
description: t("keyboard_actions.show-sql-console"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-backend-log"),
actionName: "showBackendLog",
iconClass: "bx bx-detail",
defaultShortcuts: [],
description: t("keyboard_actions.show-backend-log"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-help"),
actionName: "showHelp",
iconClass: "bx bx-help-circle",
defaultShortcuts: ["F1"],
description: t("keyboard_actions.show-help"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.show-cheatsheet"),
actionName: "showCheatsheet",
iconClass: "bx bxs-keyboard",
defaultShortcuts: ["Shift+F1"],
description: t("keyboard_actions.show-cheatsheet"),
scope: "window"
},
{
separator: t("keyboard_actions.text-note-operations")
},
{
friendlyName: t("keyboard_action_names.add-link-to-text"),
actionName: "addLinkToText",
iconClass: "bx bx-link",
defaultShortcuts: ["CommandOrControl+L"],
description: t("keyboard_actions.add-link-to-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.follow-link-under-cursor"),
actionName: "followLinkUnderCursor",
iconClass: "bx bx-link-external",
defaultShortcuts: ["CommandOrControl+Enter"],
description: t("keyboard_actions.follow-link-under-cursor"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.insert-date-and-time-to-text"),
actionName: "insertDateTimeToText",
iconClass: "bx bx-calendar-event",
defaultShortcuts: ["Alt+T"],
description: t("keyboard_actions.insert-date-and-time-to-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.paste-markdown-into-text"),
actionName: "pasteMarkdownIntoText",
iconClass: "bx bxl-markdown",
defaultShortcuts: [],
description: t("keyboard_actions.paste-markdown-into-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.cut-into-note"),
actionName: "cutIntoNote",
iconClass: "bx bx-cut",
defaultShortcuts: [],
description: t("keyboard_actions.cut-into-note"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.add-include-note-to-text"),
actionName: "addIncludeNoteToText",
iconClass: "bx bx-note",
defaultShortcuts: [],
description: t("keyboard_actions.add-include-note-to-text"),
scope: "text-detail"
},
{
friendlyName: t("keyboard_action_names.edit-read-only-note"),
actionName: "editReadOnlyNote",
iconClass: "bx bx-edit-alt",
defaultShortcuts: [],
description: t("keyboard_actions.edit-readonly-note"),
scope: "window"
},
{
separator: t("keyboard_actions.attributes-labels-and-relations")
},
{
friendlyName: t("keyboard_action_names.add-new-label"),
actionName: "addNewLabel",
iconClass: "bx bx-hash",
defaultShortcuts: ["Alt+L"],
description: t("keyboard_actions.add-new-label"),
scope: "window"
},
{
friendlyName: t("keyboard_action_names.add-new-relation"),
actionName: "addNewRelation",
iconClass: "bx bx-transfer",
defaultShortcuts: ["Alt+R"],
description: t("keyboard_actions.create-new-relation"),
scope: "window"
},
{
separator: t("keyboard_actions.ribbon-tabs")
},
{
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-classic-editor"),
actionName: "toggleRibbonTabClassicEditor",
iconClass: "bx bx-text",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-classic-editor-toolbar"),
scope: "window"
},
{
actionName: "toggleRibbonTabBasicProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-basic-properties"),
iconClass: "bx bx-slider",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-basic-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabBookProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-book-properties"),
iconClass: "bx bx-book",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-book-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabFileProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-file-properties"),
iconClass: "bx bx-file",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-file-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabImageProperties",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-image-properties"),
iconClass: "bx bx-image",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-image-properties"),
scope: "window"
},
{
actionName: "toggleRibbonTabOwnedAttributes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-owned-attributes"),
iconClass: "bx bx-list-check",
defaultShortcuts: ["Alt+A"],
description: t("keyboard_actions.toggle-owned-attributes"),
scope: "window"
},
{
actionName: "toggleRibbonTabInheritedAttributes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-inherited-attributes"),
iconClass: "bx bx-list-plus",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-inherited-attributes"),
scope: "window"
},
// TODO: Remove or change since promoted attributes have been changed.
{
actionName: "toggleRibbonTabPromotedAttributes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-promoted-attributes"),
iconClass: "bx bx-star",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-promoted-attributes"),
scope: "window"
},
{
actionName: "toggleRibbonTabNoteMap",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-note-map"),
iconClass: "bx bxs-network-chart",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-link-map"),
scope: "window"
},
{
actionName: "toggleRibbonTabNoteInfo",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-note-info"),
iconClass: "bx bx-info-circle",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-note-info"),
scope: "window"
},
{
actionName: "toggleRibbonTabNotePaths",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-note-paths"),
iconClass: "bx bx-collection",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-note-paths"),
scope: "window"
},
{
actionName: "toggleRibbonTabSimilarNotes",
friendlyName: t("keyboard_action_names.toggle-ribbon-tab-similar-notes"),
iconClass: "bx bx-bar-chart",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-similar-notes"),
scope: "window"
},
{
separator: t("keyboard_actions.other")
},
{
actionName: "toggleRightPane",
friendlyName: t("keyboard_action_names.toggle-right-pane"),
iconClass: "bx bx-dock-right",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-right-pane"),
scope: "window"
},
{
actionName: "printActiveNote",
friendlyName: t("keyboard_action_names.print-active-note"),
iconClass: "bx bx-printer",
defaultShortcuts: [],
description: t("keyboard_actions.print-active-note"),
scope: "window"
},
{
actionName: "exportAsPdf",
friendlyName: t("keyboard_action_names.export-active-note-as-pdf"),
iconClass: "bx bxs-file-pdf",
defaultShortcuts: [],
description: t("keyboard_actions.export-as-pdf"),
scope: "window"
},
{
actionName: "openNoteExternally",
friendlyName: t("keyboard_action_names.open-note-externally"),
iconClass: "bx bx-file-find",
defaultShortcuts: [],
description: t("keyboard_actions.open-note-externally"),
scope: "window"
},
{
actionName: "renderActiveNote",
friendlyName: t("keyboard_action_names.render-active-note"),
iconClass: "bx bx-refresh",
defaultShortcuts: [],
description: t("keyboard_actions.render-active-note"),
scope: "window"
},
{
actionName: "runActiveNote",
friendlyName: t("keyboard_action_names.run-active-note"),
iconClass: "bx bx-play",
defaultShortcuts: ["CommandOrControl+Enter"],
description: t("keyboard_actions.run-active-note"),
scope: "code-detail"
},
{
actionName: "toggleNoteHoisting",
friendlyName: t("keyboard_action_names.toggle-note-hoisting"),
iconClass: "bx bx-chevrons-up",
defaultShortcuts: ["Alt+H"],
description: t("keyboard_actions.toggle-note-hoisting"),
scope: "window"
},
{
actionName: "unhoist",
friendlyName: t("keyboard_action_names.unhoist-note"),
iconClass: "bx bx-door-open",
defaultShortcuts: ["Alt+U"],
description: t("keyboard_actions.unhoist"),
scope: "window"
},
{
actionName: "reloadFrontendApp",
friendlyName: t("keyboard_action_names.reload-frontend-app"),
iconClass: "bx bx-refresh",
defaultShortcuts: ["F5", "CommandOrControl+R"],
description: t("keyboard_actions.reload-frontend-app"),
scope: "window"
},
{
actionName: "openDevTools",
friendlyName: t("keyboard_action_names.open-developer-tools"),
iconClass: "bx bx-bug-alt",
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+I"] : [],
isElectronOnly: true,
description: t("keyboard_actions.open-dev-tools"),
scope: "window"
},
{
actionName: "findInText",
friendlyName: t("keyboard_action_names.find-in-text"),
iconClass: "bx bx-search",
defaultShortcuts: isElectron ? ["CommandOrControl+F"] : [],
description: t("keyboard_actions.find-in-text"),
scope: "window"
},
{
actionName: "toggleLeftPane",
friendlyName: t("keyboard_action_names.toggle-left-pane"),
iconClass: "bx bx-sidebar",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-left-note-tree-panel"),
scope: "window"
},
{
actionName: "toggleFullscreen",
friendlyName: t("keyboard_action_names.toggle-full-screen"),
iconClass: "bx bx-fullscreen",
defaultShortcuts: ["F11"],
description: t("keyboard_actions.toggle-full-screen"),
scope: "window"
},
{
actionName: "zoomOut",
friendlyName: t("keyboard_action_names.zoom-out"),
iconClass: "bx bx-zoom-out",
defaultShortcuts: isElectron ? ["CommandOrControl+-"] : [],
isElectronOnly: true,
description: t("keyboard_actions.zoom-out"),
scope: "window"
},
{
actionName: "zoomIn",
friendlyName: t("keyboard_action_names.zoom-in"),
iconClass: "bx bx-zoom-in",
description: t("keyboard_actions.zoom-in"),
defaultShortcuts: isElectron ? ["CommandOrControl+="] : [],
isElectronOnly: true,
scope: "window"
},
{
actionName: "zoomReset",
friendlyName: t("keyboard_action_names.reset-zoom-level"),
iconClass: "bx bx-search-alt",
description: t("keyboard_actions.reset-zoom-level"),
defaultShortcuts: isElectron ? ["CommandOrControl+0"] : [],
isElectronOnly: true,
scope: "window"
},
{
actionName: "copyWithoutFormatting",
friendlyName: t("keyboard_action_names.copy-without-formatting"),
iconClass: "bx bx-copy-alt",
defaultShortcuts: ["CommandOrControl+Alt+C"],
description: t("keyboard_actions.copy-without-formatting"),
scope: "text-detail"
},
{
actionName: "forceSaveRevision",
friendlyName: t("keyboard_action_names.force-save-revision"),
iconClass: "bx bx-save",
defaultShortcuts: [],
description: t("keyboard_actions.force-save-revision"),
scope: "window"
}
];
/*
* Apply macOS-specific tweaks.
*/
const platformModifier = isMac ? "Meta" : "Ctrl";
for (const action of DEFAULT_KEYBOARD_ACTIONS) {
if ("defaultShortcuts" in action && action.defaultShortcuts) {
action.defaultShortcuts = action.defaultShortcuts.map((shortcut) => shortcut.replace("CommandOrControl", platformModifier));
}
}
return DEFAULT_KEYBOARD_ACTIONS;
}
function getKeyboardActions() {
const actions: KeyboardShortcut[] = JSON.parse(JSON.stringify(getDefaultKeyboardActions()));
for (const action of actions) {
if ("effectiveShortcuts" in action && action.effectiveShortcuts) {
action.effectiveShortcuts = action.defaultShortcuts ? action.defaultShortcuts.slice() : [];
}
}
for (const option of optionService.getOptions()) {
if (option.name.startsWith("keyboardShortcuts")) {
let actionName = option.name.substring(17);
actionName = actionName.charAt(0).toLowerCase() + actionName.slice(1);
const action = actions.find((ea) => "actionName" in ea && ea.actionName === actionName) as ActionKeyboardShortcut;
if (action) {
try {
action.effectiveShortcuts = JSON.parse(option.value);
} catch (e) {
log.error(`Could not parse shortcuts for action ${actionName}`);
}
} else {
log.info(`Keyboard action ${actionName} found in database, but not in action definition.`);
}
}
}
return actions;
}
export default {
getDefaultKeyboardActions,
getKeyboardActions
};

View File

@ -0,0 +1,146 @@
/**
* @module
*
* Options are key-value pairs that are used to store information such as user preferences (for example
* the current theme, sync server information), but also information about the state of the application.
*
* Although options internally are represented as strings, their value can be interpreted as a number or
* boolean by calling the appropriate methods from this service (e.g. {@link #getOptionInt}).\
*
* Generally options are shared across multiple instances of the application via the sync mechanism,
* however it is possible to have options that are local to an instance. For example, the user can select
* a theme on a device and it will not affect other devices.
*/
import becca from "../becca/becca.js";
import BOption from "../becca/entities/boption.js";
import type { OptionRow } from "@triliumnext/commons";
import type { FilterOptionsByType, OptionDefinitions, OptionMap, OptionNames } from "@triliumnext/commons";
import { getSql } from "./sql/index.js";
function getOptionOrNull(name: OptionNames): string | null {
let option;
if (becca.loaded) {
option = becca.getOption(name);
} else {
// e.g. in initial sync becca is not loaded because DB is not initialized
try {
option = getSql().getRow<OptionRow>("SELECT * FROM options WHERE name = ?", [name]);
} catch (e: unknown) {
// DB is not initialized.
return null;
}
}
return option ? option.value : null;
}
function getOption(name: OptionNames) {
const val = getOptionOrNull(name);
if (val === null) {
throw new Error(`Option '${name}' doesn't exist`);
}
return val;
}
function getOptionInt(name: FilterOptionsByType<number>, defaultValue?: number): number {
const val = getOption(name);
const intVal = parseInt(val);
if (isNaN(intVal)) {
if (defaultValue === undefined) {
throw new Error(`Could not parse '${val}' into integer for option '${name}'`);
} else {
return defaultValue;
}
}
return intVal;
}
function getOptionBool(name: FilterOptionsByType<boolean>): boolean {
const val = getOption(name);
if (typeof val !== "string" || !["true", "false"].includes(val)) {
throw new Error(`Could not parse '${val}' into boolean for option '${name}'`);
}
return val === "true";
}
function setOption<T extends OptionNames>(name: T, value: string | OptionDefinitions[T]) {
const option = becca.getOption(name);
if (option) {
option.value = value as string;
option.save();
} else {
createOption(name, value, false);
}
// Clear current AI provider when AI-related options change
const aiOptions = [
'aiSelectedProvider', 'openaiApiKey', 'openaiBaseUrl', 'openaiDefaultModel',
'anthropicApiKey', 'anthropicBaseUrl', 'anthropicDefaultModel',
'ollamaBaseUrl', 'ollamaDefaultModel'
];
// TODO: Disabled AI integration.
// if (aiOptions.includes(name)) {
// // Import dynamically to avoid circular dependencies
// setImmediate(async () => {
// try {
// const aiServiceManager = (await import('./llm/ai_service_manager.js')).default;
// aiServiceManager.getInstance().clearCurrentProvider();
// console.log(`Cleared AI provider after ${name} option changed`);
// } catch (error) {
// console.log(`Could not clear AI provider: ${error}`);
// }
// });
// }
}
/**
* Creates a new option in the database, with the given name, value and whether it should be synced.
*
* @param name the name of the option to be created.
* @param value the value of the option, as a string. It can then be interpreted as other types such as a number of boolean.
* @param isSynced `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme).
*/
function createOption<T extends OptionNames>(name: T, value: string | OptionDefinitions[T], isSynced: boolean) {
new BOption({
name: name,
value: value as string,
isSynced: isSynced
}).save();
}
function getOptions() {
return Object.values(becca.options);
}
function getOptionMap() {
const map: Record<string, string> = {};
for (const option of Object.values(becca.options)) {
map[option.name] = option.value;
}
return map as OptionMap;
}
export default {
getOption,
getOptionInt,
getOptionBool,
setOption,
createOption,
getOptions,
getOptionMap,
getOptionOrNull
};

View File

@ -0,0 +1,280 @@
import { type KeyboardShortcutWithRequiredActionName, type OptionMap, type OptionNames, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
import appInfo from "./app_info.js";
import dateUtils from "./utils/date.js";
import keyboardActions from "./keyboard_actions.js";
import log from "./log.js";
import optionService from "./options.js";
import { isWindows, randomSecureToken } from "./utils.js";
function initDocumentOptions() {
optionService.createOption("documentId", randomSecureToken(16), false);
optionService.createOption("documentSecret", randomSecureToken(16), false);
}
/**
* Contains additional options to be initialized for a new database, containing the information entered by the user.
*/
interface NotSyncedOpts {
syncServerHost?: string;
syncProxy?: string;
}
/**
* Represents a correspondence between an option and its default value, to be initialized when the database is missing that particular option (after a migration from an older version, or when creating a new database).
*/
interface DefaultOption {
name: OptionNames;
/**
* The value to initialize the option with, if the option is not already present in the database.
*
* If a function is passed Gin instead, the function is called if the option does not exist (with access to the current options) and the return value is used instead. Useful to migrate a new option with a value depending on some other option that might be initialized.
*/
value: string | ((options: OptionMap) => string);
isSynced: boolean;
}
/**
* Initializes the default options for new databases only.
*
* @param initialized `true` if the database has been fully initialized (i.e. a new database was created), or `false` if the database is created for sync.
* @param opts additional options to be initialized, for example the sync configuration.
*/
async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) {
optionService.createOption(
"openNoteContexts",
JSON.stringify([
{
notePath: "root",
active: true
}
]),
false
);
optionService.createOption("lastDailyBackupDate", dateUtils.utcNowDateTime(), false);
optionService.createOption("lastWeeklyBackupDate", dateUtils.utcNowDateTime(), false);
optionService.createOption("lastMonthlyBackupDate", dateUtils.utcNowDateTime(), false);
optionService.createOption("dbVersion", appInfo.dbVersion.toString(), false);
optionService.createOption("initialized", initialized ? "true" : "false", false);
optionService.createOption("lastSyncedPull", "0", false);
optionService.createOption("lastSyncedPush", "0", false);
optionService.createOption("theme", "next", false);
optionService.createOption("textNoteEditorType", "ckeditor-classic", true);
optionService.createOption("syncServerHost", opts.syncServerHost || "", false);
optionService.createOption("syncServerTimeout", "120000", false);
optionService.createOption("syncProxy", opts.syncProxy || "", false);
}
/**
* Contains all the default options that must be initialized on new and existing databases (at startup). The value can also be determined based on other options, provided they have already been initialized.
*/
const defaultOptions: DefaultOption[] = [
{ name: "revisionSnapshotTimeInterval", value: "600", isSynced: true },
{ name: "revisionSnapshotTimeIntervalTimeScale", value: "60", isSynced: true }, // default to Minutes
{ name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true },
{ name: "protectedSessionTimeout", value: "600", isSynced: true },
{ name: "protectedSessionTimeoutTimeScale", value: "60", isSynced: true },
{ name: "zoomFactor", value: isWindows ? "0.9" : "1.0", isSynced: false },
{ name: "overrideThemeFonts", value: "false", isSynced: false },
{ name: "mainFontFamily", value: "theme", isSynced: false },
{ name: "mainFontSize", value: "100", isSynced: false },
{ name: "treeFontFamily", value: "theme", isSynced: false },
{ name: "treeFontSize", value: "100", isSynced: false },
{ name: "detailFontFamily", value: "theme", isSynced: false },
{ name: "detailFontSize", value: "110", isSynced: false },
{ name: "monospaceFontFamily", value: "theme", isSynced: false },
{ name: "monospaceFontSize", value: "110", isSynced: false },
{ name: "spellCheckEnabled", value: "true", isSynced: false },
{ name: "spellCheckLanguageCode", value: "en-US", isSynced: false },
{ name: "imageMaxWidthHeight", value: "2000", isSynced: true },
{ name: "imageJpegQuality", value: "75", isSynced: true },
{ name: "autoFixConsistencyIssues", value: "true", isSynced: false },
{ name: "vimKeymapEnabled", value: "false", isSynced: false },
{ name: "codeLineWrapEnabled", value: "true", isSynced: false },
{
name: "codeNotesMimeTypes",
value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-elixir","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml","text/x-sh","application/typescript"]',
isSynced: true
},
{ name: "leftPaneWidth", value: "25", isSynced: false },
{ name: "leftPaneVisible", value: "true", isSynced: false },
{ name: "rightPaneWidth", value: "25", isSynced: false },
{ name: "rightPaneVisible", value: "true", isSynced: false },
{ name: "rightPaneCollapsedItems", value: "[]", isSynced: false },
{ name: "nativeTitleBarVisible", value: "false", isSynced: false },
{ name: "eraseEntitiesAfterTimeInSeconds", value: "604800", isSynced: true }, // default is 7 days
{ name: "eraseEntitiesAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
{ name: "hideArchivedNotes_main", value: "false", isSynced: false },
{ name: "debugModeEnabled", value: "false", isSynced: false },
{ name: "headingStyle", value: "underline", isSynced: true },
{ name: "autoCollapseNoteTree", value: "true", isSynced: true },
{ name: "autoReadonlySizeText", value: "32000", isSynced: false },
{ name: "autoReadonlySizeCode", value: "64000", isSynced: false },
{ name: "dailyBackupEnabled", value: "true", isSynced: false },
{ name: "weeklyBackupEnabled", value: "true", isSynced: false },
{ name: "monthlyBackupEnabled", value: "true", isSynced: false },
{ name: "maxContentWidth", value: "1200", isSynced: false },
{ name: "centerContent", value: "false", isSynced: false },
{ name: "compressImages", value: "true", isSynced: true },
{ name: "downloadImagesAutomatically", value: "true", isSynced: true },
{ name: "minTocHeadings", value: "5", isSynced: true },
{ name: "highlightsList", value: '["underline","color","bgColor"]', isSynced: true },
{ name: "checkForUpdates", value: "true", isSynced: true },
{ name: "disableTray", value: "false", isSynced: false },
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
{ name: "logRetentionDays", value: "90", isSynced: false }, // default 90 days
{ name: "customSearchEngineName", value: "DuckDuckGo", isSynced: true },
{ name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true },
{ name: "editedNotesOpenInRibbon", value: "true", isSynced: true },
{ name: "mfaEnabled", value: "false", isSynced: false },
{ name: "mfaMethod", value: "totp", isSynced: false },
{ name: "encryptedRecoveryCodes", value: "false", isSynced: false },
{ name: "userSubjectIdentifierSaved", value: "false", isSynced: false },
// Appearance
{ name: "splitEditorOrientation", value: "horizontal", isSynced: true },
{
name: "codeNoteTheme",
value: (optionsMap) => {
switch (optionsMap.theme) {
case "light":
case "next-light":
return "default:vs-code-light";
case "dark":
case "next-dark":
default:
return "default:vs-code-dark";
}
},
isSynced: false
},
{ name: "motionEnabled", value: "true", isSynced: false },
{ name: "shadowsEnabled", value: "true", isSynced: false },
{ name: "backdropEffectsEnabled", value: "true", isSynced: false },
{ name: "smoothScrollEnabled", value: "true", isSynced: false },
{ name: "newLayout", value: "true", isSynced: true },
// Internationalization
{ name: "locale", value: "en", isSynced: true },
{ name: "formattingLocale", value: "", isSynced: true }, // no value means auto-detect
{ name: "firstDayOfWeek", value: "1", isSynced: true },
{ name: "firstWeekOfYear", value: "0", isSynced: true },
{ name: "minDaysInFirstWeek", value: "4", isSynced: true },
{ name: "languages", value: "[]", isSynced: true },
// Code block configuration
{
name: "codeBlockTheme",
value: (optionsMap) => {
if (optionsMap.theme === "light") {
return "default:stackoverflow-light";
}
return "default:stackoverflow-dark";
},
isSynced: false
},
{ name: "codeBlockWordWrap", value: "false", isSynced: true },
// Text note configuration
{ name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true },
{ name: "textNoteEditorMultilineToolbar", value: "false", isSynced: true },
{ name: "textNoteEmojiCompletionEnabled", value: "true", isSynced: true },
{ name: "textNoteCompletionEnabled", value: "true", isSynced: true },
{ name: "textNoteSlashCommandsEnabled", value: "true", isSynced: true },
// HTML import configuration
{ name: "layoutOrientation", value: "vertical", isSynced: false },
{ name: "backgroundEffects", value: "true", isSynced: false },
{
name: "allowedHtmlTags",
value: JSON.stringify(SANITIZER_DEFAULT_ALLOWED_TAGS),
isSynced: true
},
// Share settings
{ name: "redirectBareDomain", value: "false", isSynced: true },
{ name: "showLoginInShareTheme", value: "false", isSynced: true },
// AI Options
{ name: "aiEnabled", value: "false", isSynced: true },
{ name: "openaiApiKey", value: "", isSynced: false },
{ name: "openaiDefaultModel", value: "", isSynced: true },
{ name: "openaiBaseUrl", value: "https://api.openai.com/v1", isSynced: true },
{ name: "anthropicApiKey", value: "", isSynced: false },
{ name: "anthropicDefaultModel", value: "", isSynced: true },
{ name: "voyageApiKey", value: "", isSynced: false },
{ name: "anthropicBaseUrl", value: "https://api.anthropic.com/v1", isSynced: true },
{ name: "ollamaEnabled", value: "false", isSynced: true },
{ name: "ollamaDefaultModel", value: "", isSynced: true },
{ name: "ollamaBaseUrl", value: "http://localhost:11434", isSynced: true },
{ name: "aiTemperature", value: "0.7", isSynced: true },
{ name: "aiSystemPrompt", value: "", isSynced: true },
{ name: "aiSelectedProvider", value: "openai", isSynced: true },
{
name: "seenCallToActions",
value: JSON.stringify([
"new_layout", "background_effects", "next_theme"
]),
isSynced: true
},
{ name: "experimentalFeatures", value: "[]", isSynced: true }
];
/**
* Initializes the options, by checking which options from {@link #allDefaultOptions()} are missing and registering them. It will also check some environment variables such as safe mode, to make any necessary adjustments.
*
* This method is called regardless of whether a new database is created, or an existing database is used.
*/
function initStartupOptions() {
const optionsMap = optionService.getOptionMap();
const allDefaultOptions = defaultOptions.concat(getKeyboardDefaultOptions());
for (const { name, value, isSynced } of allDefaultOptions) {
if (!(name in optionsMap)) {
let resolvedValue;
if (typeof value === "function") {
resolvedValue = value(optionsMap);
} else {
resolvedValue = value;
}
optionService.createOption(name, resolvedValue, isSynced);
log.info(`Created option "${name}" with default value "${resolvedValue}"`);
}
}
if (process.env.TRILIUM_START_NOTE_ID || process.env.TRILIUM_SAFE_MODE) {
optionService.setOption(
"openNoteContexts",
JSON.stringify([
{
notePath: process.env.TRILIUM_START_NOTE_ID || "root",
active: true
}
])
);
}
}
function getKeyboardDefaultOptions() {
return (keyboardActions.getDefaultKeyboardActions().filter((ka) => "actionName" in ka) as KeyboardShortcutWithRequiredActionName[]).map((ka) => ({
name: `keyboardShortcuts${ka.actionName.charAt(0).toUpperCase()}${ka.actionName.slice(1)}`,
value: JSON.stringify(ka.defaultShortcuts),
isSynced: false
})) as DefaultOption[];
}
export default {
initDocumentOptions,
initNotSyncedOptions,
initStartupOptions
};