diff --git a/packages/editor/App.tsx b/packages/editor/App.tsx index 676a07632..6db778ecf 100644 --- a/packages/editor/App.tsx +++ b/packages/editor/App.tsx @@ -506,9 +506,19 @@ const App: React.FC = () => { if (vaultBrowser.activeFile && vaultPath) { linkedDocHook.open(docPath, buildVaultDocUrl(vaultPath)); } else { - linkedDocHook.open(docPath); + // Pass the current file's directory as base for relative path resolution + const baseDir = linkedDocHook.filepath + ? linkedDocHook.filepath.replace(/\/[^/]+$/, '') + : imageBaseDir; + if (baseDir) { + linkedDocHook.open(docPath, (path) => + `/api/doc?path=${encodeURIComponent(path)}&base=${encodeURIComponent(baseDir)}` + ); + } else { + linkedDocHook.open(docPath); + } } - }, [vaultBrowser.activeFile, vaultPath, linkedDocHook, buildVaultDocUrl]); + }, [vaultBrowser.activeFile, vaultPath, linkedDocHook, buildVaultDocUrl, imageBaseDir]); // Wrap linked doc back to also clear vault active file const handleLinkedDocBack = React.useCallback(() => { diff --git a/packages/server/annotate.ts b/packages/server/annotate.ts index 28a380e56..286c3817d 100644 --- a/packages/server/annotate.ts +++ b/packages/server/annotate.ts @@ -14,7 +14,9 @@ import { isRemoteSession, getServerPort } from "./remote"; import { getRepoInfo } from "./repo"; import { handleImage, handleUpload, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete } from "./shared-handlers"; +import { handleDoc } from "./reference-handlers"; import { contentHash, deleteDraft } from "./draft"; +import { dirname } from "path"; // Re-export utilities export { isRemoteSession, getServerPort } from "./remote"; @@ -130,6 +132,17 @@ export async function startAnnotateServer( return handleImage(req); } + // API: Serve a linked markdown document + // Inject source file's directory as base for relative path resolution + if (url.pathname === "/api/doc" && req.method === "GET") { + if (!url.searchParams.has("base")) { + const docUrl = new URL(req.url); + docUrl.searchParams.set("base", dirname(filePath)); + return handleDoc(new Request(docUrl.toString())); + } + return handleDoc(req); + } + // API: Upload image -> save to temp -> return path if (url.pathname === "/api/upload" && req.method === "POST") { return handleUpload(req); diff --git a/packages/server/reference-handlers.ts b/packages/server/reference-handlers.ts index f8e4fa8c1..e2bdd2442 100644 --- a/packages/server/reference-handlers.ts +++ b/packages/server/reference-handlers.ts @@ -73,6 +73,20 @@ export async function handleDoc(req: Request): Promise { return Response.json({ error: "Missing path parameter" }, { status: 400 }); } + // If a base directory is provided, try resolving relative to it first + // (used by annotate mode to resolve paths relative to the source file) + const base = url.searchParams.get("base"); + if (base && !requestedPath.startsWith("/") && /\.mdx?$/i.test(requestedPath)) { + const fromBase = resolve(base, requestedPath); + try { + const file = Bun.file(fromBase); + if (await file.exists()) { + const markdown = await file.text(); + return Response.json({ markdown, filepath: fromBase }); + } + } catch { /* fall through to standard resolution */ } + } + const projectRoot = process.cwd(); const result = await resolveMarkdownFile(requestedPath, projectRoot);