From 014d57e942273b12a8ceb4bda49bf1ad7a6a0569 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 25 May 2026 12:08:11 +0530 Subject: [PATCH 1/2] ENG-1373: Drag pages onto the canvas to create node shapes Page refs aren't draggable by default, so make them draggable via the existing rm-page-ref observer and route the drag payload through the canvas text-content handler that already turns [[name]] into a discourse-node shape. - Always-on: only the draggable attribute is global; the drag payload and drop handling live in the canvas component, so drags are inert unless a canvas is open. - Custom MIME (application/x-roam-page), not text/plain, so a drag can't leak [[title]] into other blocks or apps. - Guard the existing block-dragstart so dragging a ref doesn't also attach its containing block's UID. --- apps/roam/src/components/canvas/Tldraw.tsx | 29 ++++++++++++++++++- .../utils/initializeObserversAndListeners.ts | 3 ++ .../roam/src/utils/pageRefObserverHandlers.ts | 4 +++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index eb102d5f1..265ff38be 100644 --- a/apps/roam/src/components/canvas/Tldraw.tsx +++ b/apps/roam/src/components/canvas/Tldraw.tsx @@ -643,8 +643,19 @@ const TldrawCanvasShared = ({ useEffect(() => { const handleDragStart = (e: DragEvent) => { const target = e.target as HTMLElement; - const uid = getBlockUidFromBullet(target); + const pageRef = target.closest(".rm-page-ref"); + if (pageRef) { + const pageTitle = ( + pageRef.getAttribute("data-tag") || + pageRef.parentElement?.getAttribute("data-link-title") + )?.replace(/\\"/g, '"'); + if (pageTitle) + e.dataTransfer?.setData("application/x-roam-page", pageTitle); + return; + } + + const uid = getBlockUidFromBullet(target); if (uid) e.dataTransfer?.setData("application/x-roam-uid", uid); }; @@ -658,6 +669,22 @@ const TldrawCanvasShared = ({ const handleDrop = (e: React.DragEvent) => { e.preventDefault(); + + const pageTitle = e.dataTransfer.getData("application/x-roam-page"); + if (pageTitle && appRef.current && extensionAPI) { + posthog.capture("Canvas: Roam Page Dropped"); + const dropPoint = appRef.current.screenToPage({ + x: e.clientX, + y: e.clientY, + }); + void appRef.current.putExternalContent({ + type: "text", + text: `[[${pageTitle}]]`, + point: dropPoint, + }); + return; + } + const uid = e.dataTransfer.getData("application/x-roam-uid"); if (!uid || !appRef.current || !extensionAPI) return; diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index 227dcd34a..f5ae8b368 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -21,6 +21,7 @@ import { addPageRefObserver, getPageRefObserversSize, previewPageRefHandler, + makePageRefDraggable, getOverlayHandler, onPageRefObserverChange, getSuggestiveOverlayHandler, @@ -242,6 +243,8 @@ export const initObservers = ({ onPageRefObserverChange(overlayHandler)(true); } + onPageRefObserverChange(makePageRefDraggable)(true); + if (getPageRefObserversSize()) enablePageRefObserver(); const configPageUid = getPageUidByPageTitle(DISCOURSE_CONFIG_PAGE_TITLE); diff --git a/apps/roam/src/utils/pageRefObserverHandlers.ts b/apps/roam/src/utils/pageRefObserverHandlers.ts index cac2d362b..b9342c893 100644 --- a/apps/roam/src/utils/pageRefObserverHandlers.ts +++ b/apps/roam/src/utils/pageRefObserverHandlers.ts @@ -181,6 +181,10 @@ export const previewPageRefHandler = (s: HTMLSpanElement) => { } }; +export const makePageRefDraggable = (s: HTMLSpanElement) => { + s.draggable = true; +}; + export const enablePageRefObserver = () => { if (pageRefObserverRef.current) return pageRefObserverRef.current; From c6e8e9177918a9bea2dd860d2d9a1187ec36f99a Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 25 May 2026 15:08:59 +0530 Subject: [PATCH 2/2] ENG-1373: Unregister page-ref draggable handler on unload The always-on registration had no matching teardown, so the singleton rm-page-ref observer and the makePageRefDraggable callback survived extension unload/reload. Add a cleanup that unregisters the handler; for users without the overlay/preview/suggestive handlers this empties the set and disconnects the observer. --- apps/roam/src/utils/initializeObserversAndListeners.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index f5ae8b368..8663f983d 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -469,6 +469,11 @@ export const initObservers = ({ discourseNodeSearchTriggerListener, nodeCreationPopoverListener, }, - cleanups: [unsubGlobalTrigger, unsubPersonalTrigger, unsubSearchTrigger], + cleanups: [ + unsubGlobalTrigger, + unsubPersonalTrigger, + unsubSearchTrigger, + () => onPageRefObserverChange(makePageRefDraggable)(false), + ], }; };