From 2d29d1b41fed8af1b3b2921784d655b9eea234c9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 4 Oct 2025 13:04:40 +0300 Subject: [PATCH] chore(react): add back note map link configuration --- apps/client/src/services/utils.ts | 2 +- apps/client/src/widgets/note_map.ts | 50 ----------------- apps/client/src/widgets/note_map/NoteMap.tsx | 7 ++- apps/client/src/widgets/note_map/rendering.ts | 54 ++++++++++++++++++- apps/client/src/widgets/note_map/utils.ts | 1 + 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 61f013829..c17443e89 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -169,7 +169,7 @@ const entityMap: Record = { "=": "=" }; -function escapeHtml(str: string) { +export function escapeHtml(str: string) { return str.replace(/[&<>"'`=\/]/g, (s) => entityMap[s]); } diff --git a/apps/client/src/widgets/note_map.ts b/apps/client/src/widgets/note_map.ts index 0811c7515..3e0bda763 100644 --- a/apps/client/src/widgets/note_map.ts +++ b/apps/client/src/widgets/note_map.ts @@ -34,14 +34,10 @@ export default class NoteMapWidget extends NoteContextAwareWidget { private fixNodes: boolean; private widgetMode: WidgetMode; - private themeStyle!: string; private $container!: JQuery; - private $styleResolver!: JQuery; private $fixNodesButton!: JQuery; graph!: ForceGraph; private noteIdToSizeMap!: Record; - private zoomLevel!: number; - private nodes!: Node[]; constructor(widgetMode: WidgetMode) { super(); @@ -117,13 +113,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget { } }); - if (this.mapType === "link") { - this.graph - .linkLabel((l) => `${esc((l as Link).source.name)} - ${esc((l as Link).name)} - ${esc((l as Link).target.name)}`) - .linkCanvasObject((link, ctx) => this.paintLink(link as Link, ctx)) - .linkCanvasObjectMode(() => "after"); - } - const nodeLinkRatio = data.nodes.length / data.links.length; const magnifiedRatio = Math.pow(nodeLinkRatio, 1.5); const charge = -20 / magnifiedRatio; @@ -148,45 +137,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget { this.zoomLevel = level; } - paintLink(link: Link, ctx: CanvasRenderingContext2D) { - if (this.zoomLevel < 5) { - return; - } - - ctx.font = `3px ${this.cssData.fontFamily}`; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = this.cssData.mutedTextColor; - - const { source, target } = link; - if (typeof source !== "object" || typeof target !== "object") { - return; - } - - if (source.x && source.y && target.x && target.y) { - const x = (source.x + target.x) / 2; - const y = (source.y + target.y) / 2; - ctx.save(); - ctx.translate(x, y); - - const deltaY = source.y - target.y; - const deltaX = source.x - target.x; - - let angle = Math.atan2(deltaY, deltaX); - let moveY = 2; - - if (angle < -Math.PI / 2 || angle > Math.PI / 2) { - angle += Math.PI; - moveY = -2; - } - - ctx.rotate(angle); - ctx.fillText(link.name, 0, moveY); - } - - ctx.restore(); - } - renderData(data: Data) { if (this.widgetMode === "ribbon" && this.note?.type !== "search") { setTimeout(() => { diff --git a/apps/client/src/widgets/note_map/NoteMap.tsx b/apps/client/src/widgets/note_map/NoteMap.tsx index c9f73f6b9..8b0193896 100644 --- a/apps/client/src/widgets/note_map/NoteMap.tsx +++ b/apps/client/src/widgets/note_map/NoteMap.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "preact/hooks"; import "./NoteMap.css"; -import { getMapRootNoteId, getThemeStyle, NoteMapWidgetMode, rgb2hex } from "./utils"; +import { getMapRootNoteId, getThemeStyle, MapType, NoteMapWidgetMode, rgb2hex } from "./utils"; import { RefObject } from "preact"; import FNote from "../../entities/fnote"; import { useElementSize, useNoteContext, useNoteLabel } from "../react/hooks"; @@ -16,8 +16,6 @@ interface NoteMapProps { parentRef: RefObject; } -type MapType = "tree" | "link"; - export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) { const containerRef = useRef(null); const styleResolverRef = useRef(null); @@ -50,7 +48,8 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) { noteIdToSizeMap: notesAndRelations.noteIdToSizeMap, notesAndRelations, themeStyle: getThemeStyle(), - widgetMode + widgetMode, + mapType }); graph.graphData(notesAndRelations); }); diff --git a/apps/client/src/widgets/note_map/rendering.ts b/apps/client/src/widgets/note_map/rendering.ts index 2cea9ea39..1f9fa89eb 100644 --- a/apps/client/src/widgets/note_map/rendering.ts +++ b/apps/client/src/widgets/note_map/rendering.ts @@ -1,7 +1,8 @@ import type ForceGraph from "force-graph"; import { Link, Node, NotesAndRelationsData } from "./data"; import { NodeObject } from "force-graph"; -import { getColorForNode, NoteMapWidgetMode } from "./utils"; +import { getColorForNode, MapType, NoteMapWidgetMode } from "./utils"; +import { escapeHtml } from "../../services/utils"; export interface CssData { fontFamily: string; @@ -16,9 +17,10 @@ interface RenderData { themeStyle: "light" | "dark"; widgetMode: NoteMapWidgetMode; notesAndRelations: NotesAndRelationsData; + mapType: MapType; } -export function setupRendering(graph: ForceGraph, { noteId, themeStyle, widgetMode, noteIdToSizeMap, notesAndRelations, cssData }: RenderData) { +export function setupRendering(graph: ForceGraph, { noteId, themeStyle, widgetMode, noteIdToSizeMap, notesAndRelations, cssData, mapType }: RenderData) { // variables for the hover effect. We have to save the neighbours of a hovered node in a set. Also we need to save the links as well as the hovered node itself const neighbours = new Set(); const highlightLinks = new Set(); @@ -57,6 +59,46 @@ export function setupRendering(graph: ForceGraph, { noteId, themeStyle, widgetMo ctx.fillText(title, x, y + Math.round(size * 1.5)); } + + function paintLink(link: Link, ctx: CanvasRenderingContext2D) { + if (zoomLevel < 5) { + return; + } + + ctx.font = `3px ${cssData.fontFamily}`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = cssData.mutedTextColor; + + const { source, target } = link; + if (typeof source !== "object" || typeof target !== "object") { + return; + } + + if (source.x && source.y && target.x && target.y) { + const x = (source.x + target.x) / 2; + const y = (source.y + target.y) / 2; + ctx.save(); + ctx.translate(x, y); + + const deltaY = source.y - target.y; + const deltaX = source.x - target.x; + + let angle = Math.atan2(deltaY, deltaX); + let moveY = 2; + + if (angle < -Math.PI / 2 || angle > Math.PI / 2) { + angle += Math.PI; + moveY = -2; + } + + ctx.rotate(angle); + ctx.fillText(link.name, 0, moveY); + } + + ctx.restore(); + } + // main code for highlighting hovered nodes and neighbours. here we "style" the nodes. the nodes are rendered several hundred times per second. graph .d3AlphaDecay(0.01) @@ -92,5 +134,13 @@ export function setupRendering(graph: ForceGraph, { noteId, themeStyle, widgetMo highlightLinks.clear(); }) .onZoom((zoom) => zoomLevel = zoom.k); + + // Link-specific config + if (mapType) { + graph + .linkLabel((l) => `${escapeHtml((l as Link).source.name)} - ${escapeHtml((l as Link).name)} - ${escapeHtml((l as Link).target.name)}`) + .linkCanvasObject((link, ctx) => paintLink(link as Link, ctx)) + .linkCanvasObjectMode(() => "after"); + } } diff --git a/apps/client/src/widgets/note_map/utils.ts b/apps/client/src/widgets/note_map/utils.ts index 1a902cdb8..b6829069b 100644 --- a/apps/client/src/widgets/note_map/utils.ts +++ b/apps/client/src/widgets/note_map/utils.ts @@ -4,6 +4,7 @@ import hoisted_note from "../../services/hoisted_note"; import { Node } from "./data"; export type NoteMapWidgetMode = "ribbon" | "hoisted"; +export type MapType = "tree" | "link"; export function rgb2hex(rgb: string) { return `#${(rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) || [])