2022-12-01 13:07:23 +01:00
import froca from "../services/froca.js" ;
import bundleService from "../services/bundle.js" ;
2021-04-24 22:18:25 +02:00
import RootCommandExecutor from "./root_command_executor.js" ;
2025-01-09 18:36:24 +02:00
import Entrypoints , { type SqlExecuteResults } from "./entrypoints.js" ;
2022-12-01 13:07:23 +01:00
import options from "../services/options.js" ;
2025-04-13 23:20:22 +03:00
import utils , { hasTouchBar } from "../services/utils.js" ;
2022-12-01 13:07:23 +01:00
import zoomComponent from "./zoom.js" ;
2020-02-07 21:08:55 +01:00
import TabManager from "./tab_manager.js" ;
2022-12-01 13:07:23 +01:00
import Component from "./component.js" ;
import keyboardActionsService from "../services/keyboard_actions.js" ;
2025-01-09 18:36:24 +02:00
import linkService , { type ViewScope } from "../services/link.js" ;
import MobileScreenSwitcherExecutor , { type Screen } from "./mobile_screen_switcher.js" ;
2020-03-17 12:28:02 +01:00
import MainTreeExecutors from "./main_tree_executors.js" ;
2022-12-01 13:07:23 +01:00
import toast from "../services/toast.js" ;
2022-12-01 13:24:34 +01:00
import ShortcutComponent from "./shortcut_component.js" ;
2024-10-15 15:46:34 +08:00
import { t , initLocale } from "../services/i18n.js" ;
2025-01-13 23:18:10 +02:00
import type NoteDetailWidget from "../widgets/note_detail.js" ;
2025-01-09 18:36:24 +02:00
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js" ;
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js" ;
import type { ConfirmWithMessageOptions , ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js" ;
2025-01-13 23:18:10 +02:00
import type LoadResults from "../services/load_results.js" ;
2025-01-09 18:36:24 +02:00
import type { Attribute } from "../services/attribute_parser.js" ;
2025-01-13 23:18:10 +02:00
import type NoteTreeWidget from "../widgets/note_tree.js" ;
import type { default as NoteContext , GetTextEditorCallback } from "./note_context.js" ;
2025-02-05 21:06:21 +02:00
import type TypeWidget from "../widgets/type_widgets/type_widget.js" ;
2025-03-05 12:44:36 +01:00
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js" ;
2025-04-13 23:04:06 +03:00
import type { NativeImage , TouchBar } from "electron" ;
2025-04-13 23:20:22 +03:00
import TouchBarComponent from "./touch_bar.js" ;
2025-05-05 21:44:27 +03:00
import type { CKTextEditor } from "@triliumnext/ckeditor5" ;
2025-05-28 20:42:21 +03:00
import type CodeMirror from "@triliumnext/codemirror" ;
2025-06-12 21:59:59 +03:00
import { StartupChecks } from "./startup_checks.js" ;
2020-01-11 21:19:56 +01:00
2024-07-25 20:55:04 +03:00
interface Layout {
getRootWidget : ( appContext : AppContext ) = > RootWidget ;
}
interface RootWidget extends Component {
2024-12-22 17:56:53 +02:00
render : ( ) = > JQuery < HTMLElement > ;
2024-07-25 20:55:04 +03:00
}
interface BeforeUploadListener extends Component {
beforeUnloadEvent ( ) : boolean ;
}
2024-12-22 19:31:29 +02:00
/ * *
* Base interface for the data / arguments for a given command ( see { @link CommandMappings } ) .
* /
2024-12-22 21:59:08 +02:00
export interface CommandData {
2024-12-23 15:16:41 +02:00
ntxId? : string | null ;
2024-12-21 23:29:17 +02:00
}
2024-12-22 19:31:29 +02:00
/ * *
* Represents a set of commands that are triggered from the context menu , providing information such as the selected note .
* /
export interface ContextMenuCommandData extends CommandData {
2025-01-28 15:44:15 +02:00
node : Fancytree.FancytreeNode ;
notePath? : string ;
2024-12-22 19:31:29 +02:00
noteId? : string ;
2025-04-16 14:55:00 +03:00
selectedOrActiveBranchIds : string [ ] ;
selectedOrActiveNoteIds? : string [ ] ;
2024-12-22 19:31:29 +02:00
}
2024-12-23 14:10:57 +02:00
export interface NoteCommandData extends CommandData {
2025-03-03 22:46:40 +01:00
notePath? : string | null ;
hoistedNoteId? : string | null ;
2024-12-23 14:10:57 +02:00
viewScope? : ViewScope ;
}
2025-02-02 18:33:58 +02:00
export interface ExecuteCommandData < T > extends CommandData {
2025-03-02 20:47:57 +01:00
resolve : ( data : T ) = > void ;
2024-12-23 15:16:41 +02:00
}
2025-04-04 17:57:54 +03:00
export interface NoteSwitchedContext {
noteContext : NoteContext ;
2025-04-04 18:07:26 +03:00
notePath : string | null | undefined ;
2025-04-04 17:57:54 +03:00
}
2024-12-22 19:31:29 +02:00
/ * *
* The keys represent the different commands that can be triggered via { @link AppContext # triggerCommand } ( first argument ) , and the values represent the data or arguments definition of the given command . All data for commands must extend { @link CommandData } .
* /
export type CommandMappings = {
2024-12-21 23:57:55 +02:00
"api-log-messages" : CommandData ;
2025-03-02 20:47:57 +01:00
focusTree : CommandData ;
2025-01-28 15:44:15 +02:00
focusOnTitle : CommandData ;
2025-01-28 14:07:56 +02:00
focusOnDetail : CommandData ;
2025-01-04 11:51:16 +02:00
focusOnSearchDefinition : Required < CommandData > ;
2024-12-21 23:57:55 +02:00
searchNotes : CommandData & {
2025-01-04 11:51:16 +02:00
searchString? : string ;
ancestorNoteId? : string | null ;
} ;
2025-02-02 19:44:18 +02:00
closeTocCommand : CommandData ;
2025-02-24 12:39:40 +02:00
closeHlt : CommandData ;
2025-01-04 21:59:35 +02:00
showLaunchBarSubtree : CommandData ;
2025-02-11 19:04:27 +02:00
showRevisions : CommandData ;
2025-03-09 02:19:26 +00:00
showLlmChat : CommandData ;
2025-03-28 20:01:39 +00:00
createAiChat : CommandData ;
2025-01-04 11:51:16 +02:00
showOptions : CommandData & {
section : string ;
2024-12-21 23:57:55 +02:00
} ;
2025-01-31 21:08:09 +02:00
showExportDialog : CommandData & {
notePath : string ;
2025-02-01 16:23:28 +02:00
defaultType : "single" | "subtree" ;
2025-01-31 21:08:09 +02:00
} ;
2024-12-21 23:57:55 +02:00
showDeleteNotesDialog : CommandData & {
branchIdsToDelete : string [ ] ;
callback : ( value : ResolveOptions ) = > void ;
forceDeleteAllClones : boolean ;
} ;
2024-12-21 23:47:18 +02:00
showConfirmDeleteNoteBoxWithNoteDialog : ConfirmWithTitleOptions ;
2024-12-21 23:57:55 +02:00
openedFileUpdated : CommandData & {
entityType : string ;
entityId : string ;
lastModifiedMs : number ;
filePath : string ;
} ;
focusAndSelectTitle : CommandData & {
2025-01-14 20:08:57 +02:00
isNewNote? : boolean ;
2024-12-21 23:57:55 +02:00
} ;
2024-12-21 23:47:18 +02:00
showPromptDialog : PromptDialogOptions ;
showInfoDialog : ConfirmWithMessageOptions ;
showConfirmDialog : ConfirmWithMessageOptions ;
2025-01-28 15:44:15 +02:00
showRecentChanges : CommandData & { ancestorNoteId : string } ;
2025-03-02 20:47:57 +01:00
showImportDialog : CommandData & { noteId : string } ;
2024-12-23 14:10:57 +02:00
openNewNoteSplit : NoteCommandData ;
2025-01-09 18:07:02 +02:00
openInWindow : NoteCommandData ;
2024-12-22 18:33:57 +02:00
openNoteInNewTab : CommandData ;
openNoteInNewSplit : CommandData ;
openNoteInNewWindow : CommandData ;
2025-02-24 12:39:40 +02:00
openAboutDialog : CommandData ;
hideFloatingButtons : { } ;
2025-01-19 20:19:21 +02:00
hideLeftPane : CommandData ;
2025-06-12 22:16:57 +03:00
showCpuArchWarning : CommandData ;
2025-01-19 20:19:21 +02:00
showLeftPane : CommandData ;
2025-01-28 15:44:15 +02:00
hoistNote : CommandData & { noteId : string } ;
2025-02-11 19:16:11 +02:00
leaveProtectedSession : CommandData ;
enterProtectedSession : CommandData ;
2025-04-04 17:57:54 +03:00
noteContextReorder : CommandData & {
ntxIdsInOrder : string [ ] ;
oldMainNtxId? : string | null ;
newMainNtxId? : string | null ;
} ;
2024-12-22 19:31:29 +02:00
openInTab : ContextMenuCommandData ;
openNoteInSplit : ContextMenuCommandData ;
toggleNoteHoisting : ContextMenuCommandData ;
insertNoteAfter : ContextMenuCommandData ;
insertChildNote : ContextMenuCommandData ;
2025-01-17 23:55:46 +02:00
delete : ContextMenuCommandData ;
2025-04-11 20:41:04 +02:00
editNoteTitle : { } ;
2024-12-22 19:31:29 +02:00
protectSubtree : ContextMenuCommandData ;
unprotectSubtree : ContextMenuCommandData ;
2025-03-02 20:47:57 +01:00
openBulkActionsDialog :
2025-03-05 12:44:36 +01:00
| ContextMenuCommandData
| {
selectedOrActiveNoteIds? : string [ ] ;
} ;
2024-12-22 19:31:29 +02:00
editBranchPrefix : ContextMenuCommandData ;
convertNoteToAttachment : ContextMenuCommandData ;
duplicateSubtree : ContextMenuCommandData ;
expandSubtree : ContextMenuCommandData ;
collapseSubtree : ContextMenuCommandData ;
sortChildNotes : ContextMenuCommandData ;
copyNotePathToClipboard : ContextMenuCommandData ;
recentChangesInSubtree : ContextMenuCommandData ;
cutNotesToClipboard : ContextMenuCommandData ;
copyNotesToClipboard : ContextMenuCommandData ;
pasteNotesFromClipboard : ContextMenuCommandData ;
pasteNotesAfterFromClipboard : ContextMenuCommandData ;
moveNotesTo : ContextMenuCommandData ;
cloneNotesTo : ContextMenuCommandData ;
deleteNotes : ContextMenuCommandData ;
importIntoNote : ContextMenuCommandData ;
exportNote : ContextMenuCommandData ;
searchInSubtree : ContextMenuCommandData ;
2025-01-28 15:44:15 +02:00
moveNoteUp : ContextMenuCommandData ;
moveNoteDown : ContextMenuCommandData ;
moveNoteUpInHierarchy : ContextMenuCommandData ;
moveNoteDownInHierarchy : ContextMenuCommandData ;
selectAllNotesInParent : ContextMenuCommandData ;
2024-12-22 19:31:29 +02:00
2025-03-08 12:06:35 +02:00
createNoteIntoInbox : CommandData ;
2024-12-22 19:31:29 +02:00
addNoteLauncher : ContextMenuCommandData ;
addScriptLauncher : ContextMenuCommandData ;
addWidgetLauncher : ContextMenuCommandData ;
addSpacerLauncher : ContextMenuCommandData ;
moveLauncherToVisible : ContextMenuCommandData ;
moveLauncherToAvailable : ContextMenuCommandData ;
resetLauncher : ContextMenuCommandData ;
2024-12-21 23:57:55 +02:00
executeInActiveNoteDetailWidget : CommandData & {
2025-01-09 18:07:02 +02:00
callback : ( value : NoteDetailWidget | PromiseLike < NoteDetailWidget > ) = > void ;
2024-12-23 15:16:41 +02:00
} ;
2025-01-09 18:07:02 +02:00
executeWithTextEditor : CommandData &
2025-05-05 21:44:27 +03:00
ExecuteCommandData < CKTextEditor > & {
2025-03-19 14:22:40 +01:00
callback? : GetTextEditorCallback ;
} ;
2025-05-28 20:42:21 +03:00
executeWithCodeEditor : CommandData & ExecuteCommandData < CodeMirror > ;
2025-02-02 19:08:44 +02:00
/ * *
* Called upon when attempting to retrieve the content element of a { @link NoteContext } .
* Generally should not be invoked manually , as it is used by { @link NoteContext . getContentElement } .
* /
2025-02-02 18:33:58 +02:00
executeWithContentElement : CommandData & ExecuteCommandData < JQuery < HTMLElement > > ;
2025-02-05 21:06:21 +02:00
executeWithTypeWidget : CommandData & ExecuteCommandData < TypeWidget | null > ;
2024-12-21 23:57:55 +02:00
addTextToActiveEditor : CommandData & {
text : string ;
} ;
2024-12-22 18:33:57 +02:00
/** Works only in the electron context menu. */
replaceMisspelling : CommandData ;
2024-12-22 17:56:53 +02:00
2024-12-21 23:57:55 +02:00
importMarkdownInline : CommandData ;
showPasswordNotSet : CommandData ;
showProtectedSessionPasswordDialog : CommandData ;
2025-02-24 12:39:40 +02:00
showUploadAttachmentsDialog : CommandData & { noteId : string } ;
2025-03-21 17:34:39 +02:00
showIncludeNoteDialog : CommandData & { textTypeWidget : EditableTextTypeWidget } ;
showAddLinkDialog : CommandData & { textTypeWidget : EditableTextTypeWidget , text : string } ;
2024-12-21 23:57:55 +02:00
closeProtectedSessionPasswordDialog : CommandData ;
2024-12-22 18:33:57 +02:00
copyImageReferenceToClipboard : CommandData ;
copyImageToClipboard : CommandData ;
2024-12-22 19:31:29 +02:00
updateAttributesList : {
2024-12-22 21:59:08 +02:00
attributes : Attribute [ ] ;
2024-12-22 19:31:29 +02:00
} ;
2024-12-22 21:59:08 +02:00
addNewLabel : CommandData ;
addNewRelation : CommandData ;
addNewLabelDefinition : CommandData ;
addNewRelationDefinition : CommandData ;
2024-12-23 14:14:38 +02:00
cloneNoteIdsTo : CommandData & {
noteIds : string [ ] ;
} ;
moveBranchIdsTo : CommandData & {
branchIds : string [ ] ;
} ;
2025-01-04 12:44:40 +02:00
/** Sets the active {@link Screen} (e.g. to toggle the tree sidebar). It triggers the {@link EventMappings.activeScreenChanged} event, but only if the provided <em>screen</em> is different than the current one. */
2024-12-23 14:21:43 +02:00
setActiveScreen : CommandData & {
screen : Screen ;
2025-01-09 18:07:02 +02:00
} ;
2025-01-09 20:20:06 +02:00
closeTab : CommandData ;
2025-02-24 12:39:40 +02:00
closeToc : CommandData ;
2025-01-09 20:20:06 +02:00
closeOtherTabs : CommandData ;
closeRightTabs : CommandData ;
closeAllTabs : CommandData ;
reopenLastTab : CommandData ;
moveTabToNewWindow : CommandData ;
copyTabToNewWindow : CommandData ;
closeActiveTab : CommandData & {
2025-03-02 20:47:57 +01:00
$el : JQuery < HTMLElement > ;
} ;
2025-01-11 11:02:22 +02:00
setZoomFactorAndSave : {
zoomFactor : string ;
2025-03-02 20:47:57 +01:00
} ;
2025-01-22 19:33:53 +02:00
2025-02-02 19:44:18 +02:00
reEvaluateRightPaneVisibility : CommandData ;
2025-02-05 21:06:21 +02:00
runActiveNote : CommandData ;
2025-02-11 19:09:04 +02:00
scrollContainerToCommand : CommandData & {
position : number ;
} ;
2025-02-24 12:39:40 +02:00
scrollToEnd : CommandData ;
closeThisNoteSplit : CommandData ;
2025-03-02 20:47:57 +01:00
moveThisNoteSplit : CommandData & { isMovingLeft : boolean } ;
2025-03-08 13:13:21 +02:00
jumpToNote : CommandData ;
2025-02-02 19:44:18 +02:00
2025-01-22 19:33:53 +02:00
// Geomap
2025-03-02 20:47:57 +01:00
deleteFromMap : { noteId : string } ;
openGeoLocation : { noteId : string ; event : JQuery.MouseDownEvent } ;
2025-02-14 13:55:04 +02:00
toggleZenMode : CommandData ;
2025-02-24 12:39:40 +02:00
updateAttributeList : CommandData & { attributes : Attribute [ ] } ;
saveAttributes : CommandData ;
reloadAttributes : CommandData ;
2025-03-02 20:47:57 +01:00
refreshNoteList : CommandData & { noteId : string } ;
2025-02-24 22:50:16 +02:00
refreshResults : { } ;
refreshSearchDefinition : { } ;
2025-03-08 22:00:29 +02:00
2025-03-08 22:56:32 +02:00
geoMapCreateChildNote : CommandData ;
2025-03-08 22:00:29 +02:00
buildTouchBar : CommandData & {
2025-04-13 23:04:06 +03:00
TouchBar : typeof TouchBar ;
2025-03-08 22:00:29 +02:00
buildIcon ( name : string ) : NativeImage ;
} ;
2025-03-08 22:46:14 +02:00
refreshTouchBar : CommandData ;
2025-06-17 17:03:23 +03:00
reloadTextEditor : CommandData ;
2025-01-09 18:07:02 +02:00
} ;
2024-12-21 23:47:18 +02:00
2024-12-21 23:54:47 +02:00
type EventMappings = {
initialRenderComplete : { } ;
frocaReloaded : { } ;
2025-05-20 14:33:01 +08:00
setLeftPaneVisibility : {
leftPaneVisible : boolean | null ;
}
2024-12-21 23:54:47 +02:00
protectedSessionStarted : { } ;
notesReloaded : {
noteIds : string [ ] ;
} ;
refreshIncludedNote : {
noteId : string ;
} ;
apiLogMessages : {
noteId : string ;
messages : string [ ] ;
} ;
2024-12-22 21:59:08 +02:00
entitiesReloaded : {
2025-01-09 18:07:02 +02:00
loadResults : LoadResults ;
2024-12-22 21:59:08 +02:00
} ;
addNewLabel : CommandData ;
addNewRelation : CommandData ;
2024-12-23 15:16:41 +02:00
sqlQueryResults : CommandData & {
2024-12-23 14:10:57 +02:00
results : SqlExecuteResults ;
2025-01-09 18:07:02 +02:00
} ;
2025-01-04 11:51:16 +02:00
readOnlyTemporarilyDisabled : {
2025-01-09 18:07:02 +02:00
noteContext : NoteContext ;
} ;
2025-01-04 12:44:40 +02:00
/** Triggered when the {@link CommandMappings.setActiveScreen} command is invoked. */
activeScreenChanged : {
activeScreen : Screen ;
2025-01-09 18:07:02 +02:00
} ;
2025-01-07 12:34:10 +02:00
activeContextChanged : {
noteContext : NoteContext ;
2025-01-09 18:07:02 +02:00
} ;
2025-01-22 21:55:42 +02:00
beforeNoteSwitch : {
noteContext : NoteContext ;
} ;
2025-01-28 14:07:56 +02:00
beforeNoteContextRemove : {
ntxIds : string [ ] ;
} ;
2025-04-04 17:57:54 +03:00
noteSwitched : NoteSwitchedContext ;
noteSwitchedAndActivated : NoteSwitchedContext ;
2025-01-07 12:34:10 +02:00
setNoteContext : {
noteContext : NoteContext ;
2025-01-09 18:07:02 +02:00
} ;
2025-01-07 12:34:10 +02:00
reEvaluateHighlightsListWidgetVisibility : {
noteId : string | undefined ;
2025-01-09 18:07:02 +02:00
} ;
2025-02-02 19:44:18 +02:00
reEvaluateTocWidgetVisibility : {
noteId : string | undefined ;
} ;
2025-01-07 12:34:10 +02:00
showHighlightsListWidget : {
noteId : string ;
2025-01-09 18:07:02 +02:00
} ;
2025-02-26 00:53:15 +01:00
showTocWidget : {
noteId : string ;
} ;
2025-02-24 13:45:36 +02:00
showSearchError : {
error : string ;
} ;
searchRefreshed : { ntxId? : string | null } ;
2025-01-09 20:20:06 +02:00
hoistedNoteChanged : {
2025-01-22 21:55:42 +02:00
noteId : string ;
ntxId : string | null ;
2025-01-09 20:20:06 +02:00
} ;
2025-03-03 22:56:24 +01:00
contextsReopened : {
2025-04-04 18:07:26 +03:00
ntxId? : string ;
2025-03-03 22:46:40 +01:00
mainNtxId : string | null ;
2025-01-09 20:20:06 +02:00
tabPosition : number ;
2025-04-04 17:57:54 +03:00
afterNtxId? : string ;
2025-01-09 20:20:06 +02:00
} ;
2025-01-22 21:55:42 +02:00
noteDetailRefreshed : {
ntxId? : string | null ;
} ;
2025-03-03 23:15:58 +01:00
noteContextReorder : {
2025-01-09 20:20:06 +02:00
oldMainNtxId : string ;
newMainNtxId : string ;
2025-02-11 19:16:11 +02:00
ntxIdsInOrder : string [ ] ;
2025-01-09 20:20:06 +02:00
} ;
newNoteContextCreated : {
noteContext : NoteContext ;
} ;
2025-03-03 22:46:40 +01:00
noteContextRemoved : {
2025-01-09 20:20:06 +02:00
ntxIds : string [ ] ;
2025-01-14 19:18:44 +02:00
} ;
2025-03-22 16:30:19 +02:00
exportSvg : { ntxId : string | null | undefined ; } ;
exportPng : { ntxId : string | null | undefined ; } ;
2025-01-20 22:50:36 +02:00
geoMapCreateChildNote : {
ntxId : string | null | undefined ; // TODO: deduplicate ntxId
} ;
2025-01-22 21:55:42 +02:00
tabReorder : {
2025-03-02 20:47:57 +01:00
ntxIdsInOrder : string [ ] ;
2025-01-22 21:55:42 +02:00
} ;
2025-01-28 14:13:21 +02:00
refreshNoteList : {
noteId : string ;
2025-02-02 19:44:18 +02:00
} ;
2025-02-05 21:06:21 +02:00
noteTypeMimeChanged : { noteId : string } ;
2025-02-14 18:18:28 +02:00
zenModeChanged : { isEnabled : boolean } ;
2025-02-28 17:55:32 +02:00
relationMapCreateChildNote : { ntxId : string | null | undefined } ;
relationMapResetPanZoom : { ntxId : string | null | undefined } ;
relationMapResetZoomIn : { ntxId : string | null | undefined } ;
relationMapResetZoomOut : { ntxId : string | null | undefined } ;
2025-03-03 22:56:24 +01:00
activeNoteChanged : { } ;
2025-03-05 12:44:36 +01:00
showAddLinkDialog : {
textTypeWidget : EditableTextTypeWidget ;
text : string ;
} ;
2025-03-19 23:27:52 +02:00
showIncludeDialog : {
textTypeWidget : EditableTextTypeWidget ;
} ;
2025-03-19 14:22:40 +01:00
openBulkActionsDialog : {
selectedOrActiveNoteIds : string [ ] ;
} ;
2025-03-19 14:27:33 +01:00
cloneNoteIdsTo : {
noteIds : string [ ] ;
} ;
2025-03-30 14:29:20 +03:00
refreshData : { ntxId : string | null | undefined } ;
2025-01-09 18:07:02 +02:00
} ;
2024-12-21 23:54:47 +02:00
2024-12-22 21:59:08 +02:00
export type EventListener < T extends EventNames > = {
2025-01-09 18:07:02 +02:00
[ key in T as ` ${ key } Event ` ] : ( data : EventData < T > ) = > void ;
} ;
2024-12-22 21:59:08 +02:00
2024-12-23 14:21:43 +02:00
export type CommandListener < T extends CommandNames > = {
2025-01-09 18:07:02 +02:00
[ key in T as ` ${ key } Command ` ] : ( data : CommandListenerData < T > ) = > void ;
} ;
2024-12-23 14:21:43 +02:00
export type CommandListenerData < T extends CommandNames > = CommandMappings [ T ] ;
2024-12-22 21:59:08 +02:00
2025-01-09 18:07:02 +02:00
type CommandAndEventMappings = CommandMappings & EventMappings ;
2025-01-14 20:08:57 +02:00
type EventOnlyNames = keyof EventMappings ;
export type EventNames = CommandNames | EventOnlyNames ;
export type EventData < T extends EventNames > = CommandAndEventMappings [ T ] ;
2024-12-21 23:54:47 +02:00
2024-12-22 19:31:29 +02:00
/ * *
* This type is a discriminated union which contains all the possible commands that can be triggered via { @link AppContext . triggerCommand } .
* /
2024-12-21 23:47:18 +02:00
export type CommandNames = keyof CommandMappings ;
2024-07-25 20:55:04 +03:00
2025-01-09 18:07:02 +02:00
type FilterByValueType < T , ValueType > = { [ K in keyof T ] : T [ K ] extends ValueType ? K : never } [ keyof T ] ;
2024-12-22 19:31:29 +02:00
/ * *
* Generic which filters { @link CommandNames } to provide only those commands that take in as data the desired implementation of { @link CommandData } . Mostly useful for contextual menu , to enforce consistency in the commands .
* /
export type FilteredCommandNames < T extends CommandData > = keyof Pick < CommandMappings , FilterByValueType < CommandMappings , T > > ;
2025-03-21 18:14:37 +02:00
export class AppContext extends Component {
2024-07-25 20:55:04 +03:00
isMainWindow : boolean ;
components : Component [ ] ;
beforeUnloadListeners : WeakRef < BeforeUploadListener > [ ] ;
tabManager ! : TabManager ;
layout? : Layout ;
2024-12-23 14:14:38 +02:00
noteTreeWidget? : NoteTreeWidget ;
2024-07-25 20:55:04 +03:00
2025-02-24 13:50:08 +02:00
lastSearchString? : string ;
2024-07-25 20:55:04 +03:00
constructor ( isMainWindow : boolean ) {
2020-04-25 23:52:13 +02:00
super ( ) ;
this . isMainWindow = isMainWindow ;
2022-12-01 13:07:23 +01:00
// non-widget/layout components needed for the application
this . components = [ ] ;
2021-02-27 23:39:02 +01:00
this . beforeUnloadListeners = [ ] ;
2020-04-25 23:52:13 +02:00
}
2024-08-11 08:12:01 +03:00
/ * *
* Must be called as soon as possible , before the creation of any components since this method is in charge of initializing the locale . Any attempts to read translation before this method is called will result in ` undefined ` .
* /
async earlyInit() {
await options . initializedPromise ;
2024-08-11 14:22:37 +03:00
await initLocale ( ) ;
2024-08-11 08:12:01 +03:00
}
2024-07-25 20:55:04 +03:00
setLayout ( layout : Layout ) {
2020-02-06 21:47:31 +01:00
this . layout = layout ;
2020-01-12 19:05:09 +01:00
}
2020-04-25 23:52:13 +02:00
async start() {
2022-12-01 13:07:23 +01:00
this . initComponents ( ) ;
this . renderWidgets ( ) ;
2020-03-29 23:10:45 +02:00
2022-12-18 16:12:29 +01:00
await froca . initializedPromise ;
2022-11-22 22:45:50 +01:00
2020-04-25 23:52:13 +02:00
this . tabManager . loadTabs ( ) ;
2020-02-02 22:32:44 +01:00
2021-03-18 15:09:08 -05:00
setTimeout ( ( ) = > bundleService . executeStartupBundles ( ) , 2000 ) ;
2020-02-02 22:04:28 +01:00
}
2020-01-12 19:05:09 +01:00
2022-12-01 13:07:23 +01:00
initComponents() {
this . tabManager = new TabManager ( ) ;
2025-06-12 21:59:59 +03:00
this . components = [
this . tabManager ,
new RootCommandExecutor ( ) ,
new Entrypoints ( ) ,
new MainTreeExecutors ( ) ,
new ShortcutComponent ( ) ,
new StartupChecks ( )
] ;
2022-12-01 13:07:23 +01:00
if ( utils . isMobile ( ) ) {
this . components . push ( new MobileScreenSwitcherExecutor ( ) ) ;
}
for ( const component of this . components ) {
this . child ( component ) ;
}
if ( utils . isElectron ( ) ) {
this . child ( zoomComponent ) ;
}
2025-04-13 23:20:22 +03:00
if ( hasTouchBar ) {
this . child ( new TouchBarComponent ( ) ) ;
}
2022-12-01 13:07:23 +01:00
}
renderWidgets() {
2024-07-25 20:55:04 +03:00
if ( ! this . layout ) {
throw new Error ( "Missing layout." ) ;
}
2020-02-16 19:21:17 +01:00
const rootWidget = this . layout . getRootWidget ( this ) ;
const $renderedWidget = rootWidget . render ( ) ;
2020-01-14 21:23:32 +01:00
2020-02-16 20:09:59 +01:00
keyboardActionsService . updateDisplayedShortcuts ( $renderedWidget ) ;
2020-02-09 22:31:52 +01:00
$ ( "body" ) . append ( $renderedWidget ) ;
2025-01-09 18:07:02 +02:00
$renderedWidget . on ( "click" , "[data-trigger-command]" , function ( ) {
2023-05-07 10:43:51 +02:00
if ( $ ( this ) . hasClass ( "disabled" ) ) {
return ;
}
2025-01-09 18:07:02 +02:00
const commandName = $ ( this ) . attr ( "data-trigger-command" ) ;
2020-05-05 23:58:52 +02:00
const $component = $ ( this ) . closest ( ".component" ) ;
const component = $component . prop ( "component" ) ;
2020-02-09 22:31:52 +01:00
2025-01-09 18:07:02 +02:00
component . triggerCommand ( commandName , { $el : $ ( this ) } ) ;
2020-02-09 22:31:52 +01:00
} ) ;
2020-02-05 22:08:45 +01:00
2020-02-27 10:03:14 +01:00
this . child ( rootWidget ) ;
2025-01-14 20:08:57 +02:00
this . triggerEvent ( "initialRenderComplete" , { } ) ;
2020-01-11 21:19:56 +01:00
}
2025-01-14 20:08:57 +02:00
triggerEvent < K extends EventNames > ( name : K , data : EventData < K > ) {
2020-02-29 19:43:19 +01:00
return this . handleEvent ( name , data ) ;
2020-02-01 22:29:32 +01:00
}
2020-02-15 10:41:21 +01:00
2024-12-22 19:31:29 +02:00
triggerCommand < K extends CommandNames > ( name : K , _data? : CommandMappings [ K ] ) {
const data = _data || { } ;
2022-12-01 13:07:23 +01:00
for ( const executor of this . components ) {
2024-07-25 20:55:04 +03:00
const fun = ( executor as any ) [ ` ${ name } Command ` ] ;
2020-02-15 10:41:21 +01:00
2020-02-29 19:43:19 +01:00
if ( fun ) {
return executor . callMethod ( fun , data ) ;
2020-02-15 10:41:21 +01:00
}
}
2023-06-30 11:18:34 +02:00
// this might hint at error, but sometimes this is used by components which are at different places
2020-05-05 23:58:52 +02:00
// in the component tree to communicate with each other
2020-02-17 22:38:46 +01:00
console . debug ( ` Unhandled command ${ name } , converting to event. ` ) ;
2024-12-21 23:54:47 +02:00
return this . triggerEvent ( name , data as CommandAndEventMappings [ K ] ) ;
2020-02-15 10:41:21 +01:00
}
2024-07-25 20:55:04 +03:00
getComponentByEl ( el : HTMLElement ) {
2025-01-09 18:07:02 +02:00
return $ ( el ) . closest ( ".component" ) . prop ( "component" ) ;
2020-02-16 19:21:17 +01:00
}
2021-02-27 23:39:02 +01:00
2024-07-25 20:55:04 +03:00
addBeforeUnloadListener ( obj : BeforeUploadListener ) {
2021-02-27 23:39:02 +01:00
if ( typeof WeakRef !== "function" ) {
// older browsers don't support WeakRef
return ;
}
2024-07-25 20:55:04 +03:00
this . beforeUnloadListeners . push ( new WeakRef < BeforeUploadListener > ( obj ) ) ;
2021-02-27 23:39:02 +01:00
}
2020-01-12 19:05:09 +01:00
}
2020-04-25 23:52:13 +02:00
const appContext = new AppContext ( window . glob . isMainWindow ) ;
2020-01-12 12:48:17 +01:00
2020-02-02 10:41:43 +01:00
// we should save all outstanding changes before the page/app is closed
2025-01-09 18:07:02 +02:00
$ ( window ) . on ( "beforeunload" , ( ) = > {
2021-02-27 23:39:02 +01:00
let allSaved = true ;
2025-01-09 18:07:02 +02:00
appContext . beforeUnloadListeners = appContext . beforeUnloadListeners . filter ( ( wr ) = > ! ! wr . deref ( ) ) ;
2021-02-27 23:39:02 +01:00
for ( const weakRef of appContext . beforeUnloadListeners ) {
const component = weakRef . deref ( ) ;
if ( ! component ) {
continue ;
}
if ( ! component . beforeUnloadEvent ( ) ) {
console . log ( ` Component ${ component . componentId } is not finished saving its state. ` ) ;
2024-10-15 15:46:34 +08:00
toast . showMessage ( t ( "app_context.please_wait_for_save" ) , 10000 ) ;
2021-02-27 23:39:02 +01:00
allSaved = false ;
}
}
if ( ! allSaved ) {
return "some string" ;
}
2020-02-02 10:41:43 +01:00
} ) ;
2025-01-09 18:07:02 +02:00
$ ( window ) . on ( "hashchange" , function ( ) {
2025-03-06 22:21:50 +02:00
const { notePath , ntxId , viewScope , searchString } = linkService . parseNavigationStateFromUrl ( window . location . href ) ;
2020-03-21 21:04:34 +01:00
2023-05-07 21:18:21 +02:00
if ( notePath || ntxId ) {
2023-04-11 21:41:55 +02:00
appContext . tabManager . switchToNoteContext ( ntxId , notePath , viewScope ) ;
2025-03-06 22:21:50 +02:00
} else if ( searchString ) {
appContext . triggerCommand ( "searchNotes" , { searchString } ) ;
2020-02-03 20:07:34 +01:00
}
} ) ;
2020-05-11 20:08:55 +02:00
export default appContext ;