Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 97 additions & 17 deletions apps/roam/src/components/canvas/Tldraw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export const MAX_WIDTH = "400px";

const ICON_URL = `data:image/svg+xml;utf8,${encodeURIComponent(WHITE_LOGO_SVG)}`;

const PAGE_REF_REGEX = /^\[\[(.+?)\]\]$/;

/** Valid file size for asset props; undefined when unknown (e.g. Roam/file API not a real File) to avoid persisting null. */
const getValidFileSize = (file: { size?: number }): number | undefined =>
typeof file.size === "number" && Number.isFinite(file.size) && file.size > 0
Expand Down Expand Up @@ -641,15 +643,63 @@ const TldrawCanvasShared = ({

// Handle Roam block drag and drop
useEffect(() => {
const temporaryDraggableAttribute = "data-roamjs-canvas-page-ref-draggable";
let activePageRef: HTMLElement | null = null;
const isPageRefDragSource = (pageRef: HTMLElement): boolean =>
!!pageRef.closest(
".roamjs-query-results-view, .roamjs-discourse-result-panel, .roamjs-discourse-context-overlay-container, .rm-query, .rm-query-content",
);
const clearActivePageRef = () => {
if (activePageRef?.hasAttribute(temporaryDraggableAttribute)) {
activePageRef.draggable = false;
activePageRef.removeAttribute(temporaryDraggableAttribute);
}
activePageRef = null;
};
const handlePointerDown = (e: PointerEvent) => {
if (e.defaultPrevented || e.button !== 0) return;
const target = e.target as HTMLElement;
const pageRef = target.closest<HTMLElement>(".rm-page-ref");
if (!pageRef || pageRef.draggable || !isPageRefDragSource(pageRef))
return;

activePageRef = pageRef;
pageRef.draggable = true;
pageRef.setAttribute(temporaryDraggableAttribute, "true");
};
Comment thread
sid597 marked this conversation as resolved.
const handleDragStart = (e: DragEvent) => {
const target = e.target as HTMLElement;
const uid = getBlockUidFromBullet(target);

const pageRef = target.closest<HTMLElement>(".rm-page-ref");
if (pageRef && isPageRefDragSource(pageRef)) {
const pageTitle = (
pageRef.getAttribute("data-tag") ||
pageRef.getAttribute("data-link-title") ||
pageRef.parentElement?.getAttribute("data-link-title")
)?.replace(/\\"/g, '"');
if (pageTitle) {
e.dataTransfer?.setData("application/x-roam-page", pageTitle);
return;
}
}
Comment thread
sid597 marked this conversation as resolved.

const uid = getBlockUidFromBullet(target);
if (uid) e.dataTransfer?.setData("application/x-roam-uid", uid);
};

document.addEventListener("pointerdown", handlePointerDown);
document.addEventListener("pointerup", clearActivePageRef);
document.addEventListener("pointercancel", clearActivePageRef);
document.addEventListener("dragstart", handleDragStart);
return () => document.removeEventListener("dragstart", handleDragStart);
document.addEventListener("dragend", clearActivePageRef);
return () => {
clearActivePageRef();
document.removeEventListener("pointerdown", handlePointerDown);
document.removeEventListener("pointerup", clearActivePageRef);
document.removeEventListener("pointercancel", clearActivePageRef);
document.removeEventListener("dragstart", handleDragStart);
document.removeEventListener("dragend", clearActivePageRef);
};
}, []);

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
Expand All @@ -658,6 +708,22 @@ const TldrawCanvasShared = ({

const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
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;
Expand Down Expand Up @@ -1267,30 +1333,36 @@ const InsideEditorAndUiContext = ({
try {
const text = content.text ?? "";

// Check for page reference: [[pageName]]
const pageMatch = text.match(/^\[\[(.+?)\]\]$/);
if (pageMatch?.[1]) {
const pageName = pageMatch[1];
const pageUid = getPageUidByPageTitle(pageName);
if (!pageUid) return await callDefaultTextHandler(content);

const tryCreatePageNodeShape = async (
title: string,
): Promise<boolean> => {
const pageUid = getPageUidByPageTitle(title);
if (!pageUid) return false;
const nodeType = findDiscourseNode({
uid: pageUid,
title: pageName,
title,
nodes: allNodes,
});
if (!nodeType) return await callDefaultTextHandler(content);

if (!nodeType) return false;
await createDiscourseNodeShape({
uid: pageUid,
nodeText: pageName,
nodeText: title,
nodeType: nodeType.type,
content,
});
posthog.capture("Canvas: Node Added from External Content", {
source: "page-reference",
});
return;
return true;
};

// Check for page reference: [[pageName]]
const pageMatch = text.match(PAGE_REF_REGEX);
if (pageMatch?.[1]) {
if (await tryCreatePageNodeShape(pageMatch[1])) {
posthog.capture("Canvas: Node Added from External Content", {
source: "page-reference",
});
return;
}
return await callDefaultTextHandler(content);
}

// Check for block reference: ((uid))
Expand All @@ -1303,6 +1375,14 @@ const InsideEditorAndUiContext = ({
if (!blockText || !isLive)
return await callDefaultTextHandler(content);

const refMatch = blockText.match(PAGE_REF_REGEX);
if (refMatch?.[1] && (await tryCreatePageNodeShape(refMatch[1]))) {
posthog.capture("Canvas: Node Added from External Content", {
source: "block-page-reference",
});
return;
}

await createDiscourseNodeShape({
uid,
nodeText: blockText,
Expand Down