mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-01 20:32:19 +08:00
chore(client/ts): port components/note_context
This commit is contained in:
parent
c06dc23ecf
commit
8a47b2f5a8
@ -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[];
|
||||
|
Loading…
x
Reference in New Issue
Block a user