/** * The 9 built-in file viewer descriptors: every preview surface is a * registered viewer (image / pdf / docx / xlsx / pptx / markdown / html / * code / binary-download), exactly like external plugins register theirs. * The `binary-download` viewer sniffs NUL bytes via `detect` for unknown * binaries and serves doc/xls/ppt by extension; `code` is the catch-all * (`exts: []`, lowest priority) that claims any file no other viewer did. * * The heavy viewers (docx/xlsx/pptx and the CodeMirror-backed * markdown/html/code) render through {@link lazyChunkComponent} wrappers — * their libraries are fetched only when such a file is first opened (see * chunk-loader.ts). The descriptor metadata (id/exts/priority/detect) is * identical either way, so matching semantics and external-plugin * overrides are unaffected; the `component` wrapper keeps the descriptor * contract `(props) => ReactNode`. * * Every viewer carries the declarative settings-surface fields — `title` * and `icon` — so the Side card settings page can render the enable/disable * inventory without hardcoding (eating our own dogfood). */ import { IconCodeOutline16, IconDownloadOutline16 } from '@deepseek-ai/dsh-client-ui-primitives' import { lazyChunkComponent } from '../lazy-chunk.tsx' import { PdfView } from '../PdfView.tsx' import { BinaryDownload } from '../binary-download.tsx' import { IconImageOutline16, IconMarkdownOutline16, IconPdfOutline16, IconDocxOutline16, IconXlsxOutline16, IconPptxOutline16, IconHtmlOutline16, } from '../icons.tsx' import type { ComponentType } from 'react' import type { FileViewerDescriptor, FileViewerProps } from '../service.ts' import { t } from '../locales.ts' import css from '../sidebar.module.css' /** * Lazy wrappers over the chunk-resident viewer components. The `pick` * functions are module-level (stable identity — the wrapper effect depends * on them); the cast bridges the chunk exports record to the descriptor * prop shape (the views read only their own subset of FileViewerProps). */ const LazyDocx = lazyChunkComponent('docx', (mod) => mod.DocxView as ComponentType | undefined) const LazyXlsx = lazyChunkComponent('xlsx', (mod) => mod.XlsxView as ComponentType | undefined) const LazyPptx = lazyChunkComponent('pptx', (mod) => mod.PptxView as ComponentType | undefined) const LazyTextEditor = lazyChunkComponent('editor', (mod) => mod.TextEditor as ComponentType | undefined) /** The 9 built-in file viewer descriptors. */ export function builtinViewers(): readonly FileViewerDescriptor[] { return [ { id: 'image', title: () => t('viewerImage'), icon: (size: number) => , exts: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif'], fetchStrategy: 'mediaUrl', component: ({ mediaUrl: url, title }) => (
{title}
), }, { id: 'pdf', title: () => t('viewerPdf'), icon: (size: number) => , exts: ['pdf'], fetchStrategy: 'mediaUrl', component: ({ scope, path, title }) => ( ), }, { id: 'docx', title: () => t('viewerDocx'), icon: (size: number) => , exts: ['docx'], fetchStrategy: 'mediaUrl', component: (props) => , }, { id: 'xlsx', title: () => t('viewerXlsx'), icon: (size: number) => , exts: ['xlsx'], fetchStrategy: 'mediaUrl', component: (props) => , }, { id: 'pptx', title: () => t('viewerPptx'), icon: (size: number) => , exts: ['pptx'], fetchStrategy: 'mediaUrl', component: (props) => , }, { id: 'markdown', title: () => t('viewerMarkdown'), icon: (size: number) => , exts: ['md', 'markdown'], fetchStrategy: 'fsRead', component: (props) => , }, { id: 'html', title: () => t('viewerHtml'), icon: (size: number) => , exts: ['html', 'htm'], fetchStrategy: 'fsRead', // Declarative settings: the sandbox escape hatch and the default-unsafe // start state render under this viewer's row in the Side card settings // page (both warned on). settings: { toggles: [{ key: 'htmlViewerNoSandbox', title: () => t('settingsHtmlSandboxTitle'), desc: () => t('settingsHtmlSandboxDesc'), }, { key: 'htmlViewerDefaultUnsafe', title: () => t('settingsHtmlDefaultUnsafeTitle'), desc: () => t('settingsHtmlDefaultUnsafeDesc'), }], }, component: (props) => , }, { id: 'code', title: () => t('viewerCode'), icon: (size: number) => , exts: [], priority: -100, fetchStrategy: 'fsRead', component: (props) => , }, { id: 'binary-download', title: () => t('viewerBinary'), icon: (size: number) => , exts: ['doc', 'xls', 'ppt'], priority: -50, fetchStrategy: 'binary-download', // NUL probe: a file whose head bytes contain a NUL is binary — claimed // before the catch-all code viewer on the head re-match. detect: (_path, head) => head.includes(0), component: ({ scope, path }) => , }, ] }