mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 19:12:27 +08:00
Merge pull request #1268 from TriliumNext/port/client_ts
Port note tree to TypeScript
This commit is contained in:
commit
d85c670d7b
@ -18,7 +18,6 @@ import type NoteDetailWidget from "../widgets/note_detail.js";
|
|||||||
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
||||||
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
||||||
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
|
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
|
||||||
import type { Node } from "../services/tree.js";
|
|
||||||
import type LoadResults from "../services/load_results.js";
|
import type LoadResults from "../services/load_results.js";
|
||||||
import type { Attribute } from "../services/attribute_parser.js";
|
import type { Attribute } from "../services/attribute_parser.js";
|
||||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||||
@ -49,15 +48,15 @@ export interface CommandData {
|
|||||||
* Represents a set of commands that are triggered from the context menu, providing information such as the selected note.
|
* Represents a set of commands that are triggered from the context menu, providing information such as the selected note.
|
||||||
*/
|
*/
|
||||||
export interface ContextMenuCommandData extends CommandData {
|
export interface ContextMenuCommandData extends CommandData {
|
||||||
node: Node;
|
node: Fancytree.FancytreeNode;
|
||||||
notePath: string;
|
notePath?: string;
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
selectedOrActiveBranchIds: any; // TODO: Remove any once type is defined
|
selectedOrActiveBranchIds?: any; // TODO: Remove any once type is defined
|
||||||
selectedOrActiveNoteIds: any; // TODO: Remove any once type is defined
|
selectedOrActiveNoteIds: any; // TODO: Remove any once type is defined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NoteCommandData extends CommandData {
|
export interface NoteCommandData extends CommandData {
|
||||||
notePath: string;
|
notePath?: string;
|
||||||
hoistedNoteId?: string;
|
hoistedNoteId?: string;
|
||||||
viewScope?: ViewScope;
|
viewScope?: ViewScope;
|
||||||
}
|
}
|
||||||
@ -72,6 +71,7 @@ export interface ExecuteCommandData<T> extends CommandData {
|
|||||||
export type CommandMappings = {
|
export type CommandMappings = {
|
||||||
"api-log-messages": CommandData;
|
"api-log-messages": CommandData;
|
||||||
focusTree: CommandData,
|
focusTree: CommandData,
|
||||||
|
focusOnTitle: CommandData;
|
||||||
focusOnDetail: CommandData;
|
focusOnDetail: CommandData;
|
||||||
focusOnSearchDefinition: Required<CommandData>;
|
focusOnSearchDefinition: Required<CommandData>;
|
||||||
searchNotes: CommandData & {
|
searchNotes: CommandData & {
|
||||||
@ -79,6 +79,7 @@ export type CommandMappings = {
|
|||||||
ancestorNoteId?: string | null;
|
ancestorNoteId?: string | null;
|
||||||
};
|
};
|
||||||
closeTocCommand: CommandData;
|
closeTocCommand: CommandData;
|
||||||
|
closeHlt: CommandData;
|
||||||
showLaunchBarSubtree: CommandData;
|
showLaunchBarSubtree: CommandData;
|
||||||
showRevisions: CommandData;
|
showRevisions: CommandData;
|
||||||
showOptions: CommandData & {
|
showOptions: CommandData & {
|
||||||
@ -106,13 +107,18 @@ export type CommandMappings = {
|
|||||||
showPromptDialog: PromptDialogOptions;
|
showPromptDialog: PromptDialogOptions;
|
||||||
showInfoDialog: ConfirmWithMessageOptions;
|
showInfoDialog: ConfirmWithMessageOptions;
|
||||||
showConfirmDialog: ConfirmWithMessageOptions;
|
showConfirmDialog: ConfirmWithMessageOptions;
|
||||||
|
showRecentChanges: CommandData & { ancestorNoteId: string };
|
||||||
|
showImportDialog: CommandData & { noteId: string; };
|
||||||
openNewNoteSplit: NoteCommandData;
|
openNewNoteSplit: NoteCommandData;
|
||||||
openInWindow: NoteCommandData;
|
openInWindow: NoteCommandData;
|
||||||
openNoteInNewTab: CommandData;
|
openNoteInNewTab: CommandData;
|
||||||
openNoteInNewSplit: CommandData;
|
openNoteInNewSplit: CommandData;
|
||||||
openNoteInNewWindow: CommandData;
|
openNoteInNewWindow: CommandData;
|
||||||
|
openAboutDialog: CommandData;
|
||||||
|
hideFloatingButtons: {};
|
||||||
hideLeftPane: CommandData;
|
hideLeftPane: CommandData;
|
||||||
showLeftPane: CommandData;
|
showLeftPane: CommandData;
|
||||||
|
hoistNote: CommandData & { noteId: string };
|
||||||
leaveProtectedSession: CommandData;
|
leaveProtectedSession: CommandData;
|
||||||
enterProtectedSession: CommandData;
|
enterProtectedSession: CommandData;
|
||||||
|
|
||||||
@ -122,9 +128,12 @@ export type CommandMappings = {
|
|||||||
insertNoteAfter: ContextMenuCommandData;
|
insertNoteAfter: ContextMenuCommandData;
|
||||||
insertChildNote: ContextMenuCommandData;
|
insertChildNote: ContextMenuCommandData;
|
||||||
delete: ContextMenuCommandData;
|
delete: ContextMenuCommandData;
|
||||||
|
editNoteTitle: ContextMenuCommandData;
|
||||||
protectSubtree: ContextMenuCommandData;
|
protectSubtree: ContextMenuCommandData;
|
||||||
unprotectSubtree: ContextMenuCommandData;
|
unprotectSubtree: ContextMenuCommandData;
|
||||||
openBulkActionsDialog: ContextMenuCommandData;
|
openBulkActionsDialog: ContextMenuCommandData | {
|
||||||
|
selectedOrActiveNoteIds?: string[]
|
||||||
|
};
|
||||||
editBranchPrefix: ContextMenuCommandData;
|
editBranchPrefix: ContextMenuCommandData;
|
||||||
convertNoteToAttachment: ContextMenuCommandData;
|
convertNoteToAttachment: ContextMenuCommandData;
|
||||||
duplicateSubtree: ContextMenuCommandData;
|
duplicateSubtree: ContextMenuCommandData;
|
||||||
@ -143,6 +152,11 @@ export type CommandMappings = {
|
|||||||
importIntoNote: ContextMenuCommandData;
|
importIntoNote: ContextMenuCommandData;
|
||||||
exportNote: ContextMenuCommandData;
|
exportNote: ContextMenuCommandData;
|
||||||
searchInSubtree: ContextMenuCommandData;
|
searchInSubtree: ContextMenuCommandData;
|
||||||
|
moveNoteUp: ContextMenuCommandData;
|
||||||
|
moveNoteDown: ContextMenuCommandData;
|
||||||
|
moveNoteUpInHierarchy: ContextMenuCommandData;
|
||||||
|
moveNoteDownInHierarchy: ContextMenuCommandData;
|
||||||
|
selectAllNotesInParent: ContextMenuCommandData;
|
||||||
|
|
||||||
addNoteLauncher: ContextMenuCommandData;
|
addNoteLauncher: ContextMenuCommandData;
|
||||||
addScriptLauncher: ContextMenuCommandData;
|
addScriptLauncher: ContextMenuCommandData;
|
||||||
@ -175,6 +189,7 @@ export type CommandMappings = {
|
|||||||
importMarkdownInline: CommandData;
|
importMarkdownInline: CommandData;
|
||||||
showPasswordNotSet: CommandData;
|
showPasswordNotSet: CommandData;
|
||||||
showProtectedSessionPasswordDialog: CommandData;
|
showProtectedSessionPasswordDialog: CommandData;
|
||||||
|
showUploadAttachmentsDialog: CommandData & { noteId: string };
|
||||||
closeProtectedSessionPasswordDialog: CommandData;
|
closeProtectedSessionPasswordDialog: CommandData;
|
||||||
copyImageReferenceToClipboard: CommandData;
|
copyImageReferenceToClipboard: CommandData;
|
||||||
copyImageToClipboard: CommandData;
|
copyImageToClipboard: CommandData;
|
||||||
@ -198,6 +213,7 @@ export type CommandMappings = {
|
|||||||
screen: Screen;
|
screen: Screen;
|
||||||
};
|
};
|
||||||
closeTab: CommandData;
|
closeTab: CommandData;
|
||||||
|
closeToc: CommandData;
|
||||||
closeOtherTabs: CommandData;
|
closeOtherTabs: CommandData;
|
||||||
closeRightTabs: CommandData;
|
closeRightTabs: CommandData;
|
||||||
closeAllTabs: CommandData;
|
closeAllTabs: CommandData;
|
||||||
@ -216,15 +232,20 @@ export type CommandMappings = {
|
|||||||
scrollContainerToCommand: CommandData & {
|
scrollContainerToCommand: CommandData & {
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
moveThisNoteSplit: CommandData & {
|
scrollToEnd: CommandData;
|
||||||
isMovingLeft: boolean;
|
closeThisNoteSplit: CommandData;
|
||||||
};
|
moveThisNoteSplit: CommandData & { isMovingLeft: boolean; };
|
||||||
|
|
||||||
// Geomap
|
// Geomap
|
||||||
deleteFromMap: { noteId: string },
|
deleteFromMap: { noteId: string },
|
||||||
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
|
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
|
||||||
|
|
||||||
toggleZenMode: CommandData;
|
toggleZenMode: CommandData;
|
||||||
|
|
||||||
|
updateAttributeList: CommandData & { attributes: Attribute[] };
|
||||||
|
saveAttributes: CommandData;
|
||||||
|
reloadAttributes: CommandData;
|
||||||
|
refreshNoteList: CommandData & { noteId: string; };
|
||||||
};
|
};
|
||||||
|
|
||||||
type EventMappings = {
|
type EventMappings = {
|
||||||
@ -329,7 +350,6 @@ type EventMappings = {
|
|||||||
showToc: {
|
showToc: {
|
||||||
noteId: string;
|
noteId: string;
|
||||||
};
|
};
|
||||||
scrollToEnd: { ntxId: string };
|
|
||||||
noteTypeMimeChanged: { noteId: string };
|
noteTypeMimeChanged: { noteId: string };
|
||||||
zenModeChanged: { isEnabled: boolean };
|
zenModeChanged: { isEnabled: boolean };
|
||||||
};
|
};
|
||||||
|
@ -80,8 +80,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
|||||||
return promises.length > 0 ? Promise.all(promises) : null;
|
return promises.length > 0 ? Promise.all(promises) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerCommand<K extends CommandNames>(name: string, _data?: CommandMappings[K]): Promise<unknown> | undefined | null {
|
triggerCommand<K extends CommandNames>(name: K, data?: CommandMappings[K]): Promise<unknown> | undefined | null {
|
||||||
const data = _data || {};
|
|
||||||
const fun = (this as any)[`${name}Command`];
|
const fun = (this as any)[`${name}Command`];
|
||||||
|
|
||||||
if (fun) {
|
if (fun) {
|
||||||
|
@ -11,7 +11,7 @@ import type { ViewScope } from "../services/link.js";
|
|||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||||
|
|
||||||
interface SetNoteOpts {
|
export interface SetNoteOpts {
|
||||||
triggerSwitchEvent?: unknown;
|
triggerSwitchEvent?: unknown;
|
||||||
viewScope?: ViewScope;
|
viewScope?: ViewScope;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ export interface FBranchRow {
|
|||||||
prefix?: string;
|
prefix?: string;
|
||||||
isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
fromSearchNote: boolean;
|
fromSearchNote: boolean;
|
||||||
|
isDeleted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import treeService, { type Node } from "../services/tree.js";
|
import treeService from "../services/tree.js";
|
||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
||||||
import dialogService from "../services/dialog.js";
|
import dialogService from "../services/dialog.js";
|
||||||
@ -12,17 +12,17 @@ type LauncherCommandNames = FilteredCommandNames<ContextMenuCommandData>;
|
|||||||
|
|
||||||
export default class LauncherContextMenu implements SelectMenuItemEventListener<LauncherCommandNames> {
|
export default class LauncherContextMenu implements SelectMenuItemEventListener<LauncherCommandNames> {
|
||||||
private treeWidget: NoteTreeWidget;
|
private treeWidget: NoteTreeWidget;
|
||||||
private node: Node;
|
private node: Fancytree.FancytreeNode;
|
||||||
|
|
||||||
constructor(treeWidget: NoteTreeWidget, node: Node) {
|
constructor(treeWidget: NoteTreeWidget, node: Fancytree.FancytreeNode) {
|
||||||
this.treeWidget = treeWidget;
|
this.treeWidget = treeWidget;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
async show(e: PointerEvent) {
|
async show(e: PointerEvent | JQuery.TouchStartEvent | JQuery.ContextMenuEvent) {
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
x: e.pageX,
|
x: e.pageX ?? 0,
|
||||||
y: e.pageY,
|
y: e.pageY ?? 0,
|
||||||
items: await this.getMenuItems(),
|
items: await this.getMenuItems(),
|
||||||
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
|
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import treeService, { type Node } from "../services/tree.js";
|
import treeService from "../services/tree.js";
|
||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import clipboard from "../services/clipboard.js";
|
import clipboard from "../services/clipboard.js";
|
||||||
import noteCreateService from "../services/note_create.js";
|
import noteCreateService from "../services/note_create.js";
|
||||||
@ -18,21 +18,23 @@ interface ConvertToAttachmentResponse {
|
|||||||
attachment?: FAttachment;
|
attachment?: FAttachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData>;
|
// This will include all commands that implement ContextMenuCommandData, but it will not work if it additional options are added via the `|` operator,
|
||||||
|
// so they need to be added manually.
|
||||||
|
export type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData> | "openBulkActionsDialog";
|
||||||
|
|
||||||
export default class TreeContextMenu implements SelectMenuItemEventListener<TreeCommandNames> {
|
export default class TreeContextMenu implements SelectMenuItemEventListener<TreeCommandNames> {
|
||||||
private treeWidget: NoteTreeWidget;
|
private treeWidget: NoteTreeWidget;
|
||||||
private node: Node;
|
private node: Fancytree.FancytreeNode;
|
||||||
|
|
||||||
constructor(treeWidget: NoteTreeWidget, node: Node) {
|
constructor(treeWidget: NoteTreeWidget, node: Fancytree.FancytreeNode) {
|
||||||
this.treeWidget = treeWidget;
|
this.treeWidget = treeWidget;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
async show(e: PointerEvent) {
|
async show(e: PointerEvent | JQuery.TouchStartEvent | JQuery.ContextMenuEvent) {
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
x: e.pageX,
|
x: e.pageX ?? 0,
|
||||||
y: e.pageY,
|
y: e.pageY ?? 0,
|
||||||
items: await this.getMenuItems(),
|
items: await this.getMenuItems(),
|
||||||
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
|
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,6 @@ import hoistedNoteService from "./hoisted_note.js";
|
|||||||
import ws from "./ws.js";
|
import ws from "./ws.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
import type { Node } from "./tree.js";
|
|
||||||
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
||||||
|
|
||||||
// TODO: Deduplicate type with server
|
// TODO: Deduplicate type with server
|
||||||
@ -160,7 +159,7 @@ async function activateParentNotePath() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moveNodeUpInHierarchy(node: Node) {
|
async function moveNodeUpInHierarchy(node: Fancytree.FancytreeNode) {
|
||||||
if (hoistedNoteService.isHoistedNode(node) || hoistedNoteService.isTopLevelNode(node) || node.getParent().data.noteType === "search") {
|
if (hoistedNoteService.isHoistedNode(node) || hoistedNoteService.isTopLevelNode(node) || node.getParent().data.noteType === "search") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import treeService, { type Node } from "./tree.js";
|
import treeService from "./tree.js";
|
||||||
import dialogService from "./dialog.js";
|
import dialogService from "./dialog.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import type NoteContext from "../components/note_context.js";
|
import type NoteContext from "../components/note_context.js";
|
||||||
@ -19,11 +19,11 @@ async function unhoist() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTopLevelNode(node: Node) {
|
function isTopLevelNode(node: Fancytree.FancytreeNode) {
|
||||||
return isHoistedNode(node.getParent());
|
return isHoistedNode(node.getParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHoistedNode(node: Node) {
|
function isHoistedNode(node: Fancytree.FancytreeNode) {
|
||||||
// even though check for 'root' should not be necessary, we keep it just in case
|
// even though check for 'root' should not be necessary, we keep it just in case
|
||||||
return node.data.noteId === "root" || node.data.noteId === getHoistedNoteId();
|
return node.data.noteId === "root" || node.data.noteId === getHoistedNoteId();
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,16 @@ import utils from "./utils.js";
|
|||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
|
|
||||||
export async function uploadFiles(entityType: string, parentNoteId: string, files: string[], options: Record<string, string | Blob>) {
|
interface UploadFilesOptions {
|
||||||
|
safeImport: boolean;
|
||||||
|
shrinkImages: boolean;
|
||||||
|
textImportedAsText: boolean;
|
||||||
|
codeImportedAsCode: boolean;
|
||||||
|
explodeArchives: boolean;
|
||||||
|
replaceUnderscoresWithSpaces: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadFiles(entityType: string, parentNoteId: string, files: string[], options: UploadFilesOptions) {
|
||||||
if (!["notes", "attachments"].includes(entityType)) {
|
if (!["notes", "attachments"].includes(entityType)) {
|
||||||
throw new Error(`Unrecognized import entity type '${entityType}'.`);
|
throw new Error(`Unrecognized import entity type '${entityType}'.`);
|
||||||
}
|
}
|
||||||
@ -26,7 +35,7 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
|
|||||||
formData.append("last", counter === files.length ? "true" : "false");
|
formData.append("last", counter === files.length ? "true" : "false");
|
||||||
|
|
||||||
for (const key in options) {
|
for (const key in options) {
|
||||||
formData.append(key, options[key]);
|
formData.append(key, (options as any)[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await $.ajax({
|
await $.ajax({
|
||||||
|
@ -8,11 +8,14 @@ interface NoteRow {
|
|||||||
isDeleted?: boolean;
|
isDeleted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BranchRow {
|
// TODO: Deduplicate with BranchRow from `rows.ts`/
|
||||||
|
export interface BranchRow {
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
branchId: string;
|
branchId: string;
|
||||||
componentId: string;
|
componentId: string;
|
||||||
parentNoteId?: string;
|
parentNoteId?: string;
|
||||||
|
isDeleted?: boolean;
|
||||||
|
isExpanded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeRow {
|
export interface AttributeRow {
|
||||||
|
@ -2,12 +2,10 @@ import server from "./server.js";
|
|||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
import type { MenuItem } from "../menus/context_menu.js";
|
import type { MenuItem } from "../menus/context_menu.js";
|
||||||
import type { ContextMenuCommandData, FilteredCommandNames } from "../components/app_context.js";
|
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
|
||||||
|
|
||||||
type NoteTypeCommandNames = FilteredCommandNames<ContextMenuCommandData>;
|
async function getNoteTypeItems(command?: TreeCommandNames) {
|
||||||
|
const items: MenuItem<TreeCommandNames>[] = [
|
||||||
async function getNoteTypeItems(command?: NoteTypeCommandNames) {
|
|
||||||
const items: MenuItem<NoteTypeCommandNames>[] = [
|
|
||||||
{ title: t("note_types.text"), command, type: "text", uiIcon: "bx bx-note" },
|
{ title: t("note_types.text"), command, type: "text", uiIcon: "bx bx-note" },
|
||||||
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
|
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
|
||||||
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },
|
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },
|
||||||
|
@ -4,20 +4,6 @@ import froca from "./froca.js";
|
|||||||
import hoistedNoteService from "../services/hoisted_note.js";
|
import hoistedNoteService from "../services/hoisted_note.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
|
|
||||||
export interface Node {
|
|
||||||
title: string;
|
|
||||||
getParent(): Node;
|
|
||||||
getChildren(): Node[];
|
|
||||||
folder: boolean;
|
|
||||||
renderTitle(): void;
|
|
||||||
data: {
|
|
||||||
noteId?: string;
|
|
||||||
isProtected?: boolean;
|
|
||||||
branchId: string;
|
|
||||||
noteType: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
@ -148,7 +134,7 @@ ws.subscribeToMessages((message) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getParentProtectedStatus(node: Node) {
|
function getParentProtectedStatus(node: Fancytree.FancytreeNode) {
|
||||||
return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected;
|
return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +191,7 @@ function getNoteIdAndParentIdFromUrl(urlOrNotePath: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNotePath(node: Node) {
|
function getNotePath(node: Fancytree.FancytreeNode) {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
logError("Node is null");
|
logError("Node is null");
|
||||||
return "";
|
return "";
|
||||||
|
@ -137,7 +137,7 @@ function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.
|
|||||||
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
|
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertArguments(...args: string[]) {
|
function assertArguments<T>(...args: T[]) {
|
||||||
for (const i in args) {
|
for (const i in args) {
|
||||||
if (!args[i]) {
|
if (!args[i]) {
|
||||||
console.trace(`Argument idx#${i} should not be falsy: ${args[i]}`);
|
console.trace(`Argument idx#${i} should not be falsy: ${args[i]}`);
|
||||||
|
1165
src/public/app/types-fancytree.d.ts
vendored
Normal file
1165
src/public/app/types-fancytree.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
src/public/app/types.d.ts
vendored
2
src/public/app/types.d.ts
vendored
@ -107,7 +107,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var logError: (message: string, e?: Error) => void;
|
var logError: (message: string, e?: Error | string) => void;
|
||||||
var logInfo: (message: string) => void;
|
var logInfo: (message: string) => void;
|
||||||
var glob: CustomGlobals;
|
var glob: CustomGlobals;
|
||||||
var require: RequireMethod;
|
var require: RequireMethod;
|
||||||
|
@ -125,7 +125,7 @@ class NoteContextAwareWidget extends BasicWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async frocaReloadedEvent() {
|
async frocaReloadedEvent(): Promise<void> {
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
|||||||
import server from "../services/server.js";
|
import server from "../services/server.js";
|
||||||
import noteCreateService from "../services/note_create.js";
|
import noteCreateService from "../services/note_create.js";
|
||||||
import toastService from "../services/toast.js";
|
import toastService from "../services/toast.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
|
||||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||||
import clipboard from "../services/clipboard.js";
|
import clipboard from "../services/clipboard.js";
|
||||||
import protectedSessionService from "../services/protected_session.js";
|
import protectedSessionService from "../services/protected_session.js";
|
||||||
@ -19,6 +19,12 @@ import protectedSessionHolder from "../services/protected_session_holder.js";
|
|||||||
import dialogService from "../services/dialog.js";
|
import dialogService from "../services/dialog.js";
|
||||||
import shortcutService from "../services/shortcuts.js";
|
import shortcutService from "../services/shortcuts.js";
|
||||||
import { t } from "../services/i18n.js";
|
import { t } from "../services/i18n.js";
|
||||||
|
import type FBranch from "../entities/fbranch.js";
|
||||||
|
import type LoadResults from "../services/load_results.js";
|
||||||
|
import type FNote from "../entities/fnote.js";
|
||||||
|
import type { NoteType } from "../entities/fnote.js";
|
||||||
|
import type { AttributeRow, BranchRow } from "../services/load_results.js";
|
||||||
|
import type { SetNoteOpts } from "../components/note_context.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="tree-wrapper">
|
<div class="tree-wrapper">
|
||||||
@ -139,9 +145,54 @@ const TPL = `
|
|||||||
const MAX_SEARCH_RESULTS_IN_TREE = 100;
|
const MAX_SEARCH_RESULTS_IN_TREE = 100;
|
||||||
|
|
||||||
// this has to be hanged on the actual elements to effectively intercept and stop click event
|
// this has to be hanged on the actual elements to effectively intercept and stop click event
|
||||||
const cancelClickPropagation = (e) => e.stopPropagation();
|
const cancelClickPropagation: JQuery.TypeEventHandler<unknown, unknown, unknown, unknown, any> = (e) => e.stopPropagation();
|
||||||
|
|
||||||
|
// TODO: Fix once we remove Node.js API from public
|
||||||
|
type Timeout = NodeJS.Timeout | string | number | undefined;
|
||||||
|
|
||||||
|
// TODO: Deduplicate with server special_notes
|
||||||
|
type LauncherType = "launcher" | "note" | "script" | "customWidget" | "spacer";
|
||||||
|
|
||||||
|
// TODO: Deduplicate with the server
|
||||||
|
interface CreateLauncherResponse {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
note: {
|
||||||
|
noteId: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandedSubtreeResponse {
|
||||||
|
branchIds: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Node extends Fancytree.NodeData {
|
||||||
|
noteId: string;
|
||||||
|
parentNoteId: string;
|
||||||
|
branchId: string;
|
||||||
|
isProtected: boolean;
|
||||||
|
noteType: NoteType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RefreshContext {
|
||||||
|
noteIdsToUpdate: Set<string>;
|
||||||
|
noteIdsToReload: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
export default class NoteTreeWidget extends NoteContextAwareWidget {
|
export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||||
|
|
||||||
|
private $tree!: JQuery<HTMLElement>;
|
||||||
|
private $treeActions!: JQuery<HTMLElement>;
|
||||||
|
private $treeSettingsButton!: JQuery<HTMLElement>;
|
||||||
|
private $treeSettingsPopup!: JQuery<HTMLElement>;
|
||||||
|
private $saveTreeSettingsButton!: JQuery<HTMLElement>;
|
||||||
|
private $hideArchivedNotesCheckbox!: JQuery<HTMLElement>;
|
||||||
|
private $autoCollapseNoteTree!: JQuery<HTMLElement>;
|
||||||
|
private treeName: "main";
|
||||||
|
private autoCollapseTimeoutId?: Timeout;
|
||||||
|
private lastFilteredHoistedNotePath?: string | null;
|
||||||
|
private tree!: Fancytree.Fancytree;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -156,7 +207,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.$tree.on("mousedown", ".unhoist-button", () => hoistedNoteService.unhoist());
|
this.$tree.on("mousedown", ".unhoist-button", () => hoistedNoteService.unhoist());
|
||||||
this.$tree.on("mousedown", ".refresh-search-button", (e) => this.refreshSearch(e));
|
this.$tree.on("mousedown", ".refresh-search-button", (e) => this.refreshSearch(e));
|
||||||
this.$tree.on("mousedown", ".add-note-button", (e) => {
|
this.$tree.on("mousedown", ".add-note-button", (e) => {
|
||||||
const node = $.ui.fancytree.getNode(e);
|
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
const parentNotePath = treeService.getNotePath(node);
|
const parentNotePath = treeService.getNotePath(node);
|
||||||
|
|
||||||
noteCreateService.createNote(parentNotePath, {
|
noteCreateService.createNote(parentNotePath, {
|
||||||
@ -165,7 +216,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$tree.on("mousedown", ".enter-workspace-button", (e) => {
|
this.$tree.on("mousedown", ".enter-workspace-button", (e) => {
|
||||||
const node = $.ui.fancytree.getNode(e);
|
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
|
|
||||||
this.triggerCommand("hoistNote", { noteId: node.data.noteId });
|
this.triggerCommand("hoistNote", { noteId: node.data.noteId });
|
||||||
});
|
});
|
||||||
@ -173,7 +224,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
// fancytree doesn't support middle click, so this is a way to support it
|
// fancytree doesn't support middle click, so this is a way to support it
|
||||||
this.$tree.on("mousedown", ".fancytree-title", (e) => {
|
this.$tree.on("mousedown", ".fancytree-title", (e) => {
|
||||||
if (e.which === 2) {
|
if (e.which === 2) {
|
||||||
const node = $.ui.fancytree.getNode(e);
|
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
|
|
||||||
const notePath = treeService.getNotePath(node);
|
const notePath = treeService.getNotePath(node);
|
||||||
|
|
||||||
@ -200,8 +251,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
|
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
|
||||||
this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
|
this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
|
||||||
|
|
||||||
const top = this.$treeActions[0].offsetTop - this.$treeSettingsPopup.outerHeight();
|
const top = this.$treeActions[0].offsetTop - (this.$treeSettingsPopup.outerHeight() ?? 0);
|
||||||
const left = Math.max(0, this.$treeActions[0].offsetLeft - this.$treeSettingsPopup.outerWidth() + this.$treeActions.outerWidth());
|
const left = Math.max(0, this.$treeActions[0].offsetLeft - (this.$treeSettingsPopup.outerWidth() ?? 0) + (this.$treeActions.outerWidth() ?? 0));
|
||||||
|
|
||||||
this.$treeSettingsPopup
|
this.$treeSettingsPopup
|
||||||
.css({
|
.css({
|
||||||
@ -241,16 +292,19 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
// see https://github.com/zadam/trilium/pull/1120 for discussion
|
// see https://github.com/zadam/trilium/pull/1120 for discussion
|
||||||
|
|
||||||
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
|
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
|
||||||
const isEnclosing = ($container, $sub) => {
|
const isEnclosing = ($container: JQuery<HTMLElement>, $sub: JQuery<HTMLElement>) => {
|
||||||
const conOffset = $container.offset();
|
const conOffset = $container.offset();
|
||||||
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
|
const conDistanceFromTop = (conOffset?.top ?? 0) + ($container.outerHeight(true) ?? 0);
|
||||||
const conDistanceFromLeft = conOffset.left + $container.outerWidth(true);
|
const conDistanceFromLeft = (conOffset?.left ?? 0) + ($container.outerWidth(true) ?? 0);
|
||||||
|
|
||||||
const subOffset = $sub.offset();
|
const subOffset = $sub.offset();
|
||||||
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
|
const subDistanceFromTop = (subOffset?.top ?? 0) + ($sub.outerHeight(true) ?? 0);
|
||||||
const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true);
|
const subDistanceFromLeft = (subOffset?.left ?? 0) + ($sub.outerWidth(true) ?? 0);
|
||||||
|
|
||||||
return conDistanceFromTop > subDistanceFromTop && conOffset.top < subOffset.top && conDistanceFromLeft > subDistanceFromLeft && conOffset.left < subOffset.left;
|
return conDistanceFromTop > subDistanceFromTop
|
||||||
|
&& (conOffset?.top ?? 0) < (subOffset?.top ?? 0)
|
||||||
|
&& conDistanceFromLeft > subDistanceFromLeft
|
||||||
|
&& (conOffset?.left ?? 0) < (subOffset?.left ?? 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$tree.on("mouseenter", "span.fancytree-title", (e) => {
|
this.$tree.on("mouseenter", "span.fancytree-title", (e) => {
|
||||||
@ -262,7 +316,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return options.is(`hideArchivedNotes_${this.treeName}`);
|
return options.is(`hideArchivedNotes_${this.treeName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setHideArchivedNotes(val) {
|
async setHideArchivedNotes(val: string) {
|
||||||
await options.save(`hideArchivedNotes_${this.treeName}`, val.toString());
|
await options.save(`hideArchivedNotes_${this.treeName}`, val.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +324,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return options.is("autoCollapseNoteTree");
|
return options.is("autoCollapseNoteTree");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAutoCollapseNoteTree(val) {
|
async setAutoCollapseNoteTree(val: string) {
|
||||||
await options.save("autoCollapseNoteTree", val.toString());
|
await options.save("autoCollapseNoteTree", val.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +342,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
},
|
},
|
||||||
scrollParent: this.$tree,
|
scrollParent: this.$tree,
|
||||||
minExpandLevel: 2, // root can't be collapsed
|
minExpandLevel: 2, // root can't be collapsed
|
||||||
click: (event, data) => {
|
click: (event, data): boolean => {
|
||||||
this.activityDetected();
|
this.activityDetected();
|
||||||
|
|
||||||
const targetType = data.targetType;
|
const targetType = data.targetType;
|
||||||
@ -305,12 +359,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
const activeNode = this.getActiveNode();
|
const activeNode = this.getActiveNode();
|
||||||
|
|
||||||
if (activeNode.getParent() !== node.getParent()) {
|
if (activeNode.getParent() !== node.getParent()) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clearSelectedNodes();
|
this.clearSelectedNodes();
|
||||||
|
|
||||||
function selectInBetween(first, second) {
|
function selectInBetween(first: Fancytree.FancytreeNode, second: Fancytree.FancytreeNode) {
|
||||||
for (let i = 0; first && first !== second && i < 10000; i++) {
|
for (let i = 0; first && first !== second && i < 10000; i++) {
|
||||||
first.setSelected(true);
|
first.setSelected(true);
|
||||||
first = first.getNextSibling();
|
first = first.getNextSibling();
|
||||||
@ -334,13 +388,15 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
node.setFocus(true);
|
node.setFocus(true);
|
||||||
} else if (data.node.isActive()) {
|
} else if (data.node.isActive()) {
|
||||||
// this is important for single column mobile view, otherwise it's not possible to see again previously displayed note
|
// this is important for single column mobile view, otherwise it's not possible to see again previously displayed note
|
||||||
this.tree.reactivate(true);
|
this.tree.reactivate();
|
||||||
} else {
|
} else {
|
||||||
node.setActive();
|
node.setActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
beforeActivate: (event, { node }) => {
|
beforeActivate: (event, { node }) => {
|
||||||
// hidden subtree is hidden hackily - we want it to be present in the tree so that we can switch to it
|
// hidden subtree is hidden hackily - we want it to be present in the tree so that we can switch to it
|
||||||
@ -368,8 +424,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
const notePath = treeService.getNotePath(data.node);
|
const notePath = treeService.getNotePath(data.node);
|
||||||
|
|
||||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||||
const opts = {};
|
const opts: SetNoteOpts = {};
|
||||||
if (activeNoteContext.viewScope.viewMode === "contextual-help") {
|
if (activeNoteContext.viewScope?.viewMode === "contextual-help") {
|
||||||
opts.viewScope = activeNoteContext.viewScope;
|
opts.viewScope = activeNoteContext.viewScope;
|
||||||
}
|
}
|
||||||
await activeNoteContext.setNote(notePath, opts);
|
await activeNoteContext.setNote(notePath, opts);
|
||||||
@ -450,7 +506,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const jsonStr = dataTransfer.getData("text");
|
const jsonStr = dataTransfer.getData("text");
|
||||||
let notes = null;
|
let notes: BranchRow[];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
notes = JSON.parse(jsonStr);
|
notes = JSON.parse(jsonStr);
|
||||||
@ -462,7 +518,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
// This function MUST be defined to enable dropping of items on the tree.
|
// This function MUST be defined to enable dropping of items on the tree.
|
||||||
// data.hitMode is 'before', 'after', or 'over'.
|
// data.hitMode is 'before', 'after', or 'over'.
|
||||||
|
|
||||||
const selectedBranchIds = notes.map((note) => note.branchId);
|
const selectedBranchIds = notes
|
||||||
|
.map((note) => note.branchId)
|
||||||
|
.filter((branchId) => branchId) as string[];
|
||||||
|
|
||||||
if (data.hitMode === "before") {
|
if (data.hitMode === "before") {
|
||||||
branchService.moveBeforeBranch(selectedBranchIds, node.data.branchId);
|
branchService.moveBeforeBranch(selectedBranchIds, node.data.branchId);
|
||||||
@ -513,7 +571,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
clones: {
|
clones: {
|
||||||
highlightActiveClones: true
|
highlightActiveClones: true
|
||||||
},
|
},
|
||||||
enhanceTitle: async function (event, data) {
|
enhanceTitle: async function (event: Event, data: {
|
||||||
|
node: Fancytree.FancytreeNode;
|
||||||
|
noteId: string;
|
||||||
|
}) {
|
||||||
const node = data.node;
|
const node = data.node;
|
||||||
|
|
||||||
if (!node.data.noteId) {
|
if (!node.data.noteId) {
|
||||||
@ -602,7 +663,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
const isMobile = utils.isMobile();
|
const isMobile = utils.isMobile();
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
let showTimeout;
|
let showTimeout: Timeout;
|
||||||
|
|
||||||
this.$tree.on("touchstart", ".fancytree-node", (e) => {
|
this.$tree.on("touchstart", ".fancytree-node", (e) => {
|
||||||
touchStart = new Date().getTime();
|
touchStart = new Date().getTime();
|
||||||
@ -642,8 +703,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.tree = $.ui.fancytree.getTree(this.$tree);
|
this.tree = $.ui.fancytree.getTree(this.$tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
showContextMenu(e) {
|
showContextMenu(e: PointerEvent | JQuery.TouchStartEvent | JQuery.ContextMenuEvent) {
|
||||||
const node = $.ui.fancytree.getNode(e);
|
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
const note = froca.getNoteFromCache(node.data.noteId);
|
const note = froca.getNoteFromCache(node.data.noteId);
|
||||||
|
|
||||||
if (note.isLaunchBarConfig()) {
|
if (note.isLaunchBarConfig()) {
|
||||||
@ -660,13 +721,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareRootNode() {
|
prepareRootNode() {
|
||||||
return this.prepareNode(froca.getBranch("none_root"));
|
const branch = froca.getBranch("none_root");
|
||||||
|
return branch && this.prepareNode(branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
prepareChildren(parentNote: FNote) {
|
||||||
* @param {FNote} parentNote
|
|
||||||
*/
|
|
||||||
prepareChildren(parentNote) {
|
|
||||||
utils.assertArguments(parentNote);
|
utils.assertArguments(parentNote);
|
||||||
|
|
||||||
const noteList = [];
|
const noteList = [];
|
||||||
@ -697,7 +756,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return noteList;
|
return noteList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateNode(node) {
|
async updateNode(node: Fancytree.FancytreeNode) {
|
||||||
const note = froca.getNoteFromCache(node.data.noteId);
|
const note = froca.getNoteFromCache(node.data.noteId);
|
||||||
const branch = froca.getBranch(node.data.branchId);
|
const branch = froca.getBranch(node.data.branchId);
|
||||||
|
|
||||||
@ -725,11 +784,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
node.renderTitle();
|
node.renderTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
prepareNode(branch: FBranch, forceLazy = false) {
|
||||||
* @param {FBranch} branch
|
|
||||||
* @param {boolean} forceLazy
|
|
||||||
*/
|
|
||||||
prepareNode(branch, forceLazy = false) {
|
|
||||||
const note = branch.getNoteFromCache();
|
const note = branch.getNoteFromCache();
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
@ -741,7 +796,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
const isFolder = note.isFolder();
|
const isFolder = note.isFolder();
|
||||||
|
|
||||||
const node = {
|
const node: Node = {
|
||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
parentNoteId: branch.parentNoteId,
|
parentNoteId: branch.parentNoteId,
|
||||||
branchId: branch.branchId,
|
branchId: branch.branchId,
|
||||||
@ -749,7 +804,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
noteType: note.type,
|
noteType: note.type,
|
||||||
title: utils.escapeHtml(title),
|
title: utils.escapeHtml(title),
|
||||||
extraClasses: this.getExtraClasses(note),
|
extraClasses: this.getExtraClasses(note),
|
||||||
icon: note.getIcon(isFolder),
|
icon: note.getIcon(),
|
||||||
refKey: note.noteId,
|
refKey: note.noteId,
|
||||||
lazy: true,
|
lazy: true,
|
||||||
folder: isFolder,
|
folder: isFolder,
|
||||||
@ -764,7 +819,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtraClasses(note) {
|
getExtraClasses(note: FNote) {
|
||||||
utils.assertArguments(note);
|
utils.assertArguments(note);
|
||||||
|
|
||||||
const extraClasses = [];
|
const extraClasses = [];
|
||||||
@ -780,9 +835,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
if (note.getParentNoteIds().length > 1) {
|
if (note.getParentNoteIds().length > 1) {
|
||||||
const realClones = note
|
const realClones = note
|
||||||
.getParentNoteIds()
|
.getParentNoteIds()
|
||||||
.map((noteId) => froca.notes[noteId])
|
.map((noteId: string) => froca.notes[noteId])
|
||||||
.filter((note) => !!note)
|
.filter((note: FNote) => !!note)
|
||||||
.filter((note) => !["_share", "_lbBookmarks"].includes(note.noteId) && note.type !== "search");
|
.filter((note: FNote) => !["_share", "_lbBookmarks"].includes(note.noteId) && note.type !== "search");
|
||||||
|
|
||||||
if (realClones.length > 1) {
|
if (realClones.length > 1) {
|
||||||
extraClasses.push("multiple-parents");
|
extraClasses.push("multiple-parents");
|
||||||
@ -820,8 +875,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return this.tree.getSelectedNodes(stopOnParents);
|
return this.tree.getSelectedNodes(stopOnParents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FancytreeNode[]} */
|
getSelectedOrActiveNodes(node: Fancytree.FancytreeNode | null = null) {
|
||||||
getSelectedOrActiveNodes(node = null) {
|
|
||||||
const nodes = this.getSelectedNodes(true);
|
const nodes = this.getSelectedNodes(true);
|
||||||
|
|
||||||
// the node you start dragging should be included even if not selected
|
// the node you start dragging should be included even if not selected
|
||||||
@ -838,14 +892,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return nodes.filter((node) => hoistedNoteService.getHoistedNoteId() !== "root" || node.data.noteId !== "_hidden");
|
return nodes.filter((node) => hoistedNoteService.getHoistedNoteId() !== "root" || node.data.noteId !== "_hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExpandedStatusForSubtree(node, isExpanded) {
|
async setExpandedStatusForSubtree(node: Fancytree.FancytreeNode | null, isExpanded: boolean) {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
node = this.getNodesByNoteId(hoistedNoteId)[0];
|
node = this.getNodesByNoteId(hoistedNoteId)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { branchIds } = await server.put(`branches/${node.data.branchId}/expanded-subtree/${isExpanded ? 1 : 0}`);
|
const { branchIds } = await server.put<ExpandedSubtreeResponse>(`branches/${node.data.branchId}/expanded-subtree/${isExpanded ? 1 : 0}`);
|
||||||
|
|
||||||
froca.getBranches(branchIds, true).forEach((branch) => (branch.isExpanded = !!isExpanded));
|
froca.getBranches(branchIds, true).forEach((branch) => (branch.isExpanded = !!isExpanded));
|
||||||
|
|
||||||
@ -863,11 +917,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
// don't activate the active note, see discussion in https://github.com/zadam/trilium/issues/3664
|
// don't activate the active note, see discussion in https://github.com/zadam/trilium/issues/3664
|
||||||
}
|
}
|
||||||
|
|
||||||
async expandTree(node = null) {
|
async expandTree(node: Fancytree.FancytreeNode | null = null) {
|
||||||
await this.setExpandedStatusForSubtree(node, true);
|
await this.setExpandedStatusForSubtree(node, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async collapseTree(node = null) {
|
async collapseTree(node: Fancytree.FancytreeNode | null = null) {
|
||||||
await this.setExpandedStatusForSubtree(node, false);
|
await this.setExpandedStatusForSubtree(node, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,8 +972,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.tree.setFocus(true);
|
this.tree.setFocus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FancytreeNode} */
|
async getNodeFromPath(notePath: string, expand = false, logErrors = true) {
|
||||||
async getNodeFromPath(notePath, expand = false, logErrors = true) {
|
|
||||||
utils.assertArguments(notePath);
|
utils.assertArguments(notePath);
|
||||||
/** @let {FancytreeNode} */
|
/** @let {FancytreeNode} */
|
||||||
let parentNode = this.getNodesByNoteId("root")[0];
|
let parentNode = this.getNodesByNoteId("root")[0];
|
||||||
@ -951,7 +1004,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
// although the previous line should set the expanded status, it seems to happen asynchronously,
|
// although the previous line should set the expanded status, it seems to happen asynchronously,
|
||||||
// so we need to make sure it is set properly before calling updateNode which uses this flag
|
// so we need to make sure it is set properly before calling updateNode which uses this flag
|
||||||
const branch = froca.getBranch(parentNode.data.branchId);
|
const branch = froca.getBranch(parentNode.data.branchId);
|
||||||
branch.isExpanded = true;
|
if (branch) {
|
||||||
|
branch.isExpanded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateNode(parentNode);
|
await this.updateNode(parentNode);
|
||||||
@ -990,25 +1045,25 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return parentNode;
|
return parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FancytreeNode} */
|
findChildNode(parentNode: Fancytree.FancytreeNode, childNoteId: string) {
|
||||||
findChildNode(parentNode, childNoteId) {
|
|
||||||
return parentNode.getChildren().find((childNode) => childNode.data.noteId === childNoteId);
|
return parentNode.getChildren().find((childNode) => childNode.data.noteId === childNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FancytreeNode} */
|
async expandToNote(notePath: string, logErrors = true) {
|
||||||
async expandToNote(notePath, logErrors = true) {
|
|
||||||
return this.getNodeFromPath(notePath, true, logErrors);
|
return this.getNodeFromPath(notePath, true, logErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FancytreeNode[]} */
|
getNodesByBranch(branch: BranchRow) {
|
||||||
getNodesByBranch(branch) {
|
|
||||||
utils.assertArguments(branch);
|
utils.assertArguments(branch);
|
||||||
|
|
||||||
|
if (!branch.noteId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return this.getNodesByNoteId(branch.noteId).filter((node) => node.data.branchId === branch.branchId);
|
return this.getNodesByNoteId(branch.noteId).filter((node) => node.data.branchId === branch.branchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FancytreeNode[]} */
|
getNodesByNoteId(noteId: string) {
|
||||||
getNodesByNoteId(noteId) {
|
|
||||||
utils.assertArguments(noteId);
|
utils.assertArguments(noteId);
|
||||||
|
|
||||||
const list = this.tree.getNodesByRef(noteId);
|
const list = this.tree.getNodesByRef(noteId);
|
||||||
@ -1043,7 +1098,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newActiveNode) {
|
if (newActiveNode) {
|
||||||
if (!newActiveNode.isVisible()) {
|
if (!newActiveNode.isVisible() && this.noteContext?.notePath) {
|
||||||
await this.expandToNote(this.noteContext.notePath);
|
await this.expandToNote(this.noteContext.notePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1055,8 +1110,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.filterHoistedBranch(false);
|
this.filterHoistedBranch(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshSearch(e) {
|
async refreshSearch(e: JQuery.MouseDownEvent) {
|
||||||
const activeNode = $.ui.fancytree.getNode(e);
|
const activeNode = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
|
|
||||||
activeNode.load(true);
|
activeNode.load(true);
|
||||||
activeNode.setExpanded(true, { noAnimation: true });
|
activeNode.setExpanded(true, { noAnimation: true });
|
||||||
@ -1064,7 +1119,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
toastService.showMessage(t("note_tree.saved-search-note-refreshed"));
|
toastService.showMessage(t("note_tree.saved-search-note-refreshed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async batchUpdate(cb) {
|
async batchUpdate(cb: () => Promise<void>) {
|
||||||
try {
|
try {
|
||||||
// disable rendering during update for increased performance
|
// disable rendering during update for increased performance
|
||||||
this.tree.enableUpdate(false);
|
this.tree.enableUpdate(false);
|
||||||
@ -1115,7 +1170,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}, 600 * 1000);
|
}, 600 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({ loadResults }) {
|
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
this.activityDetected();
|
this.activityDetected();
|
||||||
|
|
||||||
if (loadResults.isEmptyForTree()) {
|
if (loadResults.isEmptyForTree()) {
|
||||||
@ -1126,14 +1181,15 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
const activeNodeFocused = activeNode?.hasFocus();
|
const activeNodeFocused = activeNode?.hasFocus();
|
||||||
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||||
|
|
||||||
const refreshCtx = {
|
const refreshCtx: RefreshContext = {
|
||||||
noteIdsToUpdate: new Set(),
|
noteIdsToUpdate: new Set(),
|
||||||
noteIdsToReload: new Set()
|
noteIdsToReload: new Set()
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#processAttributeRows(loadResults.getAttributeRows(), refreshCtx);
|
this.#processAttributeRows(loadResults.getAttributeRows(), refreshCtx);
|
||||||
|
|
||||||
const { movedActiveNode, parentsOfAddedNodes } = await this.#processBranchRows(loadResults.getBranchRows(), refreshCtx);
|
const branchRows = loadResults.getBranchRows();
|
||||||
|
const { movedActiveNode, parentsOfAddedNodes } = await this.#processBranchRows(branchRows, refreshCtx);
|
||||||
|
|
||||||
for (const noteId of loadResults.getNoteIds()) {
|
for (const noteId of loadResults.getNoteIds()) {
|
||||||
refreshCtx.noteIdsToUpdate.add(noteId);
|
refreshCtx.noteIdsToUpdate.add(noteId);
|
||||||
@ -1149,17 +1205,17 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#processAttributeRows(attributeRows, refreshCtx) {
|
#processAttributeRows(attributeRows: AttributeRow[], refreshCtx: RefreshContext) {
|
||||||
for (const attrRow of attributeRows) {
|
for (const attrRow of attributeRows) {
|
||||||
const dirtyingLabels = ["iconClass", "cssClass", "workspace", "workspaceIconClass", "color"];
|
const dirtyingLabels = ["iconClass", "cssClass", "workspace", "workspaceIconClass", "color"];
|
||||||
|
|
||||||
if (attrRow.type === "label" && dirtyingLabels.includes(attrRow.name)) {
|
if (attrRow.type === "label" && dirtyingLabels.includes(attrRow.name ?? "") && attrRow.noteId) {
|
||||||
if (attrRow.isInheritable) {
|
if (attrRow.isInheritable) {
|
||||||
refreshCtx.noteIdsToReload.add(attrRow.noteId);
|
refreshCtx.noteIdsToReload.add(attrRow.noteId);
|
||||||
} else {
|
} else {
|
||||||
refreshCtx.noteIdsToUpdate.add(attrRow.noteId);
|
refreshCtx.noteIdsToUpdate.add(attrRow.noteId);
|
||||||
}
|
}
|
||||||
} else if (attrRow.type === "label" && attrRow.name === "archived") {
|
} else if (attrRow.type === "label" && attrRow.name === "archived" && attrRow.noteId) {
|
||||||
const note = froca.getNoteFromCache(attrRow.noteId);
|
const note = froca.getNoteFromCache(attrRow.noteId);
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
@ -1169,13 +1225,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
refreshCtx.noteIdsToReload.add(parentNote.noteId);
|
refreshCtx.noteIdsToReload.add(parentNote.noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (attrRow.type === "relation" && (attrRow.name === "template" || attrRow.name === "inherit")) {
|
} else if (attrRow.type === "relation" && (attrRow.name === "template" || attrRow.name === "inherit") && attrRow.noteId) {
|
||||||
// missing handling of things inherited from template
|
// missing handling of things inherited from template
|
||||||
refreshCtx.noteIdsToReload.add(attrRow.noteId);
|
refreshCtx.noteIdsToReload.add(attrRow.noteId);
|
||||||
} else if (attrRow.type === "relation" && attrRow.name === "imageLink") {
|
} else if (attrRow.type === "relation" && attrRow.name === "imageLink" && attrRow.noteId) {
|
||||||
const note = froca.getNoteFromCache(attrRow.noteId);
|
const note = froca.getNoteFromCache(attrRow.noteId);
|
||||||
|
|
||||||
if (note && note.getChildNoteIds().includes(attrRow.value)) {
|
if (note && note.getChildNoteIds().includes(attrRow.value ?? "")) {
|
||||||
// there's a new /deleted imageLink between note and its image child - which can show/hide
|
// there's a new /deleted imageLink between note and its image child - which can show/hide
|
||||||
// the image (if there is an imageLink relation between parent and child,
|
// the image (if there is an imageLink relation between parent and child,
|
||||||
// then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
|
// then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
|
||||||
@ -1185,7 +1241,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #processBranchRows(branchRows, refreshCtx) {
|
async #processBranchRows(branchRows: BranchRow[], refreshCtx: RefreshContext) {
|
||||||
const allBranchesDeleted = branchRows.every((branchRow) => !!branchRow.isDeleted);
|
const allBranchesDeleted = branchRows.every((branchRow) => !!branchRow.isDeleted);
|
||||||
|
|
||||||
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
|
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
|
||||||
@ -1193,12 +1249,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
let parentsOfAddedNodes = [];
|
let parentsOfAddedNodes = [];
|
||||||
|
|
||||||
for (const branchRow of branchRows) {
|
for (const branchRow of branchRows) {
|
||||||
if (branchRow.parentNoteId === "_share") {
|
if (branchRow.noteId) {
|
||||||
// all shared notes have a sign in the tree, even the descendants of shared notes
|
if (branchRow.parentNoteId === "_share") {
|
||||||
refreshCtx.noteIdsToReload.add(branchRow.noteId);
|
// all shared notes have a sign in the tree, even the descendants of shared notes
|
||||||
} else {
|
refreshCtx.noteIdsToReload.add(branchRow.noteId);
|
||||||
// adding noteId itself to update all potential clones
|
} else {
|
||||||
refreshCtx.noteIdsToUpdate.add(branchRow.noteId);
|
// adding noteId itself to update all potential clones
|
||||||
|
refreshCtx.noteIdsToUpdate.add(branchRow.noteId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (branchRow.isDeleted) {
|
if (branchRow.isDeleted) {
|
||||||
@ -1219,37 +1277,44 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
node.remove();
|
node.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
|
if (branchRow.parentNoteId) {
|
||||||
|
refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (branchRow.parentNoteId) {
|
||||||
for (const parentNode of this.getNodesByNoteId(branchRow.parentNoteId)) {
|
for (const parentNode of this.getNodesByNoteId(branchRow.parentNoteId)) {
|
||||||
parentsOfAddedNodes.push(parentNode);
|
parentsOfAddedNodes.push(parentNode);
|
||||||
|
|
||||||
if (parentNode.isFolder() && !parentNode.isLoaded()) {
|
if (!branchRow.noteId || (parentNode.isFolder() && !parentNode.isLoaded())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = await froca.getNote(branchRow.noteId);
|
const note = await froca.getNote(branchRow.noteId);
|
||||||
const frocaBranch = froca.getBranch(branchRow.branchId);
|
const frocaBranch = branchRow.branchId ? froca.getBranch(branchRow.branchId) : null;
|
||||||
const foundNode = (parentNode.getChildren() || []).find((child) => child.data.noteId === branchRow.noteId);
|
const foundNode = (parentNode.getChildren() || []).find((child) => child.data.noteId === branchRow.noteId);
|
||||||
if (foundNode) {
|
if (foundNode) {
|
||||||
// the branch already exists in the tree
|
// the branch already exists in the tree
|
||||||
if (branchRow.isExpanded !== foundNode.isExpanded()) {
|
if (branchRow.isExpanded !== foundNode.isExpanded() && frocaBranch) {
|
||||||
refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
|
refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (frocaBranch) {
|
||||||
// make sure it's loaded
|
// make sure it's loaded
|
||||||
// we're forcing lazy since it's not clear if the whole required subtree is in froca
|
// we're forcing lazy since it's not clear if the whole required subtree is in froca
|
||||||
parentNode.addChildren([this.prepareNode(frocaBranch, true)]);
|
const newNode = this.prepareNode(frocaBranch, true);
|
||||||
|
if (newNode) {
|
||||||
|
parentNode.addChildren([newNode]);
|
||||||
|
}
|
||||||
|
|
||||||
if (frocaBranch.isExpanded && note.hasChildren()) {
|
if (frocaBranch?.isExpanded && note && note.hasChildren()) {
|
||||||
refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
|
refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortChildren(parentNode);
|
this.sortChildren(parentNode);
|
||||||
|
|
||||||
// this might be a first child which would force an icon change
|
// this might be a first child which would force an icon change
|
||||||
refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
|
if (branchRow.parentNoteId) {
|
||||||
|
refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1261,7 +1326,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async #executeTreeUpdates(refreshCtx, loadResults) {
|
async #executeTreeUpdates(refreshCtx: RefreshContext, loadResults: LoadResults) {
|
||||||
await this.batchUpdate(async () => {
|
await this.batchUpdate(async () => {
|
||||||
for (const noteId of refreshCtx.noteIdsToReload) {
|
for (const noteId of refreshCtx.noteIdsToReload) {
|
||||||
for (const node of this.getNodesByNoteId(noteId)) {
|
for (const node of this.getNodesByNoteId(noteId)) {
|
||||||
@ -1288,7 +1353,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #setActiveNode(activeNotePath, activeNodeFocused, movedActiveNode, parentsOfAddedNodes) {
|
async #setActiveNode(activeNotePath: string | null, activeNodeFocused: boolean, movedActiveNode: Fancytree.FancytreeNode | null, parentsOfAddedNodes: Fancytree.FancytreeNode[]) {
|
||||||
if (movedActiveNode) {
|
if (movedActiveNode) {
|
||||||
for (const parentNode of parentsOfAddedNodes) {
|
for (const parentNode of parentsOfAddedNodes) {
|
||||||
const foundNode = (parentNode.getChildren() || []).find((child) => child.data.noteId === movedActiveNode.data.noteId);
|
const foundNode = (parentNode.getChildren() || []).find((child) => child.data.noteId === movedActiveNode.data.noteId);
|
||||||
@ -1303,15 +1368,19 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = await this.expandToNote(activeNotePath, false);
|
let node: Fancytree.FancytreeNode | null | undefined = await this.expandToNote(activeNotePath, false);
|
||||||
|
|
||||||
if (node && node.data.noteId !== treeService.getNoteIdFromUrl(activeNotePath)) {
|
if (node && node.data.noteId !== treeService.getNoteIdFromUrl(activeNotePath)) {
|
||||||
// if the active note has been moved elsewhere then it won't be found by the path,
|
// if the active note has been moved elsewhere then it won't be found by the path,
|
||||||
// so we switch to the alternative of trying to find it by noteId
|
// so we switch to the alternative of trying to find it by noteId
|
||||||
const notesById = this.getNodesByNoteId(treeService.getNoteIdFromUrl(activeNotePath));
|
const noteId = treeService.getNoteIdFromUrl(activeNotePath);
|
||||||
|
|
||||||
// if there are multiple clones, then we'd rather not activate anyone
|
if (noteId) {
|
||||||
node = notesById.length === 1 ? notesById[0] : null;
|
const notesById = this.getNodesByNoteId(noteId);
|
||||||
|
|
||||||
|
// if there are multiple clones, then we'd rather not activate anyone
|
||||||
|
node = notesById.length === 1 ? notesById[0] : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@ -1326,7 +1395,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
await node.setActive(true, { noEvents: true, noFocus: !activeNodeFocused });
|
await node.setActive(true, { noEvents: true, noFocus: !activeNodeFocused });
|
||||||
}
|
}
|
||||||
|
|
||||||
sortChildren(node) {
|
sortChildren(node: Fancytree.FancytreeNode) {
|
||||||
node.sortChildren((nodeA, nodeB) => {
|
node.sortChildren((nodeA, nodeB) => {
|
||||||
const branchA = froca.branches[nodeA.data.branchId];
|
const branchA = froca.branches[nodeA.data.branchId];
|
||||||
const branchB = froca.branches[nodeB.data.branchId];
|
const branchB = froca.branches[nodeB.data.branchId];
|
||||||
@ -1339,7 +1408,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setExpanded(branchId, isExpanded) {
|
setExpanded(branchId: string, isExpanded: boolean) {
|
||||||
utils.assertArguments(branchId);
|
utils.assertArguments(branchId);
|
||||||
|
|
||||||
const branch = froca.getBranch(branchId, true);
|
const branch = froca.getBranch(branchId, true);
|
||||||
@ -1381,7 +1450,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async hoistedNoteChangedEvent({ ntxId }) {
|
async hoistedNoteChangedEvent({ ntxId }: EventData<"hoistedNoteChanged">) {
|
||||||
if (this.isNoteContext(ntxId)) {
|
if (this.isNoteContext(ntxId)) {
|
||||||
await this.filterHoistedBranch(true);
|
await this.filterHoistedBranch(true);
|
||||||
}
|
}
|
||||||
@ -1402,7 +1471,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
this.lastFilteredHoistedNotePath = hoistedNotePath;
|
this.lastFilteredHoistedNotePath = hoistedNotePath;
|
||||||
|
|
||||||
await this.getNodeFromPath(hoistedNotePath);
|
if (hoistedNotePath) {
|
||||||
|
await this.getNodeFromPath(hoistedNotePath);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.noteContext.hoistedNoteId === "root") {
|
if (this.noteContext.hoistedNoteId === "root") {
|
||||||
this.tree.clearFilter();
|
this.tree.clearFilter();
|
||||||
@ -1411,7 +1482,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
// hack when hoisted note is cloned then it could be filtered multiple times while we want only 1
|
// hack when hoisted note is cloned then it could be filtered multiple times while we want only 1
|
||||||
this.tree.filterBranches(
|
this.tree.filterBranches(
|
||||||
(node) =>
|
(node) =>
|
||||||
node.data.noteId === this.noteContext.hoistedNoteId && // optimization to not having always resolve the node path
|
node.data.noteId === this.noteContext?.hoistedNoteId && // optimization to not having always resolve the node path
|
||||||
treeService.getNotePath(node) === hoistedNotePath
|
treeService.getNotePath(node) === hoistedNotePath
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1419,18 +1490,19 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleHiddenNode(show) {
|
toggleHiddenNode(show: boolean) {
|
||||||
const hiddenNode = this.getNodesByNoteId("_hidden")[0];
|
const hiddenNode = this.getNodesByNoteId("_hidden")[0];
|
||||||
$(hiddenNode.li).toggleClass("hidden-node-is-hidden", !show);
|
// TODO: Check how .li exists here.
|
||||||
|
$((hiddenNode as any).li).toggleClass("hidden-node-is-hidden", !show);
|
||||||
}
|
}
|
||||||
|
|
||||||
frocaReloadedEvent() {
|
async frocaReloadedEvent() {
|
||||||
this.reloadTreeFromCache();
|
this.reloadTreeFromCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getHotKeys() {
|
async getHotKeys() {
|
||||||
const actions = await keyboardActionsService.getActionsForScope("note-tree");
|
const actions = await keyboardActionsService.getActionsForScope("note-tree");
|
||||||
const hotKeyMap = {};
|
const hotKeyMap: Record<string, (node: Fancytree.FancytreeNode, e: JQuery.KeyDownEvent) => boolean> = {};
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
for (const shortcut of action.effectiveShortcuts) {
|
for (const shortcut of action.effectiveShortcuts) {
|
||||||
@ -1447,25 +1519,19 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return hotKeyMap;
|
return hotKeyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getSelectedOrActiveBranchIds(node: Fancytree.FancytreeNode) {
|
||||||
* @param {FancytreeNode} node
|
|
||||||
*/
|
|
||||||
getSelectedOrActiveBranchIds(node) {
|
|
||||||
const nodes = this.getSelectedOrActiveNodes(node);
|
const nodes = this.getSelectedOrActiveNodes(node);
|
||||||
|
|
||||||
return nodes.map((node) => node.data.branchId);
|
return nodes.map((node) => node.data.branchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getSelectedOrActiveNoteIds(node: Fancytree.FancytreeNode): string[] {
|
||||||
* @param {FancytreeNode} node
|
|
||||||
*/
|
|
||||||
getSelectedOrActiveNoteIds(node) {
|
|
||||||
const nodes = this.getSelectedOrActiveNodes(node);
|
const nodes = this.getSelectedOrActiveNodes(node);
|
||||||
|
|
||||||
return nodes.map((node) => node.data.noteId);
|
return nodes.map((node) => node.data.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteNotesCommand({ node }) {
|
async deleteNotesCommand({ node }: CommandListenerData<"deleteNotes">) {
|
||||||
const branchIds = this.getSelectedOrActiveBranchIds(node).filter((branchId) => !branchId.startsWith("virt-")); // search results can't be deleted
|
const branchIds = this.getSelectedOrActiveBranchIds(node).filter((branchId) => !branchId.startsWith("virt-")); // search results can't be deleted
|
||||||
|
|
||||||
if (!branchIds.length) {
|
if (!branchIds.length) {
|
||||||
@ -1477,7 +1543,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.clearSelectedNodes();
|
this.clearSelectedNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
canBeMovedUpOrDown(node) {
|
canBeMovedUpOrDown(node: Fancytree.FancytreeNode) {
|
||||||
if (node.data.noteId === "root") {
|
if (node.data.noteId === "root") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1487,8 +1553,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
return !parentNote?.hasLabel("sorted");
|
return !parentNote?.hasLabel("sorted");
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteUpCommand({ node }) {
|
moveNoteUpCommand({ node }: CommandListenerData<"moveNoteUp">) {
|
||||||
if (!this.canBeMovedUpOrDown(node)) {
|
if (!node || !this.canBeMovedUpOrDown(node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1499,7 +1565,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteDownCommand({ node }) {
|
moveNoteDownCommand({ node }: CommandListenerData<"moveNoteDown">) {
|
||||||
if (!this.canBeMovedUpOrDown(node)) {
|
if (!this.canBeMovedUpOrDown(node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1511,11 +1577,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteUpInHierarchyCommand({ node }) {
|
moveNoteUpInHierarchyCommand({ node }: CommandListenerData<"moveNoteUpInHierarchy">) {
|
||||||
branchService.moveNodeUpInHierarchy(node);
|
branchService.moveNodeUpInHierarchy(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteDownInHierarchyCommand({ node }) {
|
moveNoteDownInHierarchyCommand({ node }: CommandListenerData<"moveNoteDownInHierarchy">) {
|
||||||
const toNode = node.getPrevSibling();
|
const toNode = node.getPrevSibling();
|
||||||
|
|
||||||
if (toNode !== null) {
|
if (toNode !== null) {
|
||||||
@ -1571,63 +1637,63 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSubtreeCommand({ node }) {
|
expandSubtreeCommand({ node }: CommandListenerData<"expandSubtree">) {
|
||||||
this.expandTree(node);
|
this.expandTree(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
collapseSubtreeCommand({ node }) {
|
collapseSubtreeCommand({ node }: CommandListenerData<"collapseSubtree">) {
|
||||||
this.collapseTree(node);
|
this.collapseTree(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
async recentChangesInSubtreeCommand({ node }) {
|
async recentChangesInSubtreeCommand({ node }: CommandListenerData<"recentChangesInSubtree">) {
|
||||||
this.triggerCommand("showRecentChanges", { ancestorNoteId: node.data.noteId });
|
this.triggerCommand("showRecentChanges", { ancestorNoteId: node.data.noteId });
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAllNotesInParentCommand({ node }) {
|
selectAllNotesInParentCommand({ node }: CommandListenerData<"selectAllNotesInParent">) {
|
||||||
for (const child of node.getParent().getChildren()) {
|
for (const child of node.getParent().getChildren()) {
|
||||||
child.setSelected(true);
|
child.setSelected(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copyNotesToClipboardCommand({ node }) {
|
copyNotesToClipboardCommand({ node }: CommandListenerData<"copyNotesToClipboard">) {
|
||||||
clipboard.copy(this.getSelectedOrActiveBranchIds(node));
|
clipboard.copy(this.getSelectedOrActiveBranchIds(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
cutNotesToClipboardCommand({ node }) {
|
cutNotesToClipboardCommand({ node }: CommandListenerData<"cutNotesToClipboard">) {
|
||||||
clipboard.cut(this.getSelectedOrActiveBranchIds(node));
|
clipboard.cut(this.getSelectedOrActiveBranchIds(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteNotesFromClipboardCommand({ node }) {
|
pasteNotesFromClipboardCommand({ node }: CommandListenerData<"pasteNotesFromClipboard">) {
|
||||||
clipboard.pasteInto(node.data.branchId);
|
clipboard.pasteInto(node.data.branchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteNotesAfterFromClipboardCommand({ node }) {
|
pasteNotesAfterFromClipboardCommand({ node }: CommandListenerData<"pasteNotesAfterFromClipboard">) {
|
||||||
clipboard.pasteAfter(node.data.branchId);
|
clipboard.pasteAfter(node.data.branchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportNoteCommand({ node }) {
|
async exportNoteCommand({ node }: CommandListenerData<"exportNote">) {
|
||||||
const notePath = treeService.getNotePath(node);
|
const notePath = treeService.getNotePath(node);
|
||||||
|
|
||||||
this.triggerCommand("showExportDialog", { notePath, defaultType: "subtree" });
|
this.triggerCommand("showExportDialog", { notePath, defaultType: "subtree" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async importIntoNoteCommand({ node }) {
|
async importIntoNoteCommand({ node }: CommandListenerData<"importIntoNote">) {
|
||||||
this.triggerCommand("showImportDialog", { noteId: node.data.noteId });
|
this.triggerCommand("showImportDialog", { noteId: node.data.noteId });
|
||||||
}
|
}
|
||||||
|
|
||||||
editNoteTitleCommand({ node }) {
|
editNoteTitleCommand({ node }: CommandListenerData<"editNoteTitle">) {
|
||||||
appContext.triggerCommand("focusOnTitle");
|
appContext.triggerCommand("focusOnTitle");
|
||||||
}
|
}
|
||||||
|
|
||||||
protectSubtreeCommand({ node }) {
|
protectSubtreeCommand({ node }: CommandListenerData<"protectSubtree">) {
|
||||||
protectedSessionService.protectNote(node.data.noteId, true, true);
|
protectedSessionService.protectNote(node.data.noteId, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
unprotectSubtreeCommand({ node }) {
|
unprotectSubtreeCommand({ node }: CommandListenerData<"unprotectSubtree">) {
|
||||||
protectedSessionService.protectNote(node.data.noteId, false, true);
|
protectedSessionService.protectNote(node.data.noteId, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
duplicateSubtreeCommand({ node }) {
|
duplicateSubtreeCommand({ node }: CommandListenerData<"duplicateSubtree">) {
|
||||||
const nodesToDuplicate = this.getSelectedOrActiveNodes(node);
|
const nodesToDuplicate = this.getSelectedOrActiveNodes(node);
|
||||||
|
|
||||||
for (const nodeToDuplicate of nodesToDuplicate) {
|
for (const nodeToDuplicate of nodesToDuplicate) {
|
||||||
@ -1639,19 +1705,21 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
const branch = froca.getBranch(nodeToDuplicate.data.branchId);
|
const branch = froca.getBranch(nodeToDuplicate.data.branchId);
|
||||||
|
|
||||||
noteCreateService.duplicateSubtree(nodeToDuplicate.data.noteId, branch.parentNoteId);
|
if (branch?.parentNoteId) {
|
||||||
|
noteCreateService.duplicateSubtree(nodeToDuplicate.data.noteId, branch.parentNoteId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveLauncherToVisibleCommand({ selectedOrActiveBranchIds }) {
|
moveLauncherToVisibleCommand({ selectedOrActiveBranchIds }: CommandListenerData<"moveLauncherToVisible">) {
|
||||||
this.#moveLaunchers(selectedOrActiveBranchIds, "_lbVisibleLaunchers", "_lbMobileVisibleLaunchers");
|
this.#moveLaunchers(selectedOrActiveBranchIds, "_lbVisibleLaunchers", "_lbMobileVisibleLaunchers");
|
||||||
}
|
}
|
||||||
|
|
||||||
moveLauncherToAvailableCommand({ selectedOrActiveBranchIds }) {
|
moveLauncherToAvailableCommand({ selectedOrActiveBranchIds }: CommandListenerData<"moveLauncherToAvailable">) {
|
||||||
this.#moveLaunchers(selectedOrActiveBranchIds, "_lbAvailableLaunchers", "_lbMobileAvailableLaunchers");
|
this.#moveLaunchers(selectedOrActiveBranchIds, "_lbAvailableLaunchers", "_lbMobileAvailableLaunchers");
|
||||||
}
|
}
|
||||||
|
|
||||||
#moveLaunchers(selectedOrActiveBranchIds, desktopParent, mobileParent) {
|
#moveLaunchers(selectedOrActiveBranchIds: string[], desktopParent: string, mobileParent: string) {
|
||||||
const desktopLaunchersToMove = selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith("_lbMobile"));
|
const desktopLaunchersToMove = selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith("_lbMobile"));
|
||||||
if (desktopLaunchersToMove) {
|
if (desktopLaunchersToMove) {
|
||||||
branchService.moveToParentNote(desktopLaunchersToMove, "_lbRoot_" + desktopParent);
|
branchService.moveToParentNote(desktopLaunchersToMove, "_lbRoot_" + desktopParent);
|
||||||
@ -1663,24 +1731,24 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addNoteLauncherCommand({ node }) {
|
addNoteLauncherCommand({ node }: CommandListenerData<"addNoteLauncher">) {
|
||||||
this.createLauncherNote(node, "note");
|
this.createLauncherNote(node, "note");
|
||||||
}
|
}
|
||||||
|
|
||||||
addScriptLauncherCommand({ node }) {
|
addScriptLauncherCommand({ node }: CommandListenerData<"addScriptLauncher">) {
|
||||||
this.createLauncherNote(node, "script");
|
this.createLauncherNote(node, "script");
|
||||||
}
|
}
|
||||||
|
|
||||||
addWidgetLauncherCommand({ node }) {
|
addWidgetLauncherCommand({ node }: CommandListenerData<"addWidgetLauncher">) {
|
||||||
this.createLauncherNote(node, "customWidget");
|
this.createLauncherNote(node, "customWidget");
|
||||||
}
|
}
|
||||||
|
|
||||||
addSpacerLauncherCommand({ node }) {
|
addSpacerLauncherCommand({ node }: CommandListenerData<"addSpacerLauncher">) {
|
||||||
this.createLauncherNote(node, "spacer");
|
this.createLauncherNote(node, "spacer");
|
||||||
}
|
}
|
||||||
|
|
||||||
async createLauncherNote(node, launcherType) {
|
async createLauncherNote(node: Fancytree.FancytreeNode, launcherType: LauncherType) {
|
||||||
const resp = await server.post(`special-notes/launchers/${node.data.noteId}/${launcherType}`);
|
const resp = await server.post<CreateLauncherResponse>(`special-notes/launchers/${node.data.noteId}/${launcherType}`);
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
toastService.showError(resp.message);
|
toastService.showError(resp.message);
|
@ -3,7 +3,7 @@
|
|||||||
import dateNoteService from "../../services/date_notes.js";
|
import dateNoteService from "../../services/date_notes.js";
|
||||||
import sql from "../../services/sql.js";
|
import sql from "../../services/sql.js";
|
||||||
import cls from "../../services/cls.js";
|
import cls from "../../services/cls.js";
|
||||||
import specialNotesService from "../../services/special_notes.js";
|
import specialNotesService, { type LauncherType } from "../../services/special_notes.js";
|
||||||
import becca from "../../becca/becca.js";
|
import becca from "../../becca/becca.js";
|
||||||
import type { Request } from "express";
|
import type { Request } from "express";
|
||||||
|
|
||||||
@ -85,7 +85,8 @@ function getHoistedNote() {
|
|||||||
function createLauncher(req: Request) {
|
function createLauncher(req: Request) {
|
||||||
return specialNotesService.createLauncher({
|
return specialNotesService.createLauncher({
|
||||||
parentNoteId: req.params.parentNoteId,
|
parentNoteId: req.params.parentNoteId,
|
||||||
launcherType: req.params.launcherType
|
// TODO: Validate the parameter
|
||||||
|
launcherType: req.params.launcherType as LauncherType
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,9 +156,11 @@ function createScriptLauncher(parentNoteId: string, forceNoteId?: string) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LauncherType = "launcher" | "note" | "script" | "customWidget" | "spacer";
|
||||||
|
|
||||||
interface LauncherConfig {
|
interface LauncherConfig {
|
||||||
parentNoteId: string;
|
parentNoteId: string;
|
||||||
launcherType: string;
|
launcherType: LauncherType;
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user