From 8a47b2f5a86c56eafabd811be33a2fe8de8c1475 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 23 Dec 2024 15:16:41 +0200 Subject: [PATCH] chore(client/ts): port components/note_context --- src/public/app/components/app_context.ts | 16 +++- .../{note_context.js => note_context.ts} | 76 ++++++++++++------- src/public/app/server_types.ts | 4 +- src/public/app/services/link.ts | 1 + src/public/app/services/load_results.ts | 22 ++++-- .../app/services/protected_session_holder.ts | 2 +- src/public/app/services/utils.ts | 2 +- 7 files changed, 85 insertions(+), 38 deletions(-) rename src/public/app/components/{note_context.js => note_context.ts} (81%) diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index e6e63c9ce..6e3609df2 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -22,6 +22,7 @@ import { Node } from "../services/tree.js"; import LoadResults from "../services/load_results.js"; import { Attribute } from "../services/attribute_parser.js"; import NoteTreeWidget from "../widgets/note_tree.js"; +import { GetTextEditorCallback } from "./note_context.js"; interface Layout { getRootWidget: (appContext: AppContext) => RootWidget; @@ -39,7 +40,7 @@ interface BeforeUploadListener extends Component { * Base interface for the data/arguments for a given command (see {@link CommandMappings}). */ export interface CommandData { - ntxId?: string; + ntxId?: string | null; } /** @@ -59,6 +60,10 @@ export interface NoteCommandData extends CommandData { viewScope?: ViewScope; } +export interface ExecuteCommandData extends CommandData { + resolve: unknown; +} + /** * 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}. */ @@ -130,6 +135,12 @@ export type CommandMappings = { executeInActiveNoteDetailWidget: CommandData & { callback: (value: NoteDetailWidget | PromiseLike) => void }; + executeWithTextEditor: CommandData & ExecuteCommandData & { + callback?: GetTextEditorCallback; + }; + executeWithCodeEditor: CommandData & ExecuteCommandData; + executeWithContentElement: CommandData & ExecuteCommandData; + executeWithTypeWidget: CommandData & ExecuteCommandData; addTextToActiveEditor: CommandData & { text: string; }; @@ -181,8 +192,7 @@ type EventMappings = { }; addNewLabel: CommandData; addNewRelation: CommandData; - sqlQueryResults: { - ntxId: string; + sqlQueryResults: CommandData & { results: SqlExecuteResults; } } diff --git a/src/public/app/components/note_context.js b/src/public/app/components/note_context.ts similarity index 81% rename from src/public/app/components/note_context.js rename to src/public/app/components/note_context.ts index 559e534bb..cb49864cf 100644 --- a/src/public/app/components/note_context.js +++ b/src/public/app/components/note_context.ts @@ -1,18 +1,39 @@ import protectedSessionHolder from "../services/protected_session_holder.js"; import server from "../services/server.js"; import utils from "../services/utils.js"; -import appContext from "./app_context.js"; +import appContext, { EventData, EventListener } from "./app_context.js"; import treeService from "../services/tree.js"; import Component from "./component.js"; import froca from "../services/froca.js"; import hoistedNoteService from "../services/hoisted_note.js"; import options from "../services/options.js"; +import { ViewScope } from "../services/link.js"; +import FNote from "../entities/fnote.js"; -class NoteContext extends Component { - constructor(ntxId = null, hoistedNoteId = 'root', mainNtxId = null) { +interface SetNoteOpts { + triggerSwitchEvent?: unknown; + viewScope?: ViewScope; +} + +export type GetTextEditorCallback = () => void; + +class NoteContext extends Component + implements EventListener<"entitiesReloaded"> +{ + + ntxId: string | null; + hoistedNoteId: string; + private mainNtxId: string | null; + + private notePath?: string | null; + private noteId?: string | null; + private parentNoteId?: string | null; + private viewScope?: ViewScope; + + constructor(ntxId: string | null = null, hoistedNoteId: string = 'root', mainNtxId: string | null = null) { super(); - this.ntxId = ntxId || this.constructor.generateNtxId(); + this.ntxId = ntxId || NoteContext.generateNtxId(); this.hoistedNoteId = hoistedNoteId; this.mainNtxId = mainNtxId; @@ -41,7 +62,7 @@ class NoteContext extends Component { return !this.noteId; } - async setNote(inputNotePath, opts = {}) { + async setNote(inputNotePath: string, opts: SetNoteOpts = {}) { opts.triggerSwitchEvent = opts.triggerSwitchEvent !== undefined ? opts.triggerSwitchEvent : true; opts.viewScope = opts.viewScope || {}; opts.viewScope.viewMode = opts.viewScope.viewMode || "default"; @@ -84,16 +105,16 @@ class NoteContext extends Component { async setHoistedNoteIfNeeded() { if (this.hoistedNoteId === 'root' - && this.notePath.startsWith("root/_hidden") - && !this.note.isLabelTruthy("keepCurrentHoisting") + && this.notePath?.startsWith("root/_hidden") + && !this.note?.isLabelTruthy("keepCurrentHoisting") ) { // hidden subtree displays only when hoisted, so it doesn't make sense to keep root as hoisted note let hoistedNoteId = '_hidden'; - if (this.note.isLaunchBarConfig()) { + if (this.note?.isLaunchBarConfig()) { hoistedNoteId = '_lbRoot'; - } else if (this.note.isOptions()) { + } else if (this.note?.isOptions()) { hoistedNoteId = '_options'; } @@ -138,19 +159,19 @@ class NoteContext extends Component { } } - saveToRecentNotes(resolvedNotePath) { + saveToRecentNotes(resolvedNotePath: string) { setTimeout(async () => { // we include the note in the recent list only if the user stayed on the note at least 5 seconds if (resolvedNotePath && resolvedNotePath === this.notePath) { await server.post('recent-notes', { - noteId: this.note.noteId, + noteId: this.note?.noteId, notePath: this.notePath }); } }, 5000); } - async getResolvedNotePath(inputNotePath) { + async getResolvedNotePath(inputNotePath: string) { const resolvedNotePath = await treeService.resolveNotePath(inputNotePath, this.hoistedNoteId); if (!resolvedNotePath) { @@ -165,8 +186,7 @@ class NoteContext extends Component { return resolvedNotePath; } - /** @returns {FNote} */ - get note() { + get note(): FNote | null { if (!this.noteId || !(this.noteId in froca.notes)) { return null; } @@ -206,7 +226,7 @@ class NoteContext extends Component { await this.setHoistedNoteId('root'); } - async setHoistedNoteId(noteIdToHoist) { + async setHoistedNoteId(noteIdToHoist: string) { if (this.hoistedNoteId === noteIdToHoist) { return; } @@ -225,7 +245,7 @@ class NoteContext extends Component { /** @returns {Promise} */ async isReadOnly() { - if (this.viewScope.readOnlyTemporarilyDisabled) { + if (this?.viewScope?.readOnlyTemporarilyDisabled) { return false; } @@ -238,22 +258,26 @@ class NoteContext extends Component { return true; } - if (this.viewScope.viewMode === 'source') { + if (this.viewScope?.viewMode === 'source') { return true; } const blob = await this.note.getBlob(); + if (!blob) { + return false; + } const sizeLimit = this.note.type === 'text' ? options.getInt('autoReadonlySizeText') : options.getInt('autoReadonlySizeCode'); - return blob.contentLength > sizeLimit + return sizeLimit + && blob.contentLength > sizeLimit && !this.note.isLabelTruthy('autoReadOnlyDisabled'); } - async entitiesReloadedEvent({loadResults}) { - if (loadResults.isNoteReloaded(this.noteId)) { + async entitiesReloadedEvent({loadResults}: EventData<"entitiesReloaded">) { + if (this.noteId && loadResults.isNoteReloaded(this.noteId)) { const noteRow = loadResults.getEntityRow('notes', this.noteId); if (noteRow.isDeleted) { @@ -270,14 +294,14 @@ class NoteContext extends Component { hasNoteList() { return this.note - && this.viewScope.viewMode === 'default' + && this.viewScope?.viewMode === 'default' && this.note.hasChildren() && ['book', 'text', 'code'].includes(this.note.type) && this.note.mime !== 'text/x-sqlite;schema=trilium' && !this.note.isLabelTruthy('hideChildrenOverview'); } - async getTextEditor(callback) { + async getTextEditor(callback: GetTextEditorCallback) { return this.timeout(new Promise(resolve => appContext.triggerCommand('executeWithTextEditor', { callback, resolve, @@ -306,7 +330,7 @@ class NoteContext extends Component { }))); } - timeout(promise) { + timeout(promise: Promise) { return Promise.race([ promise, new Promise(res => setTimeout(() => res(null), 200)) @@ -327,11 +351,11 @@ class NoteContext extends Component { const { note, viewScope } = this; - let title = viewScope.viewMode === 'default' + let title = viewScope?.viewMode === 'default' ? note.title - : `${note.title}: ${viewScope.viewMode}`; + : `${note.title}: ${viewScope?.viewMode}`; - if (viewScope.attachmentId) { + if (viewScope?.attachmentId) { // assuming the attachment has been already loaded const attachment = await note.getAttachmentById(viewScope.attachmentId); diff --git a/src/public/app/server_types.ts b/src/public/app/server_types.ts index e3e87c771..0909e932c 100644 --- a/src/public/app/server_types.ts +++ b/src/public/app/server_types.ts @@ -1,8 +1,10 @@ +import { EntityRowNames } from "./services/load_results.js"; + // TODO: Deduplicate with src/services/entity_changes_interface.ts export interface EntityChange { id?: number | null; noteId?: string; - entityName: string; + entityName: EntityRowNames; entityId: string; entity?: any; positions?: Record; diff --git a/src/public/app/services/link.ts b/src/public/app/services/link.ts index 671980242..3d44a624d 100644 --- a/src/public/app/services/link.ts +++ b/src/public/app/services/link.ts @@ -30,6 +30,7 @@ type ViewMode = "default" | "source" | "attachments" | string; export interface ViewScope { viewMode?: ViewMode; attachmentId?: string; + readOnlyTemporarilyDisabled?: boolean; } interface CreateLinkOptions { diff --git a/src/public/app/services/load_results.ts b/src/public/app/services/load_results.ts index b72fa2852..d06e9636e 100644 --- a/src/public/app/services/load_results.ts +++ b/src/public/app/services/load_results.ts @@ -1,3 +1,4 @@ +import { NoteRow } from "../../../becca/entities/rows.js"; import { EntityChange } from "../server_types.js"; interface BranchRow { @@ -30,8 +31,16 @@ interface ContentNoteIdToComponentIdRow { componentId: string; } +type EntityRowMappings = { + "notes": NoteRow, + "branches": BranchRow, + "attributes": AttributeRow +} + +export type EntityRowNames = keyof EntityRowMappings; + export default class LoadResults { - private entities: Record>; + private entities: Record>; private noteIdToComponentId: Record; private componentIdToNoteIds: Record; private branchRows: BranchRow[]; @@ -43,14 +52,15 @@ export default class LoadResults { private attachmentRows: AttachmentRow[]; constructor(entityChanges: EntityChange[]) { - this.entities = {}; + const entities: Record> = {}; for (const {entityId, entityName, entity} of entityChanges) { if (entity) { - this.entities[entityName] = this.entities[entityName] || []; - this.entities[entityName][entityId] = entity; + entities[entityName] = entities[entityName] || []; + entities[entityName][entityId] = entity; } } + this.entities = entities; this.noteIdToComponentId = {}; this.componentIdToNoteIds = {}; @@ -70,8 +80,8 @@ export default class LoadResults { this.attachmentRows = []; } - getEntityRow(entityName: string, entityId: string) { - return this.entities[entityName]?.[entityId]; + getEntityRow(entityName: T, entityId: string): EntityRowMappings[T] { + return (this.entities[entityName]?.[entityId]); } addNote(noteId: string, componentId?: string | null) { diff --git a/src/public/app/services/protected_session_holder.ts b/src/public/app/services/protected_session_holder.ts index 9b0287999..e4171f389 100644 --- a/src/public/app/services/protected_session_holder.ts +++ b/src/public/app/services/protected_session_holder.ts @@ -21,7 +21,7 @@ async function touchProtectedSession() { } } -function touchProtectedSessionIfNecessary(note: FNote) { +function touchProtectedSessionIfNecessary(note: FNote | null) { if (note && note.isProtected && isProtectedSessionAvailable()) { touchProtectedSession(); } diff --git a/src/public/app/services/utils.ts b/src/public/app/services/utils.ts index 9fbd929ac..63760d299 100644 --- a/src/public/app/services/utils.ts +++ b/src/public/app/services/utils.ts @@ -382,7 +382,7 @@ function escapeRegExp(str: string) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } -function areObjectsEqual () { +function areObjectsEqual(...args: unknown[]) { let i; let l; let leftChain: Object[];