mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 21:19:01 +01:00 
			
		
		
		
	chore(client/ts): port type_widgets/relation_map
This commit is contained in:
		
							parent
							
								
									e682f01c47
								
							
						
					
					
						commit
						3047957239
					
				@ -1,9 +1,8 @@
 | 
				
			|||||||
import type { CommandNames } from "../components/app_context.js";
 | 
					 | 
				
			||||||
import keyboardActionService from "../services/keyboard_actions.js";
 | 
					import keyboardActionService from "../services/keyboard_actions.js";
 | 
				
			||||||
import note_tooltip from "../services/note_tooltip.js";
 | 
					import note_tooltip from "../services/note_tooltip.js";
 | 
				
			||||||
import utils from "../services/utils.js";
 | 
					import utils from "../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ContextMenuOptions<T extends CommandNames> {
 | 
					interface ContextMenuOptions<T> {
 | 
				
			||||||
    x: number;
 | 
					    x: number;
 | 
				
			||||||
    y: number;
 | 
					    y: number;
 | 
				
			||||||
    orientation?: "left";
 | 
					    orientation?: "left";
 | 
				
			||||||
@ -17,7 +16,7 @@ interface MenuSeparatorItem {
 | 
				
			|||||||
    title: "----";
 | 
					    title: "----";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MenuCommandItem<T extends CommandNames> {
 | 
					export interface MenuCommandItem<T> {
 | 
				
			||||||
    title: string;
 | 
					    title: string;
 | 
				
			||||||
    command?: T;
 | 
					    command?: T;
 | 
				
			||||||
    type?: string;
 | 
					    type?: string;
 | 
				
			||||||
@ -30,8 +29,8 @@ export interface MenuCommandItem<T extends CommandNames> {
 | 
				
			|||||||
    spellingSuggestion?: string;
 | 
					    spellingSuggestion?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MenuItem<T extends CommandNames> = MenuCommandItem<T> | MenuSeparatorItem;
 | 
					export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem;
 | 
				
			||||||
export type MenuHandler<T extends CommandNames> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
 | 
					export type MenuHandler<T> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
 | 
				
			||||||
export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent;
 | 
					export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ContextMenu {
 | 
					class ContextMenu {
 | 
				
			||||||
@ -55,7 +54,7 @@ class ContextMenu {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async show<T extends CommandNames>(options: ContextMenuOptions<T>) {
 | 
					    async show<T>(options: ContextMenuOptions<T>) {
 | 
				
			||||||
        this.options = options;
 | 
					        this.options = options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        note_tooltip.dismissAllTooltips();
 | 
					        note_tooltip.dismissAllTooltips();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import appContext from "../components/app_context.js";
 | 
					import appContext from "../components/app_context.js";
 | 
				
			||||||
import type { ConfirmDialogOptions, ConfirmWithMessageOptions } from "../widgets/dialogs/confirm.js";
 | 
					import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptions } from "../widgets/dialogs/confirm.js";
 | 
				
			||||||
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
 | 
					import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function info(message: string) {
 | 
					async function info(message: string) {
 | 
				
			||||||
@ -16,7 +16,7 @@ async function confirm(message: string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function confirmDeleteNoteBoxWithNote(title: string) {
 | 
					async function confirmDeleteNoteBoxWithNote(title: string) {
 | 
				
			||||||
    return new Promise((res) => appContext.triggerCommand("showConfirmDeleteNoteBoxWithNoteDialog", { title, callback: res }));
 | 
					    return new Promise<ConfirmDialogResult | undefined>((res) => appContext.triggerCommand("showConfirmDeleteNoteBoxWithNoteDialog", { title, callback: res }));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function prompt(props: PromptDialogOptions) {
 | 
					async function prompt(props: PromptDialogOptions) {
 | 
				
			||||||
 | 
				
			|||||||
@ -252,7 +252,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function goToLink(evt: MouseEvent | JQuery.ClickEvent) {
 | 
					function goToLink(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent) {
 | 
				
			||||||
    const $link = $(evt.target as any).closest("a,.block-link");
 | 
					    const $link = $(evt.target as any).closest("a,.block-link");
 | 
				
			||||||
    const hrefLink = $link.attr("href") || $link.attr("data-href");
 | 
					    const hrefLink = $link.attr("href") || $link.attr("data-href");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										42
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							@ -7,6 +7,7 @@ import server from "./services/server.ts";
 | 
				
			|||||||
import library_loader, { Library } from "./services/library_loader.ts";
 | 
					import library_loader, { Library } from "./services/library_loader.ts";
 | 
				
			||||||
import type { init } from "i18next";
 | 
					import type { init } from "i18next";
 | 
				
			||||||
import type { lint } from "./services/eslint.ts";
 | 
					import type { lint } from "./services/eslint.ts";
 | 
				
			||||||
 | 
					import type { RelationType } from "./widgets/type_widgets/relation_map.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ElectronProcess {
 | 
					interface ElectronProcess {
 | 
				
			||||||
    type: string;
 | 
					    type: string;
 | 
				
			||||||
@ -363,4 +364,45 @@ declare global {
 | 
				
			|||||||
            minimumCharacters: number;
 | 
					            minimumCharacters: number;
 | 
				
			||||||
        }[];
 | 
					        }[];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * jsPlumb
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    var jsPlumb: typeof import("jsplumb").jsPlumb;
 | 
				
			||||||
 | 
					    type jsPlumbInstance = import("jsplumb").jsPlumbInstance;
 | 
				
			||||||
 | 
					    type OverlaySpec = typeof import("jsplumb").OverlaySpec;
 | 
				
			||||||
 | 
					    type ConnectionMadeEventInfo = typeof import("jsplumb").ConnectionMadeEventInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * Panzoom
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function panzoom(el: HTMLElement, opts: {
 | 
				
			||||||
 | 
					        maxZoom: number,
 | 
				
			||||||
 | 
					        minZoom: number,
 | 
				
			||||||
 | 
					        smoothScroll: false,
 | 
				
			||||||
 | 
					        filterKey: (e: { altKey: boolean }, dx: number, dy: number, dz: number) => void;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface PanZoom {
 | 
				
			||||||
 | 
					        zoomTo(x: number, y: number, scale: number);
 | 
				
			||||||
 | 
					        moveTo(x: number, y: number);
 | 
				
			||||||
 | 
					        on(event: string, callback: () => void);
 | 
				
			||||||
 | 
					        getTransform(): unknown;
 | 
				
			||||||
 | 
					        dispose(): void;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module "jsplumb" {
 | 
				
			||||||
 | 
					    interface Connection {
 | 
				
			||||||
 | 
					        canvas: HTMLCanvasElement;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface Overlay {
 | 
				
			||||||
 | 
					        setLabel(label: string);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface ConnectParams {
 | 
				
			||||||
 | 
					        type: RelationType;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,8 @@ const TPL = `
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ConfirmDialogCallback = (val?: false | ConfirmDialogOptions) => void;
 | 
					export type ConfirmDialogResult = false | ConfirmDialogOptions;
 | 
				
			||||||
 | 
					export type ConfirmDialogCallback = (val?: ConfirmDialogResult) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ConfirmDialogOptions {
 | 
					export interface ConfirmDialogOptions {
 | 
				
			||||||
    confirmed: boolean;
 | 
					    confirmed: boolean;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { t } from "../services/i18n.js";
 | 
					import { t } from "../services/i18n.js";
 | 
				
			||||||
import BasicWidget from "./basic_widget.js";
 | 
					import BasicWidget from "./basic_widget.js";
 | 
				
			||||||
import contextMenu from "../menus/context_menu.js";
 | 
					import contextMenu from "../menus/context_menu.js";
 | 
				
			||||||
import appContext from "../components/app_context.js";
 | 
					import appContext, { type CommandNames } from "../components/app_context.js";
 | 
				
			||||||
import utils from "../services/utils.js";
 | 
					import utils from "../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TPL = `<div class="spacer"></div>`;
 | 
					const TPL = `<div class="spacer"></div>`;
 | 
				
			||||||
@ -26,7 +26,7 @@ export default class SpacerWidget extends BasicWidget {
 | 
				
			|||||||
        this.$widget.on("contextmenu", (e) => {
 | 
					        this.$widget.on("contextmenu", (e) => {
 | 
				
			||||||
            this.$widget.tooltip("hide");
 | 
					            this.$widget.tooltip("hide");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            contextMenu.show({
 | 
					            contextMenu.show<CommandNames>({
 | 
				
			||||||
                x: e.pageX,
 | 
					                x: e.pageX,
 | 
				
			||||||
                y: e.pageY,
 | 
					                y: e.pageY,
 | 
				
			||||||
                items: [{ title: t("spacer.configure_launchbar"), command: "showLaunchBarSubtree", uiIcon: "bx " + (utils.isMobile() ? "bx-mobile" : "bx-sidebar") }],
 | 
					                items: [{ title: t("spacer.configure_launchbar"), command: "showLaunchBarSubtree", uiIcon: "bx " + (utils.isMobile() ? "bx-mobile" : "bx-sidebar") }],
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ import BasicWidget from "./basic_widget.js";
 | 
				
			|||||||
import contextMenu from "../menus/context_menu.js";
 | 
					import contextMenu from "../menus/context_menu.js";
 | 
				
			||||||
import utils from "../services/utils.js";
 | 
					import utils from "../services/utils.js";
 | 
				
			||||||
import keyboardActionService from "../services/keyboard_actions.js";
 | 
					import keyboardActionService from "../services/keyboard_actions.js";
 | 
				
			||||||
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
 | 
					import appContext, { type CommandNames, type CommandListenerData, type EventData } from "../components/app_context.js";
 | 
				
			||||||
import froca from "../services/froca.js";
 | 
					import froca from "../services/froca.js";
 | 
				
			||||||
import attributeService from "../services/attributes.js";
 | 
					import attributeService from "../services/attributes.js";
 | 
				
			||||||
import type NoteContext from "../components/note_context.js";
 | 
					import type NoteContext from "../components/note_context.js";
 | 
				
			||||||
@ -268,7 +268,7 @@ export default class TabRowWidget extends BasicWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            const ntxId = $(e.target).closest(".note-tab").attr("data-ntx-id");
 | 
					            const ntxId = $(e.target).closest(".note-tab").attr("data-ntx-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            contextMenu.show({
 | 
					            contextMenu.show<CommandNames>({
 | 
				
			||||||
                x: e.pageX,
 | 
					                x: e.pageX,
 | 
				
			||||||
                y: e.pageY,
 | 
					                y: e.pageY,
 | 
				
			||||||
                items: [
 | 
					                items: [
 | 
				
			||||||
 | 
				
			|||||||
@ -5,13 +5,14 @@ import contextMenu from "../../menus/context_menu.js";
 | 
				
			|||||||
import toastService from "../../services/toast.js";
 | 
					import toastService from "../../services/toast.js";
 | 
				
			||||||
import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
 | 
					import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
 | 
				
			||||||
import TypeWidget from "./type_widget.js";
 | 
					import TypeWidget from "./type_widget.js";
 | 
				
			||||||
import appContext from "../../components/app_context.js";
 | 
					import appContext, { type EventData } from "../../components/app_context.js";
 | 
				
			||||||
import utils from "../../services/utils.js";
 | 
					import utils from "../../services/utils.js";
 | 
				
			||||||
import froca from "../../services/froca.js";
 | 
					import froca from "../../services/froca.js";
 | 
				
			||||||
import dialogService from "../../services/dialog.js";
 | 
					import dialogService from "../../services/dialog.js";
 | 
				
			||||||
import { t } from "../../services/i18n.js";
 | 
					import { t } from "../../services/i18n.js";
 | 
				
			||||||
 | 
					import type FNote from "../../entities/fnote.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uniDirectionalOverlays = [
 | 
					const uniDirectionalOverlays: OverlaySpec[] = [
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        "Arrow",
 | 
					        "Arrow",
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -92,7 +93,62 @@ const TPL = `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let containerCounter = 1;
 | 
					let containerCounter = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Clipboard {
 | 
				
			||||||
 | 
					    noteId: string;
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface MapData {
 | 
				
			||||||
 | 
					    notes: {
 | 
				
			||||||
 | 
					        noteId: string;
 | 
				
			||||||
 | 
					        x: number;
 | 
				
			||||||
 | 
					        y: number;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					    transform: {
 | 
				
			||||||
 | 
					        x: number,
 | 
				
			||||||
 | 
					        y: number,
 | 
				
			||||||
 | 
					        scale: number
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Relation {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    attributeId: string;
 | 
				
			||||||
 | 
					    sourceNoteId: string;
 | 
				
			||||||
 | 
					    targetNoteId: string;
 | 
				
			||||||
 | 
					    type: RelationType;
 | 
				
			||||||
 | 
					    render: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Deduplicate.
 | 
				
			||||||
 | 
					interface PostNoteResponse {
 | 
				
			||||||
 | 
					    note: {
 | 
				
			||||||
 | 
					        noteId: string;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Deduplicate.
 | 
				
			||||||
 | 
					interface RelationMapPostResponse {
 | 
				
			||||||
 | 
					    relations: Relation[];
 | 
				
			||||||
 | 
					    inverseRelations: Record<string, string>;
 | 
				
			||||||
 | 
					    noteTitles: Record<string, string>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MenuCommands = "openInNewTab" | "remove" | "editTitle";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class RelationMapTypeWidget extends TypeWidget {
 | 
					export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private clipboard?: Clipboard | null;
 | 
				
			||||||
 | 
					    private jsPlumbInstance?: import("jsplumb").jsPlumbInstance | null;
 | 
				
			||||||
 | 
					    private pzInstance?: PanZoom | null;
 | 
				
			||||||
 | 
					    private mapData?: MapData | null;
 | 
				
			||||||
 | 
					    private relations?: Relation[] | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private $relationMapContainer!: JQuery<HTMLElement>;
 | 
				
			||||||
 | 
					    private $relationMapWrapper!: JQuery<HTMLElement>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static getType() {
 | 
					    static getType() {
 | 
				
			||||||
        return "relationMap";
 | 
					        return "relationMap";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -109,7 +165,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.$relationMapWrapper = this.$widget.find(".relation-map-wrapper");
 | 
					        this.$relationMapWrapper = this.$widget.find(".relation-map-wrapper");
 | 
				
			||||||
        this.$relationMapWrapper.on("click", (event) => {
 | 
					        this.$relationMapWrapper.on("click", (event) => {
 | 
				
			||||||
            if (this.clipboard) {
 | 
					            if (this.clipboard && this.mapData) {
 | 
				
			||||||
                let { x, y } = this.getMousePosition(event);
 | 
					                let { x, y } = this.getMousePosition(event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // modifying position so that the cursor is on the top-center of the box
 | 
					                // modifying position so that the cursor is on the top-center of the box
 | 
				
			||||||
@ -130,7 +186,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.$relationMapContainer.attr("id", "relation-map-container-" + containerCounter++);
 | 
					        this.$relationMapContainer.attr("id", "relation-map-container-" + containerCounter++);
 | 
				
			||||||
        this.$relationMapContainer.on("contextmenu", ".note-box", (e) => {
 | 
					        this.$relationMapContainer.on("contextmenu", ".note-box", (e) => {
 | 
				
			||||||
            contextMenu.show({
 | 
					            contextMenu.show<MenuCommands>({
 | 
				
			||||||
                x: e.pageX,
 | 
					                x: e.pageX,
 | 
				
			||||||
                y: e.pageY,
 | 
					                y: e.pageY,
 | 
				
			||||||
                items: [
 | 
					                items: [
 | 
				
			||||||
@ -151,14 +207,14 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.initialized = new Promise(async (res) => {
 | 
					        this.initialized = new Promise(async (res) => {
 | 
				
			||||||
            await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP);
 | 
					            await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP);
 | 
				
			||||||
 | 
					            // TODO: Remove once we port to webpack.
 | 
				
			||||||
            jsPlumb.ready(res);
 | 
					            (jsPlumb as unknown as jsPlumbInstance).ready(res);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.doRender();
 | 
					        super.doRender();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async contextMenuHandler(command, originalTarget) {
 | 
					    async contextMenuHandler(command: MenuCommands | undefined, originalTarget: HTMLElement) {
 | 
				
			||||||
        const $noteBox = $(originalTarget).closest(".note-box");
 | 
					        const $noteBox = $(originalTarget).closest(".note-box");
 | 
				
			||||||
        const $title = $noteBox.find(".title a");
 | 
					        const $title = $noteBox.find(".title a");
 | 
				
			||||||
        const noteId = this.idToNoteId($noteBox.prop("id"));
 | 
					        const noteId = this.idToNoteId($noteBox.prop("id"));
 | 
				
			||||||
@ -168,11 +224,11 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        } else if (command === "remove") {
 | 
					        } else if (command === "remove") {
 | 
				
			||||||
            const result = await dialogService.confirmDeleteNoteBoxWithNote($title.text());
 | 
					            const result = await dialogService.confirmDeleteNoteBoxWithNote($title.text());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!result.confirmed) {
 | 
					            if (typeof result !== "object" || !result.confirmed) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.jsPlumbInstance.remove(this.noteIdToId(noteId));
 | 
					            this.jsPlumbInstance?.remove(this.noteIdToId(noteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (result.isDeleteNoteChecked) {
 | 
					            if (result.isDeleteNoteChecked) {
 | 
				
			||||||
                const taskId = utils.randomString(10);
 | 
					                const taskId = utils.randomString(10);
 | 
				
			||||||
@ -180,9 +236,13 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
                await server.remove(`notes/${noteId}?taskId=${taskId}&last=true`);
 | 
					                await server.remove(`notes/${noteId}?taskId=${taskId}&last=true`);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.mapData) {
 | 
				
			||||||
                this.mapData.notes = this.mapData.notes.filter((note) => note.noteId !== noteId);
 | 
					                this.mapData.notes = this.mapData.notes.filter((note) => note.noteId !== noteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.relations) {
 | 
				
			||||||
                this.relations = this.relations.filter((relation) => relation.sourceNoteId !== noteId && relation.targetNoteId !== noteId);
 | 
					                this.relations = this.relations.filter((relation) => relation.sourceNoteId !== noteId && relation.targetNoteId !== noteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.saveData();
 | 
					            this.saveData();
 | 
				
			||||||
        } else if (command === "editTitle") {
 | 
					        } else if (command === "editTitle") {
 | 
				
			||||||
@ -216,9 +276,9 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const blob = await this.note.getBlob();
 | 
					        const blob = await this.note?.getBlob();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (blob.content) {
 | 
					        if (blob?.content) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                this.mapData = JSON.parse(blob.content);
 | 
					                this.mapData = JSON.parse(blob.content);
 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
@ -227,15 +287,15 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    noteIdToId(noteId) {
 | 
					    noteIdToId(noteId: string) {
 | 
				
			||||||
        return `rel-map-note-${noteId}`;
 | 
					        return `rel-map-note-${noteId}`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    idToNoteId(id) {
 | 
					    idToNoteId(id: string) {
 | 
				
			||||||
        return id.substr(13);
 | 
					        return id.substr(13);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async doRefresh(note) {
 | 
					    async doRefresh(note: FNote) {
 | 
				
			||||||
        await this.loadMapData();
 | 
					        await this.loadMapData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.initJsPlumbInstance();
 | 
					        this.initJsPlumbInstance();
 | 
				
			||||||
@ -248,15 +308,19 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
    clearMap() {
 | 
					    clearMap() {
 | 
				
			||||||
        // delete all endpoints and connections
 | 
					        // delete all endpoints and connections
 | 
				
			||||||
        // this is done at this point (after async operations) to reduce flicker to the minimum
 | 
					        // this is done at this point (after async operations) to reduce flicker to the minimum
 | 
				
			||||||
        this.jsPlumbInstance.deleteEveryEndpoint();
 | 
					        this.jsPlumbInstance?.deleteEveryEndpoint();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // without this, we still end up with note boxes remaining in the canvas
 | 
					        // without this, we still end up with note boxes remaining in the canvas
 | 
				
			||||||
        this.$relationMapContainer.empty();
 | 
					        this.$relationMapContainer.empty();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async loadNotesAndRelations() {
 | 
					    async loadNotesAndRelations() {
 | 
				
			||||||
 | 
					        if (!this.mapData || !this.jsPlumbInstance) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const noteIds = this.mapData.notes.map((note) => note.noteId);
 | 
					        const noteIds = this.mapData.notes.map((note) => note.noteId);
 | 
				
			||||||
        const data = await server.post("relation-map", { noteIds, relationMapNoteId: this.noteId });
 | 
					        const data = await server.post<RelationMapPostResponse>("relation-map", { noteIds, relationMapNoteId: this.noteId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.relations = [];
 | 
					        this.relations = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -282,6 +346,10 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        this.mapData.notes = this.mapData.notes.filter((note) => note.noteId in data.noteTitles);
 | 
					        this.mapData.notes = this.mapData.notes.filter((note) => note.noteId in data.noteTitles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.jsPlumbInstance.batch(async () => {
 | 
					        this.jsPlumbInstance.batch(async () => {
 | 
				
			||||||
 | 
					            if (!this.jsPlumbInstance || !this.mapData || !this.relations) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.clearMap();
 | 
					            this.clearMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (const note of this.mapData.notes) {
 | 
					            for (const note of this.mapData.notes) {
 | 
				
			||||||
@ -301,6 +369,8 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
                    type: relation.type
 | 
					                    type: relation.type
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // TODO: Does this actually do anything.
 | 
				
			||||||
 | 
					                //@ts-expect-error
 | 
				
			||||||
                connection.id = relation.attributeId;
 | 
					                connection.id = relation.attributeId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (relation.type === "inverse") {
 | 
					                if (relation.type === "inverse") {
 | 
				
			||||||
@ -331,14 +401,18 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.pzInstance) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.pzInstance.on("transform", () => {
 | 
					        this.pzInstance.on("transform", () => {
 | 
				
			||||||
            // gets triggered on any transform change
 | 
					            // gets triggered on any transform change
 | 
				
			||||||
            this.jsPlumbInstance.setZoom(this.getZoom());
 | 
					            this.jsPlumbInstance?.setZoom(this.getZoom());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.saveCurrentTransform();
 | 
					            this.saveCurrentTransform();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.mapData.transform) {
 | 
					        if (this.mapData?.transform) {
 | 
				
			||||||
            this.pzInstance.zoomTo(0, 0, this.mapData.transform.scale);
 | 
					            this.pzInstance.zoomTo(0, 0, this.mapData.transform.scale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.pzInstance.moveTo(this.mapData.transform.x, this.mapData.transform.y);
 | 
					            this.pzInstance.moveTo(this.mapData.transform.x, this.mapData.transform.y);
 | 
				
			||||||
@ -349,9 +423,13 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    saveCurrentTransform() {
 | 
					    saveCurrentTransform() {
 | 
				
			||||||
 | 
					        if (!this.pzInstance) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const newTransform = this.pzInstance.getTransform();
 | 
					        const newTransform = this.pzInstance.getTransform();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (JSON.stringify(newTransform) !== JSON.stringify(this.mapData.transform)) {
 | 
					        if (this.mapData && JSON.stringify(newTransform) !== JSON.stringify(this.mapData.transform)) {
 | 
				
			||||||
            // clone transform object
 | 
					            // clone transform object
 | 
				
			||||||
            this.mapData.transform = JSON.parse(JSON.stringify(newTransform));
 | 
					            this.mapData.transform = JSON.parse(JSON.stringify(newTransform));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -385,6 +463,10 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
            Container: this.$relationMapContainer.attr("id")
 | 
					            Container: this.$relationMapContainer.attr("id")
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.jsPlumbInstance) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.jsPlumbInstance.registerConnectionType("uniDirectional", { anchor: "Continuous", connector: "StateMachine", overlays: uniDirectionalOverlays });
 | 
					        this.jsPlumbInstance.registerConnectionType("uniDirectional", { anchor: "Continuous", connector: "StateMachine", overlays: uniDirectionalOverlays });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.jsPlumbInstance.registerConnectionType("biDirectional", { anchor: "Continuous", connector: "StateMachine", overlays: biDirectionalOverlays });
 | 
					        this.jsPlumbInstance.registerConnectionType("biDirectional", { anchor: "Continuous", connector: "StateMachine", overlays: biDirectionalOverlays });
 | 
				
			||||||
@ -396,10 +478,10 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        this.jsPlumbInstance.bind("connection", (info, originalEvent) => this.connectionCreatedHandler(info, originalEvent));
 | 
					        this.jsPlumbInstance.bind("connection", (info, originalEvent) => this.connectionCreatedHandler(info, originalEvent));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async connectionCreatedHandler(info, originalEvent) {
 | 
					    async connectionCreatedHandler(info: ConnectionMadeEventInfo, originalEvent: Event) {
 | 
				
			||||||
        const connection = info.connection;
 | 
					        const connection = info.connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        connection.bind("contextmenu", (obj, event) => {
 | 
					        connection.bind("contextmenu", (obj: unknown, event: MouseEvent) => {
 | 
				
			||||||
            if (connection.getType().includes("link")) {
 | 
					            if (connection.getType().includes("link")) {
 | 
				
			||||||
                // don't create context menu if it's a link since there's nothing to do with link from relation map
 | 
					                // don't create context menu if it's a link since there's nothing to do with link from relation map
 | 
				
			||||||
                // (don't open browser menu either)
 | 
					                // (don't open browser menu either)
 | 
				
			||||||
@ -414,15 +496,17 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
                    items: [{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }],
 | 
					                    items: [{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }],
 | 
				
			||||||
                    selectMenuItemHandler: async ({ command }) => {
 | 
					                    selectMenuItemHandler: async ({ command }) => {
 | 
				
			||||||
                        if (command === "remove") {
 | 
					                        if (command === "remove") {
 | 
				
			||||||
                            if (!(await dialogService.confirm(t("relation_map.confirm_remove_relation")))) {
 | 
					                            if (!(await dialogService.confirm(t("relation_map.confirm_remove_relation"))) || !this.relations) {
 | 
				
			||||||
                                return;
 | 
					                                return;
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            const relation = this.relations.find((rel) => rel.attributeId === connection.id);
 | 
					                            const relation = this.relations.find((rel) => rel.attributeId === connection.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (relation) {
 | 
				
			||||||
                                await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`);
 | 
					                                await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            this.jsPlumbInstance.deleteConnection(connection);
 | 
					                            this.jsPlumbInstance?.deleteConnection(connection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            this.relations = this.relations.filter((relation) => relation.attributeId !== connection.id);
 | 
					                            this.relations = this.relations.filter((relation) => relation.attributeId !== connection.id);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@ -432,16 +516,20 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if there's no event, then this has been triggered programmatically
 | 
					        // if there's no event, then this has been triggered programmatically
 | 
				
			||||||
        if (!originalEvent) {
 | 
					        if (!originalEvent || !this.jsPlumbInstance) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let name = await dialogService.prompt({
 | 
					        let name = await dialogService.prompt({
 | 
				
			||||||
            message: t("relation_map.specify_new_relation_name"),
 | 
					            message: t("relation_map.specify_new_relation_name"),
 | 
				
			||||||
            shown: ({ $answer }) => {
 | 
					            shown: ({ $answer }) => {
 | 
				
			||||||
 | 
					                if (!$answer) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $answer.on("keyup", () => {
 | 
					                $answer.on("keyup", () => {
 | 
				
			||||||
                    // invalid characters are simply ignored (from user perspective they are not even entered)
 | 
					                    // invalid characters are simply ignored (from user perspective they are not even entered)
 | 
				
			||||||
                    const attrName = utils.filterAttributeName($answer.val());
 | 
					                    const attrName = utils.filterAttributeName($answer.val() as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    $answer.val(attrName);
 | 
					                    $answer.val(attrName);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
@ -465,7 +553,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        const targetNoteId = this.idToNoteId(connection.target.id);
 | 
					        const targetNoteId = this.idToNoteId(connection.target.id);
 | 
				
			||||||
        const sourceNoteId = this.idToNoteId(connection.source.id);
 | 
					        const sourceNoteId = this.idToNoteId(connection.source.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const relationExists = this.relations.some((rel) => rel.targetNoteId === targetNoteId && rel.sourceNoteId === sourceNoteId && rel.name === name);
 | 
					        const relationExists = this.relations?.some((rel) => rel.targetNoteId === targetNoteId && rel.sourceNoteId === sourceNoteId && rel.name === name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (relationExists) {
 | 
					        if (relationExists) {
 | 
				
			||||||
            await dialogService.info(t("relation_map.connection_exists", { name }));
 | 
					            await dialogService.info(t("relation_map.connection_exists", { name }));
 | 
				
			||||||
@ -484,11 +572,18 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        this.spacedUpdate.scheduleUpdate();
 | 
					        this.spacedUpdate.scheduleUpdate();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async createNoteBox(noteId, title, x, y) {
 | 
					    async createNoteBox(noteId: string, title: string, x: number, y: number) {
 | 
				
			||||||
 | 
					        if (!this.jsPlumbInstance) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const $link = await linkService.createLink(noteId, { title });
 | 
					        const $link = await linkService.createLink(noteId, { title });
 | 
				
			||||||
        $link.mousedown((e) => linkService.goToLink(e));
 | 
					        $link.mousedown((e) => linkService.goToLink(e));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const note = await froca.getNote(noteId);
 | 
					        const note = await froca.getNote(noteId);
 | 
				
			||||||
 | 
					        if (!note) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const $noteBox = $("<div>")
 | 
					        const $noteBox = $("<div>")
 | 
				
			||||||
            .addClass("note-box")
 | 
					            .addClass("note-box")
 | 
				
			||||||
@ -507,13 +602,14 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
            stop: (params) => {
 | 
					            stop: (params) => {
 | 
				
			||||||
                const noteId = this.idToNoteId(params.el.id);
 | 
					                const noteId = this.idToNoteId(params.el.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const note = this.mapData.notes.find((note) => note.noteId === noteId);
 | 
					                const note = this.mapData?.notes.find((note) => note.noteId === noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!note) {
 | 
					                if (!note) {
 | 
				
			||||||
                    logError(t("relation_map.note_not_found", { noteId }));
 | 
					                    logError(t("relation_map.note_not_found", { noteId }));
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                //@ts-expect-error TODO: Check if this is still valid.
 | 
				
			||||||
                [note.x, note.y] = params.finalPos;
 | 
					                [note.x, note.y] = params.finalPos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this.saveData();
 | 
					                this.saveData();
 | 
				
			||||||
@ -552,25 +648,29 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
            throw new Error(t("relation_map.cannot_match_transform", { transform }));
 | 
					            throw new Error(t("relation_map.cannot_match_transform", { transform }));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return matches[1];
 | 
					        return parseFloat(matches[1]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async dropNoteOntoRelationMapHandler(ev) {
 | 
					    async dropNoteOntoRelationMapHandler(ev: JQuery.DropEvent) {
 | 
				
			||||||
        ev.preventDefault();
 | 
					        ev.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const notes = JSON.parse(ev.originalEvent.dataTransfer.getData("text"));
 | 
					        const dragData = ev.originalEvent?.dataTransfer?.getData("text");
 | 
				
			||||||
 | 
					        if (!dragData) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const notes = JSON.parse(dragData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let { x, y } = this.getMousePosition(ev);
 | 
					        let { x, y } = this.getMousePosition(ev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const note of notes) {
 | 
					        for (const note of notes) {
 | 
				
			||||||
            const exists = this.mapData.notes.some((n) => n.noteId === note.noteId);
 | 
					            const exists = this.mapData?.notes.some((n) => n.noteId === note.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (exists) {
 | 
					            if (exists) {
 | 
				
			||||||
                toastService.showError(t("relation_map.note_already_in_diagram", { title: note.title }));
 | 
					                toastService.showError(t("relation_map.note_already_in_diagram", { title: note.title }));
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.mapData.notes.push({ noteId: note.noteId, x, y });
 | 
					            this.mapData?.notes.push({ noteId: note.noteId, x, y });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (x > 1000) {
 | 
					            if (x > 1000) {
 | 
				
			||||||
                y += 100;
 | 
					                y += 100;
 | 
				
			||||||
@ -585,14 +685,14 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        this.loadNotesAndRelations();
 | 
					        this.loadNotesAndRelations();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getMousePosition(evt) {
 | 
					    getMousePosition(evt: JQuery.ClickEvent | JQuery.DropEvent) {
 | 
				
			||||||
        const rect = this.$relationMapContainer[0].getBoundingClientRect();
 | 
					        const rect = this.$relationMapContainer[0].getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const zoom = this.getZoom();
 | 
					        const zoom = this.getZoom();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            x: (evt.clientX - rect.left) / zoom,
 | 
					            x: ((evt.clientX ?? 0) - rect.left) / zoom,
 | 
				
			||||||
            y: (evt.clientY - rect.top) / zoom
 | 
					            y: ((evt.clientY ?? 0) - rect.top) / zoom
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -602,18 +702,18 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async relationMapCreateChildNoteEvent({ ntxId }) {
 | 
					    async relationMapCreateChildNoteEvent({ ntxId }: EventData<"relationMapCreateChildNote">) {
 | 
				
			||||||
        if (!this.isNoteContext(ntxId)) {
 | 
					        if (!this.isNoteContext(ntxId)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
 | 
					        const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!title.trim()) {
 | 
					        if (!title?.trim()) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { note } = await server.post(`notes/${this.noteId}/children?target=into`, {
 | 
					        const { note } = await server.post<PostNoteResponse>(`notes/${this.noteId}/children?target=into`, {
 | 
				
			||||||
            title,
 | 
					            title,
 | 
				
			||||||
            content: "",
 | 
					            content: "",
 | 
				
			||||||
            type: "text"
 | 
					            type: "text"
 | 
				
			||||||
@ -624,29 +724,29 @@ export default class RelationMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
        this.clipboard = { noteId: note.noteId, title };
 | 
					        this.clipboard = { noteId: note.noteId, title };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    relationMapResetPanZoomEvent({ ntxId }) {
 | 
					    relationMapResetPanZoomEvent({ ntxId }: EventData<"relationMapResetPanZoom">) {
 | 
				
			||||||
        if (!this.isNoteContext(ntxId)) {
 | 
					        if (!this.isNoteContext(ntxId)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // reset to initial pan & zoom state
 | 
					        // reset to initial pan & zoom state
 | 
				
			||||||
        this.pzInstance.zoomTo(0, 0, 1 / this.getZoom());
 | 
					        this.pzInstance?.zoomTo(0, 0, 1 / this.getZoom());
 | 
				
			||||||
        this.pzInstance.moveTo(0, 0);
 | 
					        this.pzInstance?.moveTo(0, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    relationMapResetZoomInEvent({ ntxId }) {
 | 
					    relationMapResetZoomInEvent({ ntxId }: EventData<"relationMapResetZoomIn">) {
 | 
				
			||||||
        if (!this.isNoteContext(ntxId)) {
 | 
					        if (!this.isNoteContext(ntxId)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.pzInstance.zoomTo(0, 0, 1.2);
 | 
					        this.pzInstance?.zoomTo(0, 0, 1.2);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    relationMapResetZoomOutEvent({ ntxId }) {
 | 
					    relationMapResetZoomOutEvent({ ntxId }: EventData<"relationMapResetZoomOut">) {
 | 
				
			||||||
        if (!this.isNoteContext(ntxId)) {
 | 
					        if (!this.isNoteContext(ntxId)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.pzInstance.zoomTo(0, 0, 0.8);
 | 
					        this.pzInstance?.zoomTo(0, 0, 0.8);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user