66 TLBaseShape ,
77 TLResizeInfo ,
88 useEditor ,
9+ useValue ,
910} from "tldraw" ;
1011import type { App , TFile } from "obsidian" ;
1112import { memo , createElement , useEffect } from "react" ;
@@ -18,6 +19,8 @@ import {
1819import { resolveLinkedFileFromSrc } from "~/components/canvas/stores/assetStore" ;
1920import { getNodeTypeById } from "~/utils/typeUtils" ;
2021import { calcDiscourseNodeSize } from "~/utils/calcDiscourseNodeSize" ;
22+ import { openFileInSidebar } from "~/components/canvas/utils/openFileUtils" ;
23+ import { showToast } from "~/components/canvas/utils/toastUtils" ;
2124
2225export type DiscourseNodeShape = TLBaseShape <
2326 "discourse-node" ,
@@ -140,6 +143,14 @@ const discourseNodeContent = memo(
140143 const { src, title, nodeTypeId } = shape . props ;
141144 const nodeType = getNodeTypeById ( plugin , nodeTypeId ) ;
142145
146+ const isHovered = useValue (
147+ "is hovered" ,
148+ ( ) => {
149+ return editor . getHoveredShapeId ( ) === shape . id ;
150+ } ,
151+ [ editor , shape . id ] ,
152+ ) ;
153+
143154 useEffect ( ( ) => {
144155 const loadNodeData = async ( ) => {
145156 if ( ! src ) {
@@ -255,17 +266,89 @@ const discourseNodeContent = memo(
255266 nodeType ?. keyImage ,
256267 ] ) ;
257268
269+ const handleOpenInSidebar = async ( ) : Promise < void > => {
270+ if ( ! src ) {
271+ showToast ( {
272+ severity : "warning" ,
273+ title : "Cannot open node" ,
274+ description : "No source file linked" ,
275+ } ) ;
276+ return ;
277+ }
278+ try {
279+ const linkedFile = await resolveLinkedFileFromSrc ( {
280+ app,
281+ canvasFile,
282+ src,
283+ } ) ;
284+
285+ if ( ! linkedFile ) {
286+ showToast ( {
287+ severity : "warning" ,
288+ title : "Cannot open node" ,
289+ description : "Linked file not found" ,
290+ } ) ;
291+ return ;
292+ }
293+
294+ await openFileInSidebar ( app , linkedFile ) ;
295+ editor . selectNone ( ) ;
296+ } catch ( error ) {
297+ console . error ( "Error opening linked file:" , error ) ;
298+ showToast ( {
299+ severity : "error" ,
300+ title : "Error" ,
301+ description : "Failed to open linked file" ,
302+ } ) ;
303+ }
304+ } ;
305+
258306 return (
259307 < div
260308 style = { {
261309 backgroundColor : nodeType ?. color ?? "" ,
262310 } }
263- // NOTE: These Tailwind classes (p-2, border-2, rounded-md, m-1, text-base, m-0, text-sm)
311+ // NOTE: These Tailwind classes (p-2, border-2, rounded-md, m-1, text-base, m-0, text-sm)
264312 // correspond to constants in nodeConstants.ts. If you change these classes, update the
265313 // constants and the measureNodeText function to keep measurements accurate.
266- className = "box-border flex h-full w-full flex-col items-start justify-start rounded-md border-2 p-2"
314+ className = "relative box-border flex h-full w-full flex-col items-start justify-center rounded-md border-2 p-2"
267315 >
268- < h1 className = "m-1 text-base" > { title || "..." } </ h1 >
316+ { isHovered && (
317+ < button
318+ onClick = { ( e ) => {
319+ e . stopPropagation ( ) ;
320+ void handleOpenInSidebar ( ) ;
321+ } }
322+ onPointerDown = { ( e ) => {
323+ e . stopPropagation ( ) ;
324+ e . preventDefault ( ) ;
325+ } }
326+ onPointerUp = { ( e ) => {
327+ e . stopPropagation ( ) ;
328+ } }
329+ 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"
330+ style = { {
331+ pointerEvents : "auto" ,
332+ } }
333+ title = "Open in sidebar"
334+ >
335+ < svg
336+ xmlns = "http://www.w3.org/2000/svg"
337+ width = "16"
338+ height = "16"
339+ viewBox = "0 0 24 24"
340+ fill = "none"
341+ stroke = "currentColor"
342+ strokeWidth = "2"
343+ strokeLinecap = "round"
344+ strokeLinejoin = "round"
345+ >
346+ < rect x = "3" y = "3" width = "18" height = "18" rx = "2" ry = "2" />
347+ < line x1 = "15" y1 = "3" x2 = "15" y2 = "21" />
348+ </ svg >
349+ </ button >
350+ ) }
351+ < h1 className = "m-0 text-base" > { title || "..." } </ h1 >
269352 < p className = "m-0 text-sm opacity-80" > { nodeType ?. name || "" } </ p >
270353 { shape . props . imageSrc ? (
271354 < div className = "mt-2 flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden" >
0 commit comments