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.
);
}