import type { DocumentSlotState } from '../documents/useDocumentSlot'; import { resolveDocumentLink } from '../documents/resolveDocumentLink'; import type { OpenDocRequest } from '../workspaces/workspace'; import { useWikiLinkDocument } from '../wikilinks/WikiLinkVaultContext'; import { DocumentPane } from './DocumentPane'; import type { EditorMode, EditorViewSnapshot } from './types'; const DEFAULT_EMPTY_HINT = 'Select a file from the sidebar to open it here.'; type DocumentSlotViewProps = { state: DocumentSlotState; className?: string; placeholder?: string; emptyHint?: string; /** Rendering mode for the open editor; defaults to `edit`. */ mode?: EditorMode; /** * Open a document (by provider + entry). When provided, internal-link clicks * in the editor are resolved against the open document and routed here — so a * workspace gets file-to-file navigation just by forwarding its open handler. */ onOpenEntry?: (request: OpenDocRequest) => void; /** Position to restore — where this slot's tab was when it was last shown. */ viewSnapshot?: EditorViewSnapshot | null; /** Called as the editor tears down, with the position to restore next time. */ onViewSnapshot?: (snapshot: EditorViewSnapshot) => void; }; function SlotPlaceholder({ message }: { message: string }) { return (
{message}
); } /** * Renders a document slot's empty, loading, error, or open states. * Workspaces compose this inside their layout chrome. * * Both kinds of internal navigation attach here rather than in each workspace, * for the same reason: this is the one place that knows which document is open * *and* which provider it came from, which is exactly what resolving a link * target needs. Relative markdown links resolve through `resolveDocumentLink`; * `[[wikilinks]]` resolve vault-wide through `useWikiLinkDocument`. Every * workspace that renders a slot gets both without its own wiring. */ export function DocumentSlotView({ state, className, placeholder = 'Start writing…', emptyHint = DEFAULT_EMPTY_HINT, mode, onOpenEntry, viewSnapshot, onViewSnapshot, }: DocumentSlotViewProps) { // Hooks must run before the early returns below (rules of hooks). Only the // `empty` state lacks a request — and it has no document to link from. const slotRequest = state.kind === 'empty' ? null : state.request; const { vault, headings, openWikiLink } = useWikiLinkDocument( slotRequest?.providerId ?? null, slotRequest?.entryId ?? null, ); if (state.kind === 'empty') { return ; } if (state.kind === 'loading') { return ; } if (state.kind === 'error') { return ( ); } // Resolve internal-link targets against this document, then route to the // workspace's open handler. Kept here (not in each workspace) so every // workspace gets file-to-file navigation for free. // Resolve from `request.entryId`, NOT `handle.id`: the handle's id is the // provider's own document identity, which for a synced vault is an `at://` // URI rather than a path (`syncedVaultDocumentsProvider` sets `id: // session.uri`). `entryId` is always the absolute path in both providers. const { request, handle } = state; const onOpenLink = onOpenEntry ? (target: string) => onOpenEntry( resolveDocumentLink( { providerId: request.providerId, path: request.entryId }, target, ), ) : undefined; return ( // Key by document id so switching tabs to a different document remounts a // fresh editor instead of reusing one bound to the previous document. The // shared editor view binds its content on mount only, so without this a // tab switch can leave the previous file's content showing. ); }