mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-05 07:08:14 +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 LoadResults from "../services/load_results.js";
|
||||||
import { Attribute } from "../services/attribute_parser.js";
|
import { Attribute } from "../services/attribute_parser.js";
|
||||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||||
|
import { GetTextEditorCallback } from "./note_context.js";
|
||||||
|
|
||||||
interface Layout {
|
interface Layout {
|
||||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
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}).
|
* Base interface for the data/arguments for a given command (see {@link CommandMappings}).
|
||||||
*/
|
*/
|
||||||
export interface CommandData {
|
export interface CommandData {
|
||||||
ntxId?: string;
|
ntxId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,6 +60,10 @@ export interface NoteCommandData extends CommandData {
|
|||||||
viewScope?: ViewScope;
|
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}.
|
* 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 & {
|
executeInActiveNoteDetailWidget: CommandData & {
|
||||||
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void
|
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void
|
||||||
};
|
};
|
||||||
|
executeWithTextEditor: CommandData & ExecuteCommandData & {
|
||||||
|
callback?: GetTextEditorCallback;
|
||||||
|
};
|
||||||
|
executeWithCodeEditor: CommandData & ExecuteCommandData;
|
||||||
|
executeWithContentElement: CommandData & ExecuteCommandData;
|
||||||
|
executeWithTypeWidget: CommandData & ExecuteCommandData;
|
||||||
addTextToActiveEditor: CommandData & {
|
addTextToActiveEditor: CommandData & {
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
@ -181,8 +192,7 @@ type EventMappings = {
|
|||||||
};
|
};
|
||||||
addNewLabel: CommandData;
|
addNewLabel: CommandData;
|
||||||
addNewRelation: CommandData;
|
addNewRelation: CommandData;
|
||||||
sqlQueryResults: {
|
sqlQueryResults: CommandData & {
|
||||||
ntxId: string;
|
|
||||||
results: SqlExecuteResults;
|
results: SqlExecuteResults;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,39 @@
|
|||||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||||
import server from "../services/server.js";
|
import server from "../services/server.js";
|
||||||
import utils from "../services/utils.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 treeService from "../services/tree.js";
|
||||||
import Component from "./component.js";
|
import Component from "./component.js";
|
||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import hoistedNoteService from "../services/hoisted_note.js";
|
import hoistedNoteService from "../services/hoisted_note.js";
|
||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
|
import { ViewScope } from "../services/link.js";
|
||||||
|
import FNote from "../entities/fnote.js";
|
||||||
|
|
||||||
class NoteContext extends Component {
|
interface SetNoteOpts {
|
||||||
constructor(ntxId = null, hoistedNoteId = 'root', mainNtxId = null) {
|
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();
|
super();
|
||||||
|
|
||||||
this.ntxId = ntxId || this.constructor.generateNtxId();
|
this.ntxId = ntxId || NoteContext.generateNtxId();
|
||||||
this.hoistedNoteId = hoistedNoteId;
|
this.hoistedNoteId = hoistedNoteId;
|
||||||
this.mainNtxId = mainNtxId;
|
this.mainNtxId = mainNtxId;
|
||||||
|
|
||||||
@ -41,7 +62,7 @@ class NoteContext extends Component {
|
|||||||
return !this.noteId;
|
return !this.noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setNote(inputNotePath, opts = {}) {
|
async setNote(inputNotePath: string, opts: SetNoteOpts = {}) {
|
||||||
opts.triggerSwitchEvent = opts.triggerSwitchEvent !== undefined ? opts.triggerSwitchEvent : true;
|
opts.triggerSwitchEvent = opts.triggerSwitchEvent !== undefined ? opts.triggerSwitchEvent : true;
|
||||||
opts.viewScope = opts.viewScope || {};
|
opts.viewScope = opts.viewScope || {};
|
||||||
opts.viewScope.viewMode = opts.viewScope.viewMode || "default";
|
opts.viewScope.viewMode = opts.viewScope.viewMode || "default";
|
||||||
@ -84,16 +105,16 @@ class NoteContext extends Component {
|
|||||||
|
|
||||||
async setHoistedNoteIfNeeded() {
|
async setHoistedNoteIfNeeded() {
|
||||||
if (this.hoistedNoteId === 'root'
|
if (this.hoistedNoteId === 'root'
|
||||||
&& this.notePath.startsWith("root/_hidden")
|
&& this.notePath?.startsWith("root/_hidden")
|
||||||
&& !this.note.isLabelTruthy("keepCurrentHoisting")
|
&& !this.note?.isLabelTruthy("keepCurrentHoisting")
|
||||||
) {
|
) {
|
||||||
// hidden subtree displays only when hoisted, so it doesn't make sense to keep root as hoisted note
|
// hidden subtree displays only when hoisted, so it doesn't make sense to keep root as hoisted note
|
||||||
|
|
||||||
let hoistedNoteId = '_hidden';
|
let hoistedNoteId = '_hidden';
|
||||||
|
|
||||||
if (this.note.isLaunchBarConfig()) {
|
if (this.note?.isLaunchBarConfig()) {
|
||||||
hoistedNoteId = '_lbRoot';
|
hoistedNoteId = '_lbRoot';
|
||||||
} else if (this.note.isOptions()) {
|
} else if (this.note?.isOptions()) {
|
||||||
hoistedNoteId = '_options';
|
hoistedNoteId = '_options';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,19 +159,19 @@ class NoteContext extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToRecentNotes(resolvedNotePath) {
|
saveToRecentNotes(resolvedNotePath: string) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
// we include the note in the recent list only if the user stayed on the note at least 5 seconds
|
// 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) {
|
if (resolvedNotePath && resolvedNotePath === this.notePath) {
|
||||||
await server.post('recent-notes', {
|
await server.post('recent-notes', {
|
||||||
noteId: this.note.noteId,
|
noteId: this.note?.noteId,
|
||||||
notePath: this.notePath
|
notePath: this.notePath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResolvedNotePath(inputNotePath) {
|
async getResolvedNotePath(inputNotePath: string) {
|
||||||
const resolvedNotePath = await treeService.resolveNotePath(inputNotePath, this.hoistedNoteId);
|
const resolvedNotePath = await treeService.resolveNotePath(inputNotePath, this.hoistedNoteId);
|
||||||
|
|
||||||
if (!resolvedNotePath) {
|
if (!resolvedNotePath) {
|
||||||
@ -165,8 +186,7 @@ class NoteContext extends Component {
|
|||||||
return resolvedNotePath;
|
return resolvedNotePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote} */
|
get note(): FNote | null {
|
||||||
get note() {
|
|
||||||
if (!this.noteId || !(this.noteId in froca.notes)) {
|
if (!this.noteId || !(this.noteId in froca.notes)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -206,7 +226,7 @@ class NoteContext extends Component {
|
|||||||
await this.setHoistedNoteId('root');
|
await this.setHoistedNoteId('root');
|
||||||
}
|
}
|
||||||
|
|
||||||
async setHoistedNoteId(noteIdToHoist) {
|
async setHoistedNoteId(noteIdToHoist: string) {
|
||||||
if (this.hoistedNoteId === noteIdToHoist) {
|
if (this.hoistedNoteId === noteIdToHoist) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -225,7 +245,7 @@ class NoteContext extends Component {
|
|||||||
|
|
||||||
/** @returns {Promise<boolean>} */
|
/** @returns {Promise<boolean>} */
|
||||||
async isReadOnly() {
|
async isReadOnly() {
|
||||||
if (this.viewScope.readOnlyTemporarilyDisabled) {
|
if (this?.viewScope?.readOnlyTemporarilyDisabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,22 +258,26 @@ class NoteContext extends Component {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.viewScope.viewMode === 'source') {
|
if (this.viewScope?.viewMode === 'source') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await this.note.getBlob();
|
const blob = await this.note.getBlob();
|
||||||
|
if (!blob) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const sizeLimit = this.note.type === 'text'
|
const sizeLimit = this.note.type === 'text'
|
||||||
? options.getInt('autoReadonlySizeText')
|
? options.getInt('autoReadonlySizeText')
|
||||||
: options.getInt('autoReadonlySizeCode');
|
: options.getInt('autoReadonlySizeCode');
|
||||||
|
|
||||||
return blob.contentLength > sizeLimit
|
return sizeLimit
|
||||||
|
&& blob.contentLength > sizeLimit
|
||||||
&& !this.note.isLabelTruthy('autoReadOnlyDisabled');
|
&& !this.note.isLabelTruthy('autoReadOnlyDisabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({loadResults}) {
|
async entitiesReloadedEvent({loadResults}: EventData<"entitiesReloaded">) {
|
||||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
if (this.noteId && loadResults.isNoteReloaded(this.noteId)) {
|
||||||
const noteRow = loadResults.getEntityRow('notes', this.noteId);
|
const noteRow = loadResults.getEntityRow('notes', this.noteId);
|
||||||
|
|
||||||
if (noteRow.isDeleted) {
|
if (noteRow.isDeleted) {
|
||||||
@ -270,14 +294,14 @@ class NoteContext extends Component {
|
|||||||
|
|
||||||
hasNoteList() {
|
hasNoteList() {
|
||||||
return this.note
|
return this.note
|
||||||
&& this.viewScope.viewMode === 'default'
|
&& this.viewScope?.viewMode === 'default'
|
||||||
&& this.note.hasChildren()
|
&& this.note.hasChildren()
|
||||||
&& ['book', 'text', 'code'].includes(this.note.type)
|
&& ['book', 'text', 'code'].includes(this.note.type)
|
||||||
&& this.note.mime !== 'text/x-sqlite;schema=trilium'
|
&& this.note.mime !== 'text/x-sqlite;schema=trilium'
|
||||||
&& !this.note.isLabelTruthy('hideChildrenOverview');
|
&& !this.note.isLabelTruthy('hideChildrenOverview');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTextEditor(callback) {
|
async getTextEditor(callback: GetTextEditorCallback) {
|
||||||
return this.timeout(new Promise(resolve => appContext.triggerCommand('executeWithTextEditor', {
|
return this.timeout(new Promise(resolve => appContext.triggerCommand('executeWithTextEditor', {
|
||||||
callback,
|
callback,
|
||||||
resolve,
|
resolve,
|
||||||
@ -306,7 +330,7 @@ class NoteContext extends Component {
|
|||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout(promise) {
|
timeout(promise: Promise<unknown>) {
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
promise,
|
promise,
|
||||||
new Promise(res => setTimeout(() => res(null), 200))
|
new Promise(res => setTimeout(() => res(null), 200))
|
||||||
@ -327,11 +351,11 @@ class NoteContext extends Component {
|
|||||||
|
|
||||||
const { note, viewScope } = this;
|
const { note, viewScope } = this;
|
||||||
|
|
||||||
let title = viewScope.viewMode === 'default'
|
let title = viewScope?.viewMode === 'default'
|
||||||
? note.title
|
? note.title
|
||||||
: `${note.title}: ${viewScope.viewMode}`;
|
: `${note.title}: ${viewScope?.viewMode}`;
|
||||||
|
|
||||||
if (viewScope.attachmentId) {
|
if (viewScope?.attachmentId) {
|
||||||
// assuming the attachment has been already loaded
|
// assuming the attachment has been already loaded
|
||||||
const attachment = await note.getAttachmentById(viewScope.attachmentId);
|
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
|
// TODO: Deduplicate with src/services/entity_changes_interface.ts
|
||||||
export interface EntityChange {
|
export interface EntityChange {
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
entityName: string;
|
entityName: EntityRowNames;
|
||||||
entityId: string;
|
entityId: string;
|
||||||
entity?: any;
|
entity?: any;
|
||||||
positions?: Record<string, number>;
|
positions?: Record<string, number>;
|
||||||
|
@ -30,6 +30,7 @@ type ViewMode = "default" | "source" | "attachments" | string;
|
|||||||
export interface ViewScope {
|
export interface ViewScope {
|
||||||
viewMode?: ViewMode;
|
viewMode?: ViewMode;
|
||||||
attachmentId?: string;
|
attachmentId?: string;
|
||||||
|
readOnlyTemporarilyDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateLinkOptions {
|
interface CreateLinkOptions {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { NoteRow } from "../../../becca/entities/rows.js";
|
||||||
import { EntityChange } from "../server_types.js";
|
import { EntityChange } from "../server_types.js";
|
||||||
|
|
||||||
interface BranchRow {
|
interface BranchRow {
|
||||||
@ -30,8 +31,16 @@ interface ContentNoteIdToComponentIdRow {
|
|||||||
componentId: string;
|
componentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EntityRowMappings = {
|
||||||
|
"notes": NoteRow,
|
||||||
|
"branches": BranchRow,
|
||||||
|
"attributes": AttributeRow
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EntityRowNames = keyof EntityRowMappings;
|
||||||
|
|
||||||
export default class LoadResults {
|
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 noteIdToComponentId: Record<string, string[]>;
|
||||||
private componentIdToNoteIds: Record<string, string[]>;
|
private componentIdToNoteIds: Record<string, string[]>;
|
||||||
private branchRows: BranchRow[];
|
private branchRows: BranchRow[];
|
||||||
@ -43,14 +52,15 @@ export default class LoadResults {
|
|||||||
private attachmentRows: AttachmentRow[];
|
private attachmentRows: AttachmentRow[];
|
||||||
|
|
||||||
constructor(entityChanges: EntityChange[]) {
|
constructor(entityChanges: EntityChange[]) {
|
||||||
this.entities = {};
|
const entities: Record<string, Record<string, any>> = {};
|
||||||
|
|
||||||
for (const {entityId, entityName, entity} of entityChanges) {
|
for (const {entityId, entityName, entity} of entityChanges) {
|
||||||
if (entity) {
|
if (entity) {
|
||||||
this.entities[entityName] = this.entities[entityName] || [];
|
entities[entityName] = entities[entityName] || [];
|
||||||
this.entities[entityName][entityId] = entity;
|
entities[entityName][entityId] = entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.entities = entities;
|
||||||
|
|
||||||
this.noteIdToComponentId = {};
|
this.noteIdToComponentId = {};
|
||||||
this.componentIdToNoteIds = {};
|
this.componentIdToNoteIds = {};
|
||||||
@ -70,8 +80,8 @@ export default class LoadResults {
|
|||||||
this.attachmentRows = [];
|
this.attachmentRows = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityRow(entityName: string, entityId: string) {
|
getEntityRow<T extends EntityRowNames>(entityName: T, entityId: string): EntityRowMappings[T] {
|
||||||
return this.entities[entityName]?.[entityId];
|
return (this.entities[entityName]?.[entityId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
addNote(noteId: string, componentId?: string | null) {
|
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()) {
|
if (note && note.isProtected && isProtectedSessionAvailable()) {
|
||||||
touchProtectedSession();
|
touchProtectedSession();
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ function escapeRegExp(str: string) {
|
|||||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
||||||
}
|
}
|
||||||
|
|
||||||
function areObjectsEqual () {
|
function areObjectsEqual(...args: unknown[]) {
|
||||||
let i;
|
let i;
|
||||||
let l;
|
let l;
|
||||||
let leftChain: Object[];
|
let leftChain: Object[];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user