Skip to content

Commit d85c670

Browse files
authored
Merge pull request #1268 from TriliumNext/port/client_ts
Port note tree to TypeScript
2 parents 28ed616 + 23d01ec commit d85c670

19 files changed

+1458
-205
lines changed

src/public/app/components/app_context.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import type NoteDetailWidget from "../widgets/note_detail.js";
1818
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
1919
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
2020
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
21-
import type { Node } from "../services/tree.js";
2221
import type LoadResults from "../services/load_results.js";
2322
import type { Attribute } from "../services/attribute_parser.js";
2423
import type NoteTreeWidget from "../widgets/note_tree.js";
@@ -49,15 +48,15 @@ export interface CommandData {
4948
* Represents a set of commands that are triggered from the context menu, providing information such as the selected note.
5049
*/
5150
export interface ContextMenuCommandData extends CommandData {
52-
node: Node;
53-
notePath: string;
51+
node: Fancytree.FancytreeNode;
52+
notePath?: string;
5453
noteId?: string;
55-
selectedOrActiveBranchIds: any; // TODO: Remove any once type is defined
54+
selectedOrActiveBranchIds?: any; // TODO: Remove any once type is defined
5655
selectedOrActiveNoteIds: any; // TODO: Remove any once type is defined
5756
}
5857

5958
export interface NoteCommandData extends CommandData {
60-
notePath: string;
59+
notePath?: string;
6160
hoistedNoteId?: string;
6261
viewScope?: ViewScope;
6362
}
@@ -72,13 +71,15 @@ export interface ExecuteCommandData<T> extends CommandData {
7271
export type CommandMappings = {
7372
"api-log-messages": CommandData;
7473
focusTree: CommandData,
74+
focusOnTitle: CommandData;
7575
focusOnDetail: CommandData;
7676
focusOnSearchDefinition: Required<CommandData>;
7777
searchNotes: CommandData & {
7878
searchString?: string;
7979
ancestorNoteId?: string | null;
8080
};
8181
closeTocCommand: CommandData;
82+
closeHlt: CommandData;
8283
showLaunchBarSubtree: CommandData;
8384
showRevisions: CommandData;
8485
showOptions: CommandData & {
@@ -106,13 +107,18 @@ export type CommandMappings = {
106107
showPromptDialog: PromptDialogOptions;
107108
showInfoDialog: ConfirmWithMessageOptions;
108109
showConfirmDialog: ConfirmWithMessageOptions;
110+
showRecentChanges: CommandData & { ancestorNoteId: string };
111+
showImportDialog: CommandData & { noteId: string; };
109112
openNewNoteSplit: NoteCommandData;
110113
openInWindow: NoteCommandData;
111114
openNoteInNewTab: CommandData;
112115
openNoteInNewSplit: CommandData;
113116
openNoteInNewWindow: CommandData;
117+
openAboutDialog: CommandData;
118+
hideFloatingButtons: {};
114119
hideLeftPane: CommandData;
115120
showLeftPane: CommandData;
121+
hoistNote: CommandData & { noteId: string };
116122
leaveProtectedSession: CommandData;
117123
enterProtectedSession: CommandData;
118124

@@ -122,9 +128,12 @@ export type CommandMappings = {
122128
insertNoteAfter: ContextMenuCommandData;
123129
insertChildNote: ContextMenuCommandData;
124130
delete: ContextMenuCommandData;
131+
editNoteTitle: ContextMenuCommandData;
125132
protectSubtree: ContextMenuCommandData;
126133
unprotectSubtree: ContextMenuCommandData;
127-
openBulkActionsDialog: ContextMenuCommandData;
134+
openBulkActionsDialog: ContextMenuCommandData | {
135+
selectedOrActiveNoteIds?: string[]
136+
};
128137
editBranchPrefix: ContextMenuCommandData;
129138
convertNoteToAttachment: ContextMenuCommandData;
130139
duplicateSubtree: ContextMenuCommandData;
@@ -143,6 +152,11 @@ export type CommandMappings = {
143152
importIntoNote: ContextMenuCommandData;
144153
exportNote: ContextMenuCommandData;
145154
searchInSubtree: ContextMenuCommandData;
155+
moveNoteUp: ContextMenuCommandData;
156+
moveNoteDown: ContextMenuCommandData;
157+
moveNoteUpInHierarchy: ContextMenuCommandData;
158+
moveNoteDownInHierarchy: ContextMenuCommandData;
159+
selectAllNotesInParent: ContextMenuCommandData;
146160

147161
addNoteLauncher: ContextMenuCommandData;
148162
addScriptLauncher: ContextMenuCommandData;
@@ -175,6 +189,7 @@ export type CommandMappings = {
175189
importMarkdownInline: CommandData;
176190
showPasswordNotSet: CommandData;
177191
showProtectedSessionPasswordDialog: CommandData;
192+
showUploadAttachmentsDialog: CommandData & { noteId: string };
178193
closeProtectedSessionPasswordDialog: CommandData;
179194
copyImageReferenceToClipboard: CommandData;
180195
copyImageToClipboard: CommandData;
@@ -198,6 +213,7 @@ export type CommandMappings = {
198213
screen: Screen;
199214
};
200215
closeTab: CommandData;
216+
closeToc: CommandData;
201217
closeOtherTabs: CommandData;
202218
closeRightTabs: CommandData;
203219
closeAllTabs: CommandData;
@@ -216,15 +232,20 @@ export type CommandMappings = {
216232
scrollContainerToCommand: CommandData & {
217233
position: number;
218234
};
219-
moveThisNoteSplit: CommandData & {
220-
isMovingLeft: boolean;
221-
};
235+
scrollToEnd: CommandData;
236+
closeThisNoteSplit: CommandData;
237+
moveThisNoteSplit: CommandData & { isMovingLeft: boolean; };
222238

223239
// Geomap
224240
deleteFromMap: { noteId: string },
225241
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
226242

227243
toggleZenMode: CommandData;
244+
245+
updateAttributeList: CommandData & { attributes: Attribute[] };
246+
saveAttributes: CommandData;
247+
reloadAttributes: CommandData;
248+
refreshNoteList: CommandData & { noteId: string; };
228249
};
229250

230251
type EventMappings = {
@@ -329,7 +350,6 @@ type EventMappings = {
329350
showToc: {
330351
noteId: string;
331352
};
332-
scrollToEnd: { ntxId: string };
333353
noteTypeMimeChanged: { noteId: string };
334354
zenModeChanged: { isEnabled: boolean };
335355
};

src/public/app/components/component.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
8080
return promises.length > 0 ? Promise.all(promises) : null;
8181
}
8282

83-
triggerCommand<K extends CommandNames>(name: string, _data?: CommandMappings[K]): Promise<unknown> | undefined | null {
84-
const data = _data || {};
83+
triggerCommand<K extends CommandNames>(name: K, data?: CommandMappings[K]): Promise<unknown> | undefined | null {
8584
const fun = (this as any)[`${name}Command`];
8685

8786
if (fun) {

src/public/app/components/note_context.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { ViewScope } from "../services/link.js";
1111
import type FNote from "../entities/fnote.js";
1212
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
1313

14-
interface SetNoteOpts {
14+
export interface SetNoteOpts {
1515
triggerSwitchEvent?: unknown;
1616
viewScope?: ViewScope;
1717
}

src/public/app/entities/fbranch.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface FBranchRow {
88
prefix?: string;
99
isExpanded?: boolean;
1010
fromSearchNote: boolean;
11+
isDeleted?: boolean;
1112
}
1213

1314
/**

src/public/app/menus/launcher_context_menu.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import treeService, { type Node } from "../services/tree.js";
1+
import treeService from "../services/tree.js";
22
import froca from "../services/froca.js";
33
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
44
import dialogService from "../services/dialog.js";
@@ -12,17 +12,17 @@ type LauncherCommandNames = FilteredCommandNames<ContextMenuCommandData>;
1212

1313
export default class LauncherContextMenu implements SelectMenuItemEventListener<LauncherCommandNames> {
1414
private treeWidget: NoteTreeWidget;
15-
private node: Node;
15+
private node: Fancytree.FancytreeNode;
1616

17-
constructor(treeWidget: NoteTreeWidget, node: Node) {
17+
constructor(treeWidget: NoteTreeWidget, node: Fancytree.FancytreeNode) {
1818
this.treeWidget = treeWidget;
1919
this.node = node;
2020
}
2121

22-
async show(e: PointerEvent) {
22+
async show(e: PointerEvent | JQuery.TouchStartEvent | JQuery.ContextMenuEvent) {
2323
contextMenu.show({
24-
x: e.pageX,
25-
y: e.pageY,
24+
x: e.pageX ?? 0,
25+
y: e.pageY ?? 0,
2626
items: await this.getMenuItems(),
2727
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
2828
});

src/public/app/menus/tree_context_menu.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import treeService, { type Node } from "../services/tree.js";
1+
import treeService from "../services/tree.js";
22
import froca from "../services/froca.js";
33
import clipboard from "../services/clipboard.js";
44
import noteCreateService from "../services/note_create.js";
@@ -18,21 +18,23 @@ interface ConvertToAttachmentResponse {
1818
attachment?: FAttachment;
1919
}
2020

21-
type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData>;
21+
// This will include all commands that implement ContextMenuCommandData, but it will not work if it additional options are added via the `|` operator,
22+
// so they need to be added manually.
23+
export type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData> | "openBulkActionsDialog";
2224

2325
export default class TreeContextMenu implements SelectMenuItemEventListener<TreeCommandNames> {
2426
private treeWidget: NoteTreeWidget;
25-
private node: Node;
27+
private node: Fancytree.FancytreeNode;
2628

27-
constructor(treeWidget: NoteTreeWidget, node: Node) {
29+
constructor(treeWidget: NoteTreeWidget, node: Fancytree.FancytreeNode) {
2830
this.treeWidget = treeWidget;
2931
this.node = node;
3032
}
3133

32-
async show(e: PointerEvent) {
34+
async show(e: PointerEvent | JQuery.TouchStartEvent | JQuery.ContextMenuEvent) {
3335
contextMenu.show({
34-
x: e.pageX,
35-
y: e.pageY,
36+
x: e.pageX ?? 0,
37+
y: e.pageY ?? 0,
3638
items: await this.getMenuItems(),
3739
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
3840
});

src/public/app/services/branches.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import hoistedNoteService from "./hoisted_note.js";
66
import ws from "./ws.js";
77
import appContext from "../components/app_context.js";
88
import { t } from "./i18n.js";
9-
import type { Node } from "./tree.js";
109
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
1110

1211
// TODO: Deduplicate type with server
@@ -160,7 +159,7 @@ async function activateParentNotePath() {
160159
}
161160
}
162161

163-
async function moveNodeUpInHierarchy(node: Node) {
162+
async function moveNodeUpInHierarchy(node: Fancytree.FancytreeNode) {
164163
if (hoistedNoteService.isHoistedNode(node) || hoistedNoteService.isTopLevelNode(node) || node.getParent().data.noteType === "search") {
165164
return;
166165
}

src/public/app/services/hoisted_note.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import appContext from "../components/app_context.js";
2-
import treeService, { type Node } from "./tree.js";
2+
import treeService from "./tree.js";
33
import dialogService from "./dialog.js";
44
import froca from "./froca.js";
55
import type NoteContext from "../components/note_context.js";
@@ -19,11 +19,11 @@ async function unhoist() {
1919
}
2020
}
2121

22-
function isTopLevelNode(node: Node) {
22+
function isTopLevelNode(node: Fancytree.FancytreeNode) {
2323
return isHoistedNode(node.getParent());
2424
}
2525

26-
function isHoistedNode(node: Node) {
26+
function isHoistedNode(node: Fancytree.FancytreeNode) {
2727
// even though check for 'root' should not be necessary, we keep it just in case
2828
return node.data.noteId === "root" || node.data.noteId === getHoistedNoteId();
2929
}

src/public/app/services/import.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ import utils from "./utils.js";
55
import appContext from "../components/app_context.js";
66
import { t } from "./i18n.js";
77

8-
export async function uploadFiles(entityType: string, parentNoteId: string, files: string[], options: Record<string, string | Blob>) {
8+
interface UploadFilesOptions {
9+
safeImport: boolean;
10+
shrinkImages: boolean;
11+
textImportedAsText: boolean;
12+
codeImportedAsCode: boolean;
13+
explodeArchives: boolean;
14+
replaceUnderscoresWithSpaces: boolean;
15+
}
16+
17+
export async function uploadFiles(entityType: string, parentNoteId: string, files: string[], options: UploadFilesOptions) {
918
if (!["notes", "attachments"].includes(entityType)) {
1019
throw new Error(`Unrecognized import entity type '${entityType}'.`);
1120
}
@@ -26,7 +35,7 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
2635
formData.append("last", counter === files.length ? "true" : "false");
2736

2837
for (const key in options) {
29-
formData.append(key, options[key]);
38+
formData.append(key, (options as any)[key]);
3039
}
3140

3241
await $.ajax({

src/public/app/services/load_results.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ interface NoteRow {
88
isDeleted?: boolean;
99
}
1010

11-
interface BranchRow {
11+
// TODO: Deduplicate with BranchRow from `rows.ts`/
12+
export interface BranchRow {
1213
noteId?: string;
1314
branchId: string;
1415
componentId: string;
1516
parentNoteId?: string;
17+
isDeleted?: boolean;
18+
isExpanded?: boolean;
1619
}
1720

1821
export interface AttributeRow {

src/public/app/services/note_types.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import server from "./server.js";
22
import froca from "./froca.js";
33
import { t } from "./i18n.js";
44
import type { MenuItem } from "../menus/context_menu.js";
5-
import type { ContextMenuCommandData, FilteredCommandNames } from "../components/app_context.js";
5+
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
66

7-
type NoteTypeCommandNames = FilteredCommandNames<ContextMenuCommandData>;
8-
9-
async function getNoteTypeItems(command?: NoteTypeCommandNames) {
10-
const items: MenuItem<NoteTypeCommandNames>[] = [
7+
async function getNoteTypeItems(command?: TreeCommandNames) {
8+
const items: MenuItem<TreeCommandNames>[] = [
119
{ title: t("note_types.text"), command, type: "text", uiIcon: "bx bx-note" },
1210
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
1311
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },

src/public/app/services/tree.ts

+2-16
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,6 @@ import froca from "./froca.js";
44
import hoistedNoteService from "../services/hoisted_note.js";
55
import appContext from "../components/app_context.js";
66

7-
export interface Node {
8-
title: string;
9-
getParent(): Node;
10-
getChildren(): Node[];
11-
folder: boolean;
12-
renderTitle(): void;
13-
data: {
14-
noteId?: string;
15-
isProtected?: boolean;
16-
branchId: string;
17-
noteType: string;
18-
};
19-
}
20-
217
/**
228
* @returns {string|null}
239
*/
@@ -148,7 +134,7 @@ ws.subscribeToMessages((message) => {
148134
}
149135
});
150136

151-
function getParentProtectedStatus(node: Node) {
137+
function getParentProtectedStatus(node: Fancytree.FancytreeNode) {
152138
return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected;
153139
}
154140

@@ -205,7 +191,7 @@ function getNoteIdAndParentIdFromUrl(urlOrNotePath: string) {
205191
};
206192
}
207193

208-
function getNotePath(node: Node) {
194+
function getNotePath(node: Fancytree.FancytreeNode) {
209195
if (!node) {
210196
logError("Node is null");
211197
return "";

src/public/app/services/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.
137137
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
138138
}
139139

140-
function assertArguments(...args: string[]) {
140+
function assertArguments<T>(...args: T[]) {
141141
for (const i in args) {
142142
if (!args[i]) {
143143
console.trace(`Argument idx#${i} should not be falsy: ${args[i]}`);

0 commit comments

Comments
 (0)