File viewer: per-filetype rendering
At a glance
FileViewdispatches to one of 6 organisms bySchemaVariant— they're not individually exported, this page is their only documentation.RawViewis separate: exported on its own, plain props, no store access, not part of theFileViewswitch.- Pdf/Table/Text/Email use free-text selection (
useTextSelect); Xml/Json use click-driven reference selection instead. useJsonFilehas no error handling aroundJSON.parse— a malformed file throws uncaught.
FileView is the one rendering component file-viewer exports from its package root. It reads the current file's SchemaVariant from the store and switches (via ts-pattern's exhaustive match) to the matching organism — PdfView, TableView, XmlView, TextView, EmailView, or JsonView. Those six organisms are not individually exported from the package; FileView is the only supported entry point into them, so this page is where their per-filetype behavior is documented directly.
RawView is the exception — it is separately exported, and it's a different kind of component: a plain controlled contentEditable text view taking rawText/setRawText as props, with no store access of its own. It's not part of the FileView switch at all — something outside this package decides when to show a raw/editable view instead of the type-specific one.
The six organisms
Each organism pairs a presentational component with a use-* hook that loads and shapes that file type's content from the store's current File object (useStore(({ file: f }) => f.getFile())):
Pdf —
usePdfdrivesreact-pdf: trackspageNumber, extracts the current page's text (page.getTextContent()) whenever it changes, and exposespreviousPage/nextPage.PdfViewlayers highlight<mark>spans ontoreact-pdf'scustomTextRendererper text chunk — matching by counting which occurrence of a highlighted string a given chunk represents, since the same text can appear more than once on a page.Table —
useTableFilereads the file with thexlsxlibrary and converts the first worksheet into rows/columns forreact-data-grid(ws_to_rdg), including a heuristic that treats numeric cells under column names likedate/time/nr/amountas dates or plain numbers rather than raw Excel serial numbers (isDateTimeColumn/isNumberColumn, matched by substring against the column letter — note this checks the column letter like"A", not a header row, so it only fires on thenr/id/etc. substrings appearing in the letter itself; see the TODO in Selection & paths about how cell selection turns a click into a path).Xml —
useXmlFilejust reads the file as text;XmlViewrenders it withreact-interactive-xml-viewerand, in "Reference" selection mode, wires itsonClickTag/onClickAttribute/onClickValuecallbacks touseXmlPath(see Selection & paths).Text —
useTextpaginates the file's text content into fixed-size pages (linesPerPage = 50) and tracks each page's character offset into the full text (getPageOffset, reconstructed by summing prior lines' lengths) — that offset is what lets highlight ranges, which are computed against the full text, be mapped onto the currently-displayed page.Email —
useEmailparses.eml(viaeml-parse-js) or.msg(via@kenjiuno/msgreader, decompressing RTF bodies with@kenjiuno/decompressrtf+rtf-stream-parserwhen present) into{ text, html }, sanitizes the HTML (strips<script>,on*handlers, and non-httpsrc/href), and rewritescid:image references to inline base64 data URIs.EmailView's highlight logic for the HTML case is the most involved in the package: it walks the DOM to a flat text offset space, finds the target occurrence there, then re-locates and wraps it back across the original (possibly multiple) text nodes, because a single highlighted string in the rendered text can span more than one DOM text node.Json —
useJsonFilesimplyJSON.parses the store's text content.JsonViewrenders it with@uiw/react-json-view, overriding itsKeyName/Rowrenderers to calluseJsonPathon click in "Reference" mode.Uncaught parse errors
useJsonFiledoesn't wrap itsJSON.parsecall — a malformed JSON file throws uncaught rather than showing an error state in the viewer.
Pdf, Table, Text, and Email all call useTextSelect unconditionally on mount, wiring up text-selection anchoring for that view; Xml and Json don't — they use their tag/value-click model instead (see Selection & paths for both mechanisms).
Shared chrome
Most organisms end with the same <Footer /> (from molecules), and the paginated ones (Pdf, Text) share the same <Move into={false} /> / page-counter / <Move /> header pattern from @morph-mapper/ui's Move action.