mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	chore(client/ts): port components/note_context
This commit is contained in:
		| @@ -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<NoteDetailWidget>) => 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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<boolean>} */ | ||||
|     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<unknown>) { | ||||
|         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); | ||||
| 
 | ||||
| @@ -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<string, number>; | ||||
|   | ||||
| @@ -30,6 +30,7 @@ type ViewMode = "default" | "source" | "attachments" | string; | ||||
| export interface ViewScope { | ||||
|     viewMode?: ViewMode; | ||||
|     attachmentId?: string; | ||||
|     readOnlyTemporarilyDisabled?: boolean; | ||||
| } | ||||
|  | ||||
| interface CreateLinkOptions { | ||||
|   | ||||
| @@ -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<string, Record<string, unknown>>; | ||||
|     private entities: Record<keyof EntityRowMappings, Record<string, any>>; | ||||
|     private noteIdToComponentId: Record<string, string[]>; | ||||
|     private componentIdToNoteIds: Record<string, string[]>; | ||||
|     private branchRows: BranchRow[]; | ||||
| @@ -43,14 +52,15 @@ export default class LoadResults { | ||||
|     private attachmentRows: AttachmentRow[]; | ||||
|  | ||||
|     constructor(entityChanges: EntityChange[]) { | ||||
|         this.entities = {}; | ||||
|         const entities: Record<string, Record<string, any>> = {}; | ||||
|  | ||||
|         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<T extends EntityRowNames>(entityName: T, entityId: string): EntityRowMappings[T] { | ||||
|         return (this.entities[entityName]?.[entityId]); | ||||
|     } | ||||
|  | ||||
|     addNote(noteId: string, componentId?: string | null) { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ async function touchProtectedSession() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function touchProtectedSessionIfNecessary(note: FNote) { | ||||
| function touchProtectedSessionIfNecessary(note: FNote | null) { | ||||
|     if (note && note.isProtected && isProtectedSessionAvailable()) { | ||||
|         touchProtectedSession(); | ||||
|     } | ||||
|   | ||||
| @@ -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[]; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user