chore(client/ts): port components/note_context

This commit is contained in:
Elian Doran 2024-12-23 15:16:41 +02:00
parent c06dc23ecf
commit 8a47b2f5a8
No known key found for this signature in database
7 changed files with 85 additions and 38 deletions

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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>;

View File

@ -30,6 +30,7 @@ type ViewMode = "default" | "source" | "attachments" | string;
export interface ViewScope {
viewMode?: ViewMode;
attachmentId?: string;
readOnlyTemporarilyDisabled?: boolean;
}
interface CreateLinkOptions {

View File

@ -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) {

View File

@ -21,7 +21,7 @@ async function touchProtectedSession() {
}
}
function touchProtectedSessionIfNecessary(note: FNote) {
function touchProtectedSessionIfNecessary(note: FNote | null) {
if (note && note.isProtected && isProtectedSessionAvailable()) {
touchProtectedSession();
}

View File

@ -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[];