From 19c9731ecb2a957b2bf0e73773791cb341781a00 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Sun, 15 Mar 2026 19:41:03 -0600 Subject: [PATCH] Refactor Discourse Node Handling and Introduce Node Type ID - Replaced the use of createNodeShapeUtils with DiscourseNodeUtil across components to streamline discourse node management. - Introduced DISCOURSE_NODE_SHAPE_TYPE constant for consistent node type identification. - Updated various components to utilize nodeTypeId for better clarity and functionality in shape creation and manipulation. - Enhanced migration logic to accommodate new node type structure, ensuring backward compatibility and improved data integrity. This refactor improves the maintainability and readability of the codebase while ensuring that new features can be integrated more seamlessly. --- apps/roam/src/components/Export.tsx | 8 +- apps/roam/src/components/canvas/Clipboard.tsx | 4 +- .../components/canvas/DiscourseNodeUtil.tsx | 84 +++++++++++-------- .../DiscourseRelationTool.tsx | 11 ++- .../DiscourseRelationUtil.tsx | 26 ++++-- .../discourseRelationMigrations.ts | 13 +++ .../components/canvas/DiscourseToolPanel.tsx | 14 +++- apps/roam/src/components/canvas/Tldraw.tsx | 19 ++--- .../src/components/canvas/uiOverrides.tsx | 4 +- .../canvas/useCanvasStoreAdapterArgs.ts | 5 +- .../src/utils/syncCanvasNodeTitlesOnLoad.ts | 18 ++-- 11 files changed, 131 insertions(+), 75 deletions(-) diff --git a/apps/roam/src/components/Export.tsx b/apps/roam/src/components/Export.tsx index 928e459e1..cbe721e9b 100644 --- a/apps/roam/src/components/Export.tsx +++ b/apps/roam/src/components/Export.tsx @@ -62,7 +62,8 @@ import { } from "@tldraw/editor"; import calcCanvasNodeSizeAndImg from "~/utils/calcCanvasNodeSizeAndImg"; import { - createNodeShapeUtils, + DiscourseNodeUtil, + DISCOURSE_NODE_SHAPE_TYPE, DiscourseNodeShape, } from "~/components/canvas/DiscourseNodeUtil"; import { discourseContext, MAX_WIDTH } from "~/components/canvas/Tldraw"; @@ -360,7 +361,7 @@ const ExportDialog: ExportDialogComponent = ({ ); // UTILS - const discourseNodeUtils = createNodeShapeUtils(allNodes); + const discourseNodeUtils = [DiscourseNodeUtil]; const discourseRelationUtils = createAllRelationShapeUtils(allRelationIds); const referencedNodeUtils = createAllReferencedNodeUtils( @@ -551,12 +552,13 @@ const ExportDialog: ExportDialogComponent = ({ index: currentIndex, rotation: 0, isLocked: false, - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, props: { w, h, uid: r.uid, title: String(r[firstColumnKey]), + nodeTypeId: nodeType, imageUrl, size: "s", fontFamily: "sans", diff --git a/apps/roam/src/components/canvas/Clipboard.tsx b/apps/roam/src/components/canvas/Clipboard.tsx index d5254313f..a28ff0a79 100644 --- a/apps/roam/src/components/canvas/Clipboard.tsx +++ b/apps/roam/src/components/canvas/Clipboard.tsx @@ -47,6 +47,7 @@ import isDiscourseNode from "~/utils/isDiscourseNode"; import { DiscourseNodeShape, DEFAULT_STYLE_PROPS, + DISCOURSE_NODE_SHAPE_TYPE, FONT_SIZES, } from "./DiscourseNodeUtil"; import { openBlockInSidebar, createBlock } from "roamjs-components/writes"; @@ -658,7 +659,7 @@ const ClipboardPageSection = ({ const shapeId = createShapeId(); const shape = { id: shapeId, - type: nodeType.type, + type: DISCOURSE_NODE_SHAPE_TYPE, x: pagePoint.x - w / 2, y: pagePoint.y - h / 2, props: { @@ -669,6 +670,7 @@ const ClipboardPageSection = ({ imageUrl, size: "s" as TLDefaultSizeStyle, fontFamily: "sans" as TLDefaultFontStyle, + nodeTypeId: nodeType.type, }, }; editor.createShape(shape); diff --git a/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx b/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx index fc2f1d742..6631c4b0d 100644 --- a/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx +++ b/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx @@ -4,7 +4,6 @@ import { TLBaseShape, useEditor, DefaultColorStyle, - Editor, createShapeId, TLDefaultHorizontalAlignStyle, TLDefaultVerticalAlignStyle, @@ -16,6 +15,7 @@ import { DefaultSizeStyle, T, FONT_FAMILIES, + TLShape, TLDefaultFontStyle, DefaultFontStyle, toDomPrecision, @@ -92,6 +92,8 @@ export const COLOR_PALETTE: Record = { }; /* eslint-disable @typescript-eslint/naming-convention */ +export const DISCOURSE_NODE_SHAPE_TYPE = "discourse-node"; + const getRelationIds = () => new Set( Object.values(discourseContext.relations).flatMap((rs) => @@ -106,7 +108,7 @@ export const createNodeShapeTools = ( return class DiscourseNodeTool extends StateNode { static id = n.type; static initial = "idle"; - shapeType = n.type; + nodeTypeId = n.type; override onEnter = () => { this.editor.setCursor({ @@ -120,10 +122,14 @@ export const createNodeShapeTools = ( const shapeId = createShapeId(); this.editor.createShape({ id: shapeId, - type: this.shapeType, + type: DISCOURSE_NODE_SHAPE_TYPE, x: currentPagePoint.x, y: currentPagePoint.y, - props: { fontFamily: "sans", size: "s" }, + props: { + fontFamily: "sans", + size: "s", + nodeTypeId: this.nodeTypeId, + }, }); this.editor.setEditingShape(shapeId); this.editor.setCurrentTool("select"); @@ -132,23 +138,24 @@ export const createNodeShapeTools = ( }); }; -export const createNodeShapeUtils = (nodes: DiscourseNode[]) => { - return nodes.map((node) => { - class DiscourseNodeUtil extends BaseDiscourseNodeUtil { - constructor(editor: Editor) { - super(editor, node.type); - } - static override type = node.type; // removing this gives undefined error - // getDefaultProps(): DiscourseNodeShape["props"] { - // const baseProps = super.getDefaultProps(); - // return { - // ...baseProps, - // color: node.color, - // }; - // } - } - return DiscourseNodeUtil; - }); +type ShapeWithOptionalNodeTypeId = TLShape & { + props?: { + nodeTypeId?: string; + }; +}; + +export const getDiscourseNodeTypeId = ({ + shape, +}: { + shape: ShapeWithOptionalNodeTypeId; +}): string => { + return shape.props?.nodeTypeId || shape.type; +}; + +export const isDiscourseNodeShape = ( + shape: TLShape, +): shape is DiscourseNodeShape => { + return shape.type === DISCOURSE_NODE_SHAPE_TYPE; }; export type DiscourseNodeShape = TLBaseShape< @@ -159,18 +166,14 @@ export type DiscourseNodeShape = TLBaseShape< // opacity: TLOpacityType; uid: string; title: string; + nodeTypeId: string; imageUrl?: string; size: TLDefaultSizeStyle; fontFamily: TLDefaultFontStyle; } >; -export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil { - type: string; - - constructor(editor: Editor, type: string) { - super(editor); - this.type = type; - } +export class DiscourseNodeUtil extends BaseBoxShapeUtil { + static override type = DISCOURSE_NODE_SHAPE_TYPE; static override props = { w: T.number, @@ -178,6 +181,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil // opacity: T.number, uid: T.string, title: T.string, + nodeTypeId: T.string, imageUrl: T.optional(T.string), size: DefaultSizeStyle, fontFamily: DefaultFontStyle, @@ -194,6 +198,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil h: 64, uid: window.roamAlphaAPI.util.generateUID(), title: "", + nodeTypeId: "", size: "s", fontFamily: "sans", }; @@ -239,7 +244,11 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil const nodesInCanvas = Object.fromEntries( allRecords .filter((r): r is DiscourseNodeShape => { - return r.typeName === "shape" && nodeIds.has(r.type); + if (r.typeName !== "shape") return false; + const nodeTypeId = getDiscourseNodeTypeId({ shape: r }); + return ( + r.typeName === "shape" && !!nodeTypeId && nodeIds.has(nodeTypeId) + ); }) .map((r) => [r.props.uid, r] as const), ); @@ -330,12 +339,14 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil editor.createShapes(shapesToCreate).createBindings(bindingsToCreate); } - getColors() { - return getDiscourseNodeColors({ nodeType: this.type }); + getColors(shape: DiscourseNodeShape) { + return getDiscourseNodeColors({ + nodeType: getDiscourseNodeTypeId({ shape }), + }); } async toSvg(shape: DiscourseNodeShape): Promise { - const { backgroundColor, textColor } = this.getColors(); + const { backgroundColor, textColor } = this.getColors(shape); const padding = Number(DEFAULT_STYLE_PROPS.padding.replace("px", "")); const props = shape.props; const bounds = new Box(0, 0, props.w, props.h); @@ -432,7 +443,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil const extensionAPI = useExtensionAPI(); const { canvasSettings: { alias = "", "key-image": isKeyImage = "" } = {}, - } = discourseContext.nodes[shape.type] || {}; + } = discourseContext.nodes[getDiscourseNodeTypeId({ shape })] || {}; // eslint-disable-next-line react-hooks/rules-of-hooks const isOverlayEnabled = useMemo( () => getSetting(DISCOURSE_CONTEXT_OVERLAY_IN_CANVAS_KEY, false), @@ -465,7 +476,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil } }, [setLoaded, loaded, contentRef, shape.props.uid]); - const { backgroundColor, textColor } = this.getColors(); + const { backgroundColor, textColor } = this.getColors(shape); // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { @@ -482,7 +493,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil const { h, w, imageUrl } = await calcCanvasNodeSizeAndImg({ nodeText: text, uid, - nodeType: this.type, + nodeType: getDiscourseNodeTypeId({ shape }), extensionAPI, }); this.updateProps(shape.id, shape.type, { h, w, imageUrl }); @@ -490,7 +501,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil renderModifyNodeDialog({ mode: isCreating ? "create" : "edit", - nodeType: shape.type, + nodeType: getDiscourseNodeTypeId({ shape }), initialValue: { text: shape.props.title, uid: shape.props.uid }, // Only pass it when editing an existing node that has a valid Roam block UID sourceBlockUid: @@ -514,6 +525,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil this.updateProps(shape.id, shape.type, { title: text, uid, + nodeTypeId: getDiscourseNodeTypeId({ shape }), }); const autoCanvasRelations = getSetting( diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx index 3e809406b..67c8726c7 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx @@ -11,6 +11,7 @@ import { } from "./DiscourseRelationUtil"; import { discourseContext } from "~/components/canvas/Tldraw"; import { dispatchToastEvent } from "~/components/canvas/ToastListener"; +import { getDiscourseNodeTypeId } from "~/components/canvas/DiscourseNodeUtil"; export type AddReferencedNodeType = Record; type ReferenceFormatType = { @@ -77,7 +78,10 @@ export const createAllReferencedNodeTools = ( const sourceType = allAddReferencedNodeByAction[action][0].sourceType; const sourceName = allAddReferencedNodeByAction[action][0].sourceName; - if (target?.type === sourceType) { + if ( + target && + getDiscourseNodeTypeId({ shape: target }) === sourceType + ) { this.shapeType = `${action}`; } else { this.cancelAndWarn(`Starting node must be one of ${sourceName}`); @@ -362,8 +366,11 @@ export const createAllRelationShapeTools = ( // } ); + const targetNodeTypeId = target + ? getDiscourseNodeTypeId({ shape: target }) + : undefined; const relation = discourseContext.relations[name].find( - (r) => r.source === target?.type, + (r) => r.source === targetNodeTypeId, ); if (relation) { this.shapeType = relation.id; diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx index 5e40a22ad..ad03d02f5 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx @@ -103,8 +103,10 @@ import createPage from "roamjs-components/writes/createPage"; import openBlockInSidebar from "roamjs-components/writes/openBlockInSidebar"; import triplesToBlocks from "~/utils/triplesToBlocks"; import { - BaseDiscourseNodeUtil, + DiscourseNodeUtil, DiscourseNodeShape, + getDiscourseNodeTypeId, + isDiscourseNodeShape as isDiscourseNodeShapeTypeGuard, } from "~/components/canvas/DiscourseNodeUtil"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import { AddReferencedNodeType } from "./DiscourseRelationTool"; @@ -146,8 +148,7 @@ export const createAllReferencedNodeUtils = ( static override type = action; isDiscourseNodeShape(shape: TLShape): shape is DiscourseNodeShape { - const shapeUtil = this.editor.getShapeUtil(shape.type); - return shapeUtil instanceof BaseDiscourseNodeUtil; + return isDiscourseNodeShapeTypeGuard(shape); } handleCreateRelationsInRoam = async ({ @@ -177,7 +178,11 @@ export const createAllReferencedNodeUtils = ( const possibleTargets = allAddReferencedNodeByAction[arrow.type].map( (action) => action.destinationType, ); - if (!possibleTargets.includes(target.type)) { + if ( + !possibleTargets.includes( + getDiscourseNodeTypeId({ shape: target }) || "", + ) + ) { return deleteAndWarn( `Target node must be of type ${possibleTargets .map((t) => discourseContext.nodes[t].text) @@ -587,7 +592,7 @@ const asDiscourseNodeShape = ( editor: Editor, ): DiscourseNodeShape | null => { const shapeUtil = editor.getShapeUtil(shape.type); - return shapeUtil instanceof BaseDiscourseNodeUtil + return shapeUtil instanceof DiscourseNodeUtil ? (shape as DiscourseNodeShape) : null; }; @@ -628,7 +633,11 @@ export const createAllRelationShapeUtils = ( .filter((r) => r.source === relation.source) .map((r) => r.destination); - if (!possibleTargets.includes(target.type)) { + if ( + !possibleTargets.includes( + getDiscourseNodeTypeId({ shape: target }) || "", + ) + ) { const uniqueTargets = [...new Set(possibleTargets)]; const uniqueTargetTexts = uniqueTargets.map( (t) => discourseContext.nodes[t].text, @@ -637,8 +646,9 @@ export const createAllRelationShapeUtils = ( `Target node must be of type ${uniqueTargetTexts.join(", ")}`, ); } - if (arrow.type !== target.type) { - editor.updateShapes([{ id: arrow.id, type: target.type }]); + const targetNodeTypeId = getDiscourseNodeTypeId({ shape: target }); + if (targetNodeTypeId && arrow.type !== targetNodeTypeId) { + editor.updateShapes([{ id: arrow.id, type: targetNodeTypeId }]); } if (getStoredRelationsEnabled()) { const sourceAsDNS = asDiscourseNodeShape(source, editor); diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts b/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts index 7c94474fc..72be02143 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -14,6 +15,7 @@ import { import { createMigrationIds } from "tldraw"; import { RelationBinding } from "./DiscourseRelationBindings"; import { getRelationColor } from "./DiscourseRelationUtil"; +import { DISCOURSE_NODE_SHAPE_TYPE } from "~/components/canvas/DiscourseNodeUtil"; const SEQUENCE_ID_BASE = "com.roam-research.discourse-graphs"; @@ -35,6 +37,7 @@ export const createMigrations = ({ "2.3.0": 2, AddSizeAndFontFamily: 3, RemoveNullAssetFileSize: 4, + MigrateNodeTypeToDiscourseNode: 5, }); return createMigrationSequence({ sequenceId: `${SEQUENCE_ID_BASE}`, @@ -156,6 +159,16 @@ export const createMigrations = ({ delete asset.props.fileSize; }, }, + { + id: versions["MigrateNodeTypeToDiscourseNode"], + scope: "record", + filter: (r: any) => + r.typeName === "shape" && allNodeTypes.includes(r.type), + up: (shape: any) => { + shape.props.nodeTypeId = shape.type; + shape.type = DISCOURSE_NODE_SHAPE_TYPE; + }, + }, ], }); }; diff --git a/apps/roam/src/components/canvas/DiscourseToolPanel.tsx b/apps/roam/src/components/canvas/DiscourseToolPanel.tsx index 5241973bb..240ae58b8 100644 --- a/apps/roam/src/components/canvas/DiscourseToolPanel.tsx +++ b/apps/roam/src/components/canvas/DiscourseToolPanel.tsx @@ -15,7 +15,11 @@ import { useAtom } from "@tldraw/state-react"; import { TOOL_ARROW_ICON_SVG, NODE_COLOR_ICON_SVG } from "~/icons"; import { getDiscourseNodeColors } from "~/utils/getDiscourseNodeColors"; import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from "./Tldraw"; -import { DEFAULT_STYLE_PROPS, FONT_SIZES } from "./DiscourseNodeUtil"; +import { + DEFAULT_STYLE_PROPS, + DISCOURSE_NODE_SHAPE_TYPE, + FONT_SIZES, +} from "./DiscourseNodeUtil"; export type DiscourseGraphPanelProps = { nodes: DiscourseNode[]; @@ -184,10 +188,14 @@ const DiscourseGraphPanel = ({ const shapeId = createShapeId(); editor.createShape({ id: shapeId, - type: current.item.id, + type: DISCOURSE_NODE_SHAPE_TYPE, x: pagePoint.x - offsetX, y: pagePoint.y - offsetY, - props: { fontFamily: "sans", size: "s" }, + props: { + fontFamily: "sans", + size: "s", + nodeTypeId: current.item.id, + }, }); editor.setEditingShape(shapeId); editor.setCurrentTool("select"); diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index e77e91dcd..538b51e88 100644 --- a/apps/roam/src/components/canvas/Tldraw.tsx +++ b/apps/roam/src/components/canvas/Tldraw.tsx @@ -38,7 +38,6 @@ import { TLAssetId, getHashForString, TLShapeId, - TLShape, TLStore, TLStoreWithStatus, useToasts, @@ -65,9 +64,11 @@ import { createUiOverrides, } from "./uiOverrides"; import { - BaseDiscourseNodeUtil, + DiscourseNodeUtil, DiscourseNodeShape, createNodeShapeTools, + DISCOURSE_NODE_SHAPE_TYPE, + isDiscourseNodeShape, } from "./DiscourseNodeUtil"; import { useRoamStore } from "./useRoamStore"; import { @@ -506,12 +507,6 @@ const TldrawCanvasShared = ({ ); }; - const isDiscourseNodeShape = ( - shape: TLShape, - ): shape is DiscourseNodeShape => { - return allNodes.some((node) => node.type === shape.type); - }; - // Add state for tracking relation creation const relationCreationRef = useRef<{ isCreating: boolean; @@ -807,13 +802,14 @@ const TldrawCanvasShared = ({ if (nodeType) { app.createShapes([ { - type: nodeType.type, + type: DISCOURSE_NODE_SHAPE_TYPE, id: createShapeId(), props: { uid: e.detail.uid, title: e.detail.val, size: "s", fontFamily: "sans", + nodeTypeId: nodeType.type, }, ...position, }, @@ -1161,7 +1157,7 @@ const InsideEditorAndUiContext = ({ editor.createShapes([ { id: createShapeId(), - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, x: position.x - w / 2, y: position.y - h / 2, props: { @@ -1172,6 +1168,7 @@ const InsideEditorAndUiContext = ({ ...(imageUrl && { imageUrl }), size: "s", fontFamily: "sans", + nodeTypeId: nodeType, }, }, ]); @@ -1439,7 +1436,7 @@ const InsideEditorAndUiContext = ({ const removeAfterCreateHandler = editor.sideEffects.registerAfterCreateHandler("shape", (shape) => { const util = editor.getShapeUtil(shape); - if (util instanceof BaseDiscourseNodeUtil) { + if (util instanceof DiscourseNodeUtil) { const autoCanvasRelations = getSetting( AUTO_CANVAS_RELATIONS_KEY, false, diff --git a/apps/roam/src/components/canvas/uiOverrides.tsx b/apps/roam/src/components/canvas/uiOverrides.tsx index 049c84edf..af7ad1466 100644 --- a/apps/roam/src/components/canvas/uiOverrides.tsx +++ b/apps/roam/src/components/canvas/uiOverrides.tsx @@ -57,6 +57,7 @@ import { CustomDefaultToolbar } from "./CustomDefaultToolbar"; import { renderModifyNodeDialog } from "~/components/ModifyNodeDialog"; import { CanvasSyncMode } from "./canvasSyncMode"; import posthog from "posthog-js"; +import { DISCOURSE_NODE_SHAPE_TYPE } from "./DiscourseNodeUtil"; const SyncModeMenuSwitchItem = ({ checked, @@ -178,7 +179,7 @@ export const getOnSelectForShape = ({ }); editor.createShapes([ { - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, id: createShapeId(), props: { uid, @@ -188,6 +189,7 @@ export const getOnSelectForShape = ({ imageUrl: nodeImageUrl, fontFamily: "sans", size: "s", + nodeTypeId: nodeType, }, x, y, diff --git a/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts b/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts index 4791d6f1d..045e29568 100644 --- a/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts +++ b/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts @@ -5,7 +5,7 @@ import { TLAnyShapeUtilConstructor, } from "tldraw"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; -import { createNodeShapeUtils } from "./DiscourseNodeUtil"; +import { DiscourseNodeUtil } from "./DiscourseNodeUtil"; import { createAllReferencedNodeUtils, createAllRelationShapeUtils, @@ -56,7 +56,6 @@ const getUtilTypes = ({ }; const createShapeUtils = ({ - allNodes, allRelationIds, allAddReferencedNodeByAction, }: { @@ -65,7 +64,7 @@ const createShapeUtils = ({ allAddReferencedNodeByAction: AddReferencedNodeType; }): TLAnyShapeUtilConstructor[] => { return [ - ...createNodeShapeUtils(allNodes), + DiscourseNodeUtil, ...createAllRelationShapeUtils(allRelationIds), ...createAllReferencedNodeUtils(allAddReferencedNodeByAction), ]; diff --git a/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts b/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts index acd74df5d..de4f94f3a 100644 --- a/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts +++ b/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts @@ -1,5 +1,8 @@ import type { Editor } from "tldraw"; -import type { DiscourseNodeShape } from "~/components/canvas/DiscourseNodeUtil"; +import { + DISCOURSE_NODE_SHAPE_TYPE, + type DiscourseNodeShape, +} from "~/components/canvas/DiscourseNodeUtil"; /** * Query Roam for current :node/title or :block/string for each uid. @@ -64,12 +67,13 @@ export const syncCanvasNodeTitlesOnLoad = async ( const nodeTypeSet = new Set(nodeTypeIds); const relationIds = new Set(relationShapeTypeIds); const allRecords = editor.store.allRecords(); - const discourseNodeShapes = allRecords.filter( - (r) => - r.typeName === "shape" && - nodeTypeSet.has((r as DiscourseNodeShape).type) && - typeof (r as DiscourseNodeShape).props?.uid === "string", - ) as DiscourseNodeShape[]; + const discourseNodeShapes = allRecords.filter((r) => { + if (r.typeName !== "shape") return false; + if (r.type !== DISCOURSE_NODE_SHAPE_TYPE) return false; + const shape = r as DiscourseNodeShape; + if (!nodeTypeSet.has(shape.props.nodeTypeId)) return false; + return typeof shape.props?.uid === "string"; + }) as DiscourseNodeShape[]; const uids = [...new Set(discourseNodeShapes.map((s) => s.props.uid))]; if (uids.length === 0) return;