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";
|
2020-01-21 22:54:16 +01:00
|
|
|
import Entrypoints from "./entrypoints.js";
|
2022-12-01 13:07:23 +01:00
|
|
|
import options from "../services/options.js";
|
|
|
|
import utils from "../services/utils.js";
|
|
|
|
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";
|
2024-12-22 18:03:03 +02:00
|
|
|
import linkService, { ViewScope } from "../services/link.js";
|
2022-12-01 13:07:23 +01:00
|
|
|
import MobileScreenSwitcherExecutor 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";
|
2024-12-19 22:06:42 +02:00
|
|
|
import NoteDetailWidget from "../widgets/note_detail.js";
|
2024-12-21 15:34:07 +02:00
|
|
|
import { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
2024-12-21 17:12:16 +02:00
|
|
|
import { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
2024-12-21 23:47:18 +02:00
|
|
|
import { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.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-21 23:47:18 +02:00
|
|
|
interface CommandData {
|
|
|
|
ntxId?: string;
|
2024-12-21 23:29:17 +02:00
|
|
|
}
|
|
|
|
|
2024-12-21 23:47:18 +02:00
|
|
|
type CommandMappings = {
|
2024-12-21 23:57:55 +02:00
|
|
|
"api-log-messages": CommandData;
|
|
|
|
focusOnDetail: Required<CommandData>;
|
|
|
|
searchNotes: CommandData & {
|
|
|
|
searchString: string | undefined;
|
|
|
|
};
|
|
|
|
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 & {
|
|
|
|
isNewNote: boolean;
|
|
|
|
};
|
2024-12-21 23:47:18 +02:00
|
|
|
showPromptDialog: PromptDialogOptions;
|
|
|
|
showInfoDialog: ConfirmWithMessageOptions;
|
|
|
|
showConfirmDialog: ConfirmWithMessageOptions;
|
2024-12-21 23:57:55 +02:00
|
|
|
openNewNoteSplit: CommandData & {
|
|
|
|
ntxId: string;
|
2024-12-22 17:56:53 +02:00
|
|
|
notePath: string;
|
2024-12-22 18:03:03 +02:00
|
|
|
hoistedNoteId?: string;
|
|
|
|
viewScope?: ViewScope;
|
2024-12-21 23:57:55 +02:00
|
|
|
};
|
2024-12-22 18:03:03 +02:00
|
|
|
openInWindow: CommandData & {
|
|
|
|
notePath: string;
|
|
|
|
hoistedNoteId: string;
|
|
|
|
viewScope: ViewScope;
|
|
|
|
}
|
2024-12-21 23:57:55 +02:00
|
|
|
executeInActiveNoteDetailWidget: CommandData & {
|
|
|
|
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void
|
|
|
|
};
|
|
|
|
addTextToActiveEditor: CommandData & {
|
|
|
|
text: string;
|
|
|
|
};
|
2024-12-22 17:56:53 +02:00
|
|
|
|
2024-12-21 23:57:55 +02:00
|
|
|
importMarkdownInline: CommandData;
|
|
|
|
showPasswordNotSet: CommandData;
|
|
|
|
showProtectedSessionPasswordDialog: CommandData;
|
|
|
|
closeProtectedSessionPasswordDialog: CommandData;
|
2024-12-22 17:56:53 +02:00
|
|
|
resetLauncher: CommandData;
|
2024-12-21 23:47:18 +02:00
|
|
|
}
|
|
|
|
|
2024-12-21 23:54:47 +02:00
|
|
|
type EventMappings = {
|
|
|
|
initialRenderComplete: {};
|
|
|
|
frocaReloaded: {};
|
|
|
|
protectedSessionStarted: {};
|
|
|
|
notesReloaded: {
|
|
|
|
noteIds: string[];
|
|
|
|
};
|
|
|
|
refreshIncludedNote: {
|
|
|
|
noteId: string;
|
|
|
|
};
|
|
|
|
apiLogMessages: {
|
|
|
|
noteId: string;
|
|
|
|
messages: string[];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommandAndEventMappings = (CommandMappings & EventMappings);
|
|
|
|
|
2024-12-21 23:47:18 +02:00
|
|
|
export type CommandNames = keyof CommandMappings;
|
2024-12-21 23:54:47 +02:00
|
|
|
type EventNames = keyof EventMappings;
|
2024-07-25 20:55:04 +03:00
|
|
|
|
2020-02-16 19:21:17 +01:00
|
|
|
class AppContext extends Component {
|
2024-07-25 20:55:04 +03:00
|
|
|
|
|
|
|
isMainWindow: boolean;
|
|
|
|
components: Component[];
|
|
|
|
beforeUnloadListeners: WeakRef<BeforeUploadListener>[];
|
|
|
|
tabManager!: TabManager;
|
|
|
|
layout?: Layout;
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
this.components = [
|
|
|
|
this.tabManager,
|
|
|
|
new RootCommandExecutor(),
|
|
|
|
new Entrypoints(),
|
2022-12-01 13:24:34 +01:00
|
|
|
new MainTreeExecutors(),
|
|
|
|
new ShortcutComponent()
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2020-05-05 23:58:52 +02:00
|
|
|
$renderedWidget.on('click', "[data-trigger-command]", function() {
|
2023-05-07 10:43:51 +02:00
|
|
|
if ($(this).hasClass("disabled")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-05 23:58:52 +02:00
|
|
|
const commandName = $(this).attr('data-trigger-command');
|
|
|
|
const $component = $(this).closest(".component");
|
|
|
|
const component = $component.prop("component");
|
2020-02-09 22:31:52 +01:00
|
|
|
|
2020-05-06 21:24:51 +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);
|
|
|
|
|
2020-02-16 19:21:17 +01:00
|
|
|
this.triggerEvent('initialRenderComplete');
|
2020-01-11 21:19:56 +01:00
|
|
|
}
|
|
|
|
|
2024-12-21 23:54:47 +02:00
|
|
|
// TODO: Remove ignore once all commands are mapped out.
|
|
|
|
//@ts-ignore
|
|
|
|
triggerEvent<K extends EventNames | CommandNames>(name: K, data: CommandAndEventMappings[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-21 23:47:18 +02:00
|
|
|
// TODO: Remove ignore once all commands are mapped out.
|
|
|
|
//@ts-ignore
|
|
|
|
triggerCommand<K extends CommandNames>(name: K, data: CommandMappings[K] = {}) {
|
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) {
|
2020-02-16 19:21:17 +01:00
|
|
|
return $(el).closest(".component").prop('component');
|
|
|
|
}
|
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
|
2022-01-10 20:37:33 +01:00
|
|
|
$(window).on('beforeunload', () => {
|
2021-02-27 23:39:02 +01:00
|
|
|
let allSaved = true;
|
|
|
|
|
|
|
|
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter(wr => !!wr.deref());
|
|
|
|
|
|
|
|
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
|
|
|
});
|
|
|
|
|
2020-02-03 20:07:34 +01:00
|
|
|
$(window).on('hashchange', function() {
|
2023-05-07 21:18:21 +02:00
|
|
|
const {notePath, ntxId, viewScope} = 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);
|
2020-02-03 20:07:34 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-05-11 20:08:55 +02:00
|
|
|
export default appContext;
|