@cherrystudio/file-preview

中文文档
0.2.5发布于 2026/11/08AGPL-3.0-onlyTypeScriptES Module近 30 天下载 2589

Portable read-only document preview for Cherry Studio

原始使用文档
文档目录7 节
  1. WebView and inline hosts
  2. Opt-in host layout and font compatibility
  3. Styling
  4. RTF documents
  5. Building
  6. Browser regression tests
  7. Publishing

File preview

Portable React DOM previews for PDF, DOCX, PPTX, XLSX, RTF and images (including SVG). The package owns rendering, controls, structural selections, workers and translations. Hosts own file access, permissions, navigation, external opening and logging. HTML, Markdown and source-code previews remain in the desktop application.

Install a published version alongside React 19 and React DOM 19:

pnpm add @cherrystudio/file-preview react@^19 react-dom@^19

The package can be installed independently of the Cherry Studio workspace. It bundles its rendering engines and UI components; consumers do not need workspace packages or dependency patches.

import type { PreviewSource } from '@cherrystudio/file-preview/core'
import { Preview } from '@cherrystudio/file-preview/react'
import '@cherrystudio/file-preview/styles.css'

function FilePanel({ source }: { source: PreviewSource }) {
  return <Preview source={source} locale="zh-cn" />
}

Give the parent a defined height. Each source opens an independent PreviewDocument. The preview closes it after failure, replacement, refresh or unmount, including a late open that completes after cancellation. A document's size and revision stay fixed; its reader must reject version changes, short reads and reads after close. Close must be idempotent. Range offsets and lengths are safe integers within the document size. DOCX, PPTX, XLSX, RTF and images request the whole document as one [0, size) range, so a host can serve it with a single read; PDF requests partial ranges. Readers should honor abort signals where their transport supports cancellation. The package discards a read result after cancellation; it cannot stop host I/O that has already started or interrupt synchronous engine parsing. Returned byte views remain host-owned: the package does not mutate or detach them. XLSX copies the requested bytes before transferring its own buffer to the parsing worker.

refreshKey is an optional number. Changing it closes the current document and opens a fresh session, including after a failure. header accepts host-owned navigation and file-identity content for the fixed top row; the format toolbar shares that row. Without a header, format controls render above the document. Give source and resources stable references so layout, locale and theme updates retain the session.

Refreshing the same source ID and format retains the mounted format and its valid reading state while opening a fresh document. Formats preserve zoom, page, scroll, worksheet and image transforms as applicable, and clamp positions to shorter content. A different source ID or format starts a new preview. The previous document still closes on refresh; obsolete reads and parsing results cannot overwrite retained content or publish selections or terminal errors for the replacement session. Closing and reopening a preview does not persist reading history. Hosts must clear any held selection when requesting a refresh.

onSelection reports { sourceId, revision, anchor, excerpt }, or null when a pick is cleared. Anchors use worksheet A1 ranges, body paragraph ordinals, PDF pages and PPTX slides. The host owns held selections across refreshes and file switches. revision is opaque to the package and only distinguishes document versions. Hosts map selections back to their own file identity; the package never parses it. Excerpts use NFC normalization, collapse the shared JavaScript/Python whitespace set to single spaces, and trim both ends. They are limited to 2000 UTF-16 code units, without splitting a surrogate pair; an empty normalized excerpt produces null. Callback references are stabilized inside Preview, so inline callbacks do not reopen documents or repeat a selection notification merely because the host rendered.

DOCX text supports native selection and copying with Ctrl+C (Cmd+C on macOS), including across paragraphs. Copying uses the browser selection, not the normalized or length-limited reference excerpt. While paragraph picking is enabled, ending a text drag preserves the existing paragraph pick; a plain click still picks or clears it.

Recognized source.mediaType values take precedence over the filename extension; missing or unrecognized media types fall back to the extension. supportsPreview and canSelectPreview accept the same optional media type as their second argument.

onDiagnostic is the logging channel for engine warnings and errors. A terminal failure emits one diagnostic and calls onError once for that session; onError notifies host state and should not log the same error again. Expected too_large failures are warnings. Non-terminal problems, such as a failed PPTX slide or PDF outline navigation, emit diagnostics while the rest of the document remains usable.

onError receives a PreviewError with a stable code and an optional underlying cause. Error messages are diagnostic details, not localized UI copy.

CodeMeaning
invalid_rangeInvalid document size, offset or read length
short_readThe reader returned fewer or more bytes than requested
source_changedThe host detected a revision change during reading
closedA read was attempted after the host document closed
too_largeA preview source or PDF range exceeded its safety limit
load_errorOpening, parsing or rendering failed for another reason

