From c165363a00f089fdd625321a7f18aed0772a410c Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 28 Oct 2025 21:02:57 -0400 Subject: [PATCH 1/2] new ways to open file --- .../components/canvas/TldrawViewComponent.tsx | 22 ++-- .../canvas/shapes/DiscourseNodeShape.tsx | 104 +++++++++++++++++- .../components/canvas/utils/openFileUtils.ts | 25 +++++ 3 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 apps/obsidian/src/components/canvas/utils/openFileUtils.ts diff --git a/apps/obsidian/src/components/canvas/TldrawViewComponent.tsx b/apps/obsidian/src/components/canvas/TldrawViewComponent.tsx index 2e9b31272..de792c618 100644 --- a/apps/obsidian/src/components/canvas/TldrawViewComponent.tsx +++ b/apps/obsidian/src/components/canvas/TldrawViewComponent.tsx @@ -51,6 +51,7 @@ import { RelationsOverlay } from "./overlays/RelationOverlay"; import { showToast } from "./utils/toastUtils"; import { WHITE_LOGO_SVG } from "~/icons"; import { CustomContextMenu } from "./CustomContextMenu"; +import { openFileInSidebar, openFileInNewTab } from "./utils/openFileUtils"; type TldrawPreviewProps = { store: TLStore; @@ -235,8 +236,10 @@ export const TldrawPreviewComponent = ({ isCreatingRelationRef.current = false; } - if (e.shiftKey) { + // Handle Shift+Click (open in sidebar) or Cmd+Click (open in new tab) + if (e.shiftKey || e.metaKey) { const now = Date.now(); + const openInNewTab = e.metaKey; // Cmd on Mac, Ctrl on other platforms // Debounce to prevent double opening if (now - lastShiftClickRef.current < SHIFT_CLICK_DEBOUNCE_MS) { @@ -295,21 +298,12 @@ export const TldrawPreviewComponent = ({ return; } - const rightSplit = plugin.app.workspace.rightSplit; - const rightLeaf = plugin.app.workspace.getRightLeaf(false); - - if (rightLeaf) { - if (rightSplit && rightSplit.collapsed) { - rightSplit.expand(); - } - await rightLeaf.openFile(linkedFile); - plugin.app.workspace.setActiveLeaf(rightLeaf); + // Open in sidebar (Shift+Click) or new tab (Cmd+Click) + if (openInNewTab) { + await openFileInNewTab(plugin.app, linkedFile); } else { - const leaf = plugin.app.workspace.getLeaf("split", "vertical"); - await leaf.openFile(linkedFile); - plugin.app.workspace.setActiveLeaf(leaf); + await openFileInSidebar(plugin.app, linkedFile); } - editor.selectNone(); }) .catch((error) => { diff --git a/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx b/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx index bd9ebfb8d..4b2ae5937 100644 --- a/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx +++ b/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx @@ -6,6 +6,7 @@ import { TLBaseShape, TLResizeInfo, useEditor, + useValue, } from "tldraw"; import type { App, TFile } from "obsidian"; import { memo, createElement, useEffect } from "react"; @@ -18,6 +19,8 @@ import { import { resolveLinkedFileFromSrc } from "~/components/canvas/stores/assetStore"; import { getNodeTypeById } from "~/utils/typeUtils"; import { calcDiscourseNodeSize } from "~/utils/calcDiscourseNodeSize"; +import { openFileInSidebar } from "~/components/canvas/utils/openFileUtils"; +import { showToast } from "~/components/canvas/utils/toastUtils"; export type DiscourseNodeShape = TLBaseShape< "discourse-node", @@ -140,6 +143,14 @@ const discourseNodeContent = memo( const { src, title, nodeTypeId } = shape.props; const nodeType = getNodeTypeById(plugin, nodeTypeId); + const isHovered = useValue( + "is hovered", + () => { + return editor.getHoveredShapeId() === shape.id; + }, + [editor, shape.id], + ); + useEffect(() => { const loadNodeData = async () => { if (!src) { @@ -255,17 +266,104 @@ const discourseNodeContent = memo( nodeType?.keyImage, ]); + const handleOpenInSidebar = async () => { + if (!src) { + showToast({ + severity: "warning", + title: "Cannot open node", + description: "No source file linked", + }); + return; + } + + const canvasFileCache = app.metadataCache.getFileCache(canvasFile); + if (!canvasFileCache) { + showToast({ + severity: "error", + title: "Error", + description: "Could not read canvas file", + }); + return; + } + + try { + const linkedFile = await resolveLinkedFileFromSrc({ + app, + canvasFile, + src, + }); + + if (!linkedFile) { + showToast({ + severity: "warning", + title: "Cannot open node", + description: "Linked file not found", + }); + return; + } + + await openFileInSidebar(app, linkedFile); + editor.selectNone(); + } catch (error) { + console.error("Error opening linked file:", error); + showToast({ + severity: "error", + title: "Error", + description: "Failed to open linked file", + }); + } + }; + return (
-

{title || "..."}

+ {isHovered && ( + + )} +

{title || "..."}

{nodeType?.name || ""}

{shape.props.imageSrc ? (
diff --git a/apps/obsidian/src/components/canvas/utils/openFileUtils.ts b/apps/obsidian/src/components/canvas/utils/openFileUtils.ts new file mode 100644 index 000000000..8cd43a12c --- /dev/null +++ b/apps/obsidian/src/components/canvas/utils/openFileUtils.ts @@ -0,0 +1,25 @@ +import { App, TFile } from "obsidian"; + +export const openFileInSidebar = async (app: App, file: TFile): Promise => { + const rightSplit = app.workspace.rightSplit; + const rightLeaf = app.workspace.getRightLeaf(false); + + if (rightLeaf) { + if (rightSplit && rightSplit.collapsed) { + rightSplit.expand(); + } + await rightLeaf.openFile(file); + app.workspace.setActiveLeaf(rightLeaf); + } else { + const leaf = app.workspace.getLeaf("split", "vertical"); + await leaf.openFile(file); + app.workspace.setActiveLeaf(leaf); + } +}; + +export const openFileInNewTab = async (app: App, file: TFile): Promise => { + const leaf = app.workspace.getLeaf("tab"); + await leaf.openFile(file); + app.workspace.setActiveLeaf(leaf); +}; + From 3f65cb2f4dc7adce2cd1f43339811cca911b58f1 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 28 Oct 2025 21:13:27 -0400 Subject: [PATCH 2/2] sm fix --- .../canvas/shapes/DiscourseNodeShape.tsx | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx b/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx index 4b2ae5937..2a76193d4 100644 --- a/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx +++ b/apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx @@ -266,7 +266,7 @@ const discourseNodeContent = memo( nodeType?.keyImage, ]); - const handleOpenInSidebar = async () => { + const handleOpenInSidebar = async (): Promise => { if (!src) { showToast({ severity: "warning", @@ -275,17 +275,6 @@ const discourseNodeContent = memo( }); return; } - - const canvasFileCache = app.metadataCache.getFileCache(canvasFile); - if (!canvasFileCache) { - showToast({ - severity: "error", - title: "Error", - description: "Could not read canvas file", - }); - return; - } - try { const linkedFile = await resolveLinkedFileFromSrc({ app, @@ -337,13 +326,9 @@ const discourseNodeContent = memo( onPointerUp={(e) => { e.stopPropagation(); }} - className="absolute left-1 top-1 z-10 flex items-center justify-center rounded bg-white/90 p-1 shadow-sm transition-all duration-200 hover:bg-white" + className="absolute left-1 top-1 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded border border-black/10 bg-white/90 p-1 shadow-sm transition-all duration-200 hover:bg-white" style={{ - width: "24px", - height: "24px", - border: "1px solid rgba(0, 0, 0, 0.1)", pointerEvents: "auto", - cursor: "pointer", }} title="Open in sidebar" >