onRequestOpen delegates external opening to the host only after the user activates an open button. Its reason is unsupported for unrecognized formats or too_large for size-limit fallbacks; rendering or refreshing an unsupported source never opens another application automatically. DOCX/PPTX above 25 MiB, XLSX above 20 MiB, RTF above 10 MiB and images above 64 MiB show their limits. PDF has a 16 MiB assembled-range cap, without a whole-document size limit. These states offer an external-open button when the host supplies the callback. resources.baseUrl optionally overrides the bundled worker/font/CMap directory; include a trailing slash. Relative URLs resolve against the host page. readPdfResource takes precedence over resource URLs for PDF fonts and CMaps, including font substitution. Electron provides it for file-scheme pages. PDF workers belong to individual previews and never change pdf.js global options. Cleanup awaits the pdf.js loading task's destruction before terminating its worker, allowing document fonts to be released. There is no forced teardown timeout: an already-unresponsive worker can leave that destruction promise pending. Translations use an independent i18next instance with resources for 14 languages; other locales fall back to English.

FilePreviewLayout.Shell owns the shared header and toolbar placement. FilePreviewLayout, FilePreviewToolbar and its portal provider/host are also used by desktop-only formats; FilePreviewToolbarButton is available for icon commands. The registry is static; formats cannot be registered at runtime.

For a bundled web application, copy the package's dist/assets/ directory to a public directory (for example, Vite's public/preview-assets/) and point the preview at its deployed URL:

const resources = { baseUrl: '/preview-assets/' }

<Preview source={source} resources={resources} locale="zh-cn" />

Keep the workers, cmaps/ and standard_fonts/ together. Worker entry URLs must be same-origin with the application; CORS permission alone does not allow a cross-origin new Worker() entry. A cross-origin asset host needs a same-origin worker entry or a host-provided createWorker. Font and CMap requests separately require the server's cross-origin permissions when loaded from another origin. When no base URL is provided, the bundler handles the native worker URLs; hosts must still deploy the PDF resource directories or provide readPdfResource.

WebView and inline hosts

A page whose bundle has no URL, such as inline HTML loaded into a mobile WebView, cannot resolve the bundled workers or PDF resources. Supply both through resources:

import pdfWorker from '@cherrystudio/file-preview/assets/pdf.worker.js?raw'
import xlsxWorker from '@cherrystudio/file-preview/assets/xlsx.worker.js?raw'

const workerSources = { pdf: pdfWorker, xlsx: xlsxWorker }
const workerUrls: Partial<Record<'pdf' | 'xlsx', string>> = {}
const resources = {
  createWorker: (kind: 'pdf' | 'xlsx') => {
    workerUrls[kind] ??= URL.createObjectURL(new Blob([workerSources[kind]], { type: 'text/javascript' }))
    return new Worker(workerUrls[kind], { type: 'module' })
  },
  readPdfResource: (kind: 'cmap' | 'standard_font', name: string) => bridge.readPdfResource(kind, name)
}

Cache one object URL per worker kind for the host resource lifetime. After all previews using these resources have unmounted and the host disposes this resource set, release the URLs with for (const url of Object.values(workerUrls)) URL.revokeObjectURL(url). Terminating an individual worker does not revoke its object URL.

createWorker takes precedence over baseUrl for both workers. Each worker file is self-contained. Give inline HTML a base URL, such as react-native-webview's source={{ html, baseUrl: 'https://file-preview.local/' }}: in an opaque about:blank origin, browsers refuse module workers created from blob URLs. readPdfResource serves names from assets/cmaps/ (without .bcmap) and assets/standard_fonts/.

PDF and DOCX zoom with a two-finger pinch, and images pinch-zoom in their viewport. PPTX and XLSX zoom through the toolbar. A spreadsheet touch selects a cell on tap, and swiping scrolls without selecting. Disable page zoom in the host page (<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">) so a pinch outside these surfaces does not scale the whole preview.

Opt-in host layout and font compatibility

All options are opt-in. Omitting them preserves desktop rendering: a PDF sidebar, DOCX at 100% with its existing font handling, translucent spreadsheet headers, and the existing viewport inset. The package does not detect the host platform.

<Preview
  source={source}
  resources={resources}
  options={{
    bottomInset: 'content',
    pdf: { outlineLayout: useOverlayOutline ? 'overlay' : 'panel' },
    docx: { initialZoom: 'fit-width', normalizeSymbolBullets: true },
    xlsx: { opaqueHeaders: true }
  }}
/>

The host chooses useOverlayOutline from its actual available width, including landscape orientation. Overlay mode covers the preview without shrinking its pages; Escape, the close button, the backdrop, and selecting an internal destination dismiss it. Explicitly selecting either PDF layout also keeps page-width zoom in sync with container resizing; manual zoom is preserved.

DOCX fit-width fits the widest page to the pane, permits scales below 50%, and never enlarges beyond 200%. It follows container resizing until the user zooms using the toolbar or a pinch; Reset resumes fitting. No orientation message is needed for DOCX. normalizeSymbolBullets is read when opening the document: it maps known single-character Symbol/Wingdings bullet markers to Unicode and removes their legacy font override. Other fonts, unknown symbols and numbered lists are preserved.

bottomInset: 'content' adds trailing space inside PDF, DOCX and PPTX document scroll containers, in unscaled host CSS pixels. Both PDF outline layouts receive trailing scroll space too. XLSX is the fixed-control exception: its sheet tabs, status and zoom controls reserve the inset below the whole footer so host overlays cannot cover them. The grid does not add a second inset. Other formats retain their existing inset behavior. opaqueHeaders composites the muted XLSX header color over an opaque version of --background, including the frozen corner, so cells cannot show through alpha colors.

Keep source and resources references stable during layout, locale and theme updates. Changing options does not reopen the source. Font compatibility takes effect on the next document open or explicit refresh; it does not reparse an open document.

Styling

className and style apply to the preview's single theme root. Internal frames inherit its tokens. Add dark for the dark theme, and set these custom properties to retheme it. Root overrides win over the packaged values, and PDF observes this same root for theme changes.

PropertyUsed for
--background, --foregroundSurfaces and text
--primaryPicks, focus and active controls
--muted, --muted-foregroundSecondary surfaces, icons and labels
--border, --border-subtle, --ringDividers, outlines and focus rings
--file-preview-toolbar-button-sizeToolbar button size, default 1.75rem
--file-preview-bottom-insetBottom space; content mode adds document scroll space or protects the fixed XLSX footer

RTF documents

RTF previews render formatted text, Unicode and legacy code pages, links, and embedded images using rtf.js. Documents use a white canvas in both themes to retain authored foreground and highlight colors. HTTP, HTTPS and mail links open only on user action; other links remain plain text. External image imports are not loaded.

Rendering is a flowing document preview, not a page-faithful Word layout. RTF does not provide structural selection references. Parsing runs synchronously within the lazy format module, subject to the 10 MiB source limit. The bundled rtf.js patch rejects truncated documents, consumes binary image payloads, and restores the default font on character resets. Literal bytes, hexadecimal escapes and escaped symbols share one code-page decoder; Unicode controls remain decoded text. Raw double-byte trail bytes are consumed before interpreting RTF syntax. Incomplete or unmapped byte sequences fail the preview instead of inserting undefined into document content. Regression cases cover Windows-1252, GBK, Big5 and Shift-JIS, including mixed byte representations, font changes, group boundaries and Unicode fallback skipping.

Building

pnpm --dir packages/file-preview pack --pack-destination /path/to/artifacts

pack runs the production build first, so the tarball never carries a stale dist.

The library build uses Vite to process native worker URLs and Tailwind CSS, plus rolldown-plugin-dts for declaration bundles. Every third-party library except zod is bundled, including UI components and the patched docx-preview and pptx-renderer, so they are dev dependencies; consumers install only zod and the React peers. CSS excludes Tailwind preflight and scopes selectors to .file-preview-root.

Browser regression tests

From the workspace root, install Playwright's Chromium once, then run the package tests:

pnpm exec playwright install chromium
pnpm exec playwright test --config packages/file-preview/playwright.config.ts

The test configuration builds the package and starts its own Vite server, then stops the server after the run. No running Electron instance or renderer development server is required. The PDF cases exercise the built public package and actual worker/layout for mixed/changed page geometry, reading offsets, refresh, zoom, Activity and shorter documents. The PowerPoint case uses the real renderer to retain a slide-relative reading offset when the same deck changes its slide proportions, including Activity restoration.

Publishing

Versioning and npm publishing use the repository's Changesets workflow. The initial changeset promotes 0.1.0-alpha.0 to 0.1.0 in the automated version PR. Merging that version PR publishes the package under the latest tag. Subsequent publishable changes must include a changeset for @cherrystudio/file-preview.

Both release and snapshot workflows build this package through pnpm packages:build. The manual snapshot workflow publishes under the snapshot tag for integration testing:

pnpm add @cherrystudio/file-preview@snapshot

The prepack hook rebuilds the package before packing or publishing. The artifact contains the JavaScript and declaration entry points, scoped stylesheet, worker scripts, PDF CMaps and standard fonts, README and license. Verify the packed artifact in an independent consumer before the first release, including worker and PDF resource loading.

版本与发布记录

@cherrystudio/file-preview 共发布 11 个版本,首次发布于 2026/10/09;过去 12 个月发布了 11 个版本。

版本发布日期距今
0.2.5latest2026/11/08今天
0.2.42026/11/045 天前
0.2.32026/10/2317 天前
0.2.22026/10/2317 天前
0.2.12026/10/2217 天前
0.2.02026/10/2218 天前

关于 @cherrystudio/file-preview 的常见问题

@cherrystudio/file-preview 是什么?

Portable read-only document preview for Cherry Studio

如何安装 @cherrystudio/file-preview?

在项目目录运行 npm install @cherrystudio/file-preview,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.0.0)、react-dom(^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cherrystudio/file-preview 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cherrystudio/file-preview 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cherrystudio/file-preview 可以商用吗?

@cherrystudio/file-preview 使用 AGPL-3.0-only 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@cherrystudio/file-preview 的下载量有多少?

根据 npm 官方下载统计,@cherrystudio/file-preview 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,589 次,日均约 86 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cherrystudio/file-preview 还在维护吗?

最新版本 0.2.5 发布于 2026/11/08(今天);至今共发布 11 个版本,其中过去 12 个月发布了 11 个;首次发布于 2026/10/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。