@dxos/plugin-file 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import * as Button from "@dxos/react-ui/Button";
8
8
  import * as Field from "@dxos/react-ui/Field";
9
9
  import * as Input from "@dxos/react-ui/Input";
10
+ import * as Layout from "@dxos/react-ui/Layout";
10
11
  import { Form } from "@dxos/react-ui-form";
11
12
  import * as Option from "effect/Option";
12
13
  import * as EffectEx from "@dxos/effect/EffectEx";
@@ -47,8 +48,9 @@ var FileProperties = ({ subject: file }) => {
47
48
  resolve();
48
49
  }, [file.id]);
49
50
  if (!reference && !url) return null;
50
- return /* @__PURE__ */ jsxs(Form.FieldSet, { children: [reference && /* @__PURE__ */ jsxs(Field.Root, { children: [/* @__PURE__ */ jsx(Field.Label, { children: t("properties.reference.label") }), /* @__PURE__ */ jsxs("div", {
51
- className: "flex w-full gap-1",
51
+ return /* @__PURE__ */ jsxs(Form.FieldSet, { children: [reference && /* @__PURE__ */ jsxs(Field.Root, { children: [/* @__PURE__ */ jsx(Field.Label, { children: t("properties.reference.label") }), /* @__PURE__ */ jsxs(Layout.Flex, {
52
+ gap: "xs",
53
+ classNames: "w-full",
52
54
  children: [/* @__PURE__ */ jsx(Input.Root, {
53
55
  readOnly: true,
54
56
  value: reference,
@@ -60,8 +62,9 @@ var FileProperties = ({ subject: file }) => {
60
62
  })]
61
63
  })] }), url && /* @__PURE__ */ jsxs(Field.Root, { children: [
62
64
  /* @__PURE__ */ jsx(Field.Label, { children: t("properties.url.label") }),
63
- /* @__PURE__ */ jsxs("div", {
64
- className: "flex w-full gap-1",
65
+ /* @__PURE__ */ jsxs(Layout.Flex, {
66
+ gap: "xs",
67
+ classNames: "w-full",
65
68
  children: [
66
69
  /* @__PURE__ */ jsx(Input.Root, {
67
70
  readOnly: true,
@@ -1 +1 @@
1
- {"version":3,"file":"chunk-FileProperties.mjs","names":[],"sources":["../../src/containers/FileProperties/FileProperties.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport React, { useCallback, useEffect, useState } from 'react';\n\nimport type * as AppSurface from '@dxos/app-toolkit/AppSurface';\nimport { Blob, Database, Obj } from '@dxos/echo';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { Form } from '@dxos/react-ui-form';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport { File } from '@dxos/types';\n\nimport { meta } from '#meta';\n\nexport type FilePropertiesProps = AppSurface.ObjectPropertiesProps<File.File>;\n\n/**\n * Properties for a {@link File}: where its bytes actually live.\n *\n * Two values, because they answer different questions and have different lifetimes. The **reference**\n * is the stored URI — `s3://<bucket>/<space>/<hash>` or `ni:///sha-256;…` — which names the backend\n * and survives forever; it is what to quote when asking \"where did this go?\". The **URL** is what a\n * browser can fetch right now, and for a private bucket that is a presigned URL which expires, hence\n * the regenerate control rather than a value presented as permanent.\n */\nexport const FileProperties = ({ subject: file }: FilePropertiesProps) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const [reference, setReference] = useState<string | undefined>(undefined);\n const [url, setUrl] = useState<string | undefined>(undefined);\n const [pending, setPending] = useState(false);\n\n const resolve = useCallback(async () => {\n const db = Obj.getDatabase(file);\n if (!db) {\n return;\n }\n\n setPending(true);\n const program = Effect.gen(function* () {\n const blob = yield* Database.load(file.data);\n const urlOption = yield* Blob.url(blob);\n return {\n // `inline` blobs carry bytes rather than a URI; there is no reference to show.\n reference: blob.data._tag === 'external' ? blob.data.uri : undefined,\n url: Option.getOrUndefined(urlOption),\n };\n }).pipe(\n Effect.provide(Database.layer(db)),\n Effect.catch(() => Effect.succeed(undefined)),\n );\n\n const result = await EffectEx.runPromise(program);\n setReference(result?.reference);\n setUrl(result?.url);\n setPending(false);\n }, [file]);\n\n // Keyed on `file.id`, not `file.data`: ECHO's proxy returns a fresh `Ref` wrapper on every access,\n // so depending on the ref itself would re-resolve on every render.\n useEffect(() => {\n void resolve();\n }, [file.id]);\n\n if (!reference && !url) {\n return null;\n }\n\n return (\n <Form.FieldSet>\n {reference && (\n <Field.Root>\n <Field.Label>{t('properties.reference.label')}</Field.Label>\n <div className='flex w-full gap-1'>\n <Input.Root readOnly value={reference} classNames='grow' />\n <SystemButton.Clipboard iconOnly value={reference} label={t('properties.reference.copy.label')} />\n </div>\n </Field.Root>\n )}\n {url && (\n <Field.Root>\n <Field.Label>{t('properties.url.label')}</Field.Label>\n <div className='flex w-full gap-1'>\n <Input.Root readOnly value={url} classNames='grow' />\n <SystemButton.Clipboard iconOnly value={url} label={t('properties.url.copy.label')} />\n <Button.Root\n iconOnly\n icon='ph--arrows-clockwise--regular'\n label={t('properties.url.regenerate.label')}\n disabled={pending}\n onClick={() => void resolve()}\n />\n </div>\n <Field.HelperText>{t('properties.url.description')}</Field.HelperText>\n </Field.Root>\n )}\n </Form.FieldSet>\n );\n};\n\nFileProperties.displayName = 'FileProperties';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,kBAAkB,EAAE,SAAS,WAAgC;CACxE,MAAM,EAAE,MAAM,MAAM,eAAe,KAAK,QAAQ,GAAG;CACnD,MAAM,CAAC,WAAW,gBAAgB,SAA6B,KAAA,CAAS;CACxE,MAAM,CAAC,KAAK,UAAU,SAA6B,KAAA,CAAS;CAC5D,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,UAAU,YAAY,YAAY;EACtC,MAAM,KAAK,IAAI,YAAY,IAAI;EAC/B,IAAI,CAAC,IACH;EAGF,WAAW,IAAI;EACf,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,IAAI;GAC3C,MAAM,YAAY,OAAO,KAAK,IAAI,IAAI;GACtC,OAAO;IAEL,WAAW,KAAK,KAAK,SAAS,aAAa,KAAK,KAAK,MAAM,KAAA;IAC3D,KAAK,OAAO,eAAe,SAAS;GACtC;EACF,CAAC,CAAC,CAAC,KACD,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAC,GACjC,OAAO,YAAY,OAAO,QAAQ,KAAA,CAAS,CAAC,CAC9C;EAEA,MAAM,SAAS,MAAM,SAAS,WAAW,OAAO;EAChD,aAAa,QAAQ,SAAS;EAC9B,OAAO,QAAQ,GAAG;EAClB,WAAW,KAAK;CAClB,GAAG,CAAC,IAAI,CAAC;CAIT,gBAAgB;EACd,QAAa;CACf,GAAG,CAAC,KAAK,EAAE,CAAC;CAEZ,IAAI,CAAC,aAAa,CAAC,KACjB,OAAO;CAGT,OACE,qBAAC,KAAK,UAAN,EAAA,UAAA,CACG,aACC,qBAAC,MAAM,MAAP,EAAA,UAAA,CACE,oBAAC,MAAM,OAAP,EAAA,UAAc,EAAE,4BAA4B,EAAe,CAAA,GAC3D,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,MAAM,MAAP;GAAY,UAAA;GAAS,OAAO;GAAW,YAAW;EAAQ,CAAA,GAC1D,oBAAC,aAAa,WAAd;GAAwB,UAAA;GAAS,OAAO;GAAW,OAAO,EAAE,iCAAiC;EAAI,CAAA,CAC9F;CACK,CAAA,CAAA,EAAA,CAAA,GAEb,OACC,qBAAC,MAAM,MAAP,EAAA,UAAA;EACE,oBAAC,MAAM,OAAP,EAAA,UAAc,EAAE,sBAAsB,EAAe,CAAA;EACrD,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,oBAAC,MAAM,MAAP;KAAY,UAAA;KAAS,OAAO;KAAK,YAAW;IAAQ,CAAA;IACpD,oBAAC,aAAa,WAAd;KAAwB,UAAA;KAAS,OAAO;KAAK,OAAO,EAAE,2BAA2B;IAAI,CAAA;IACrF,oBAAC,OAAO,MAAR;KACE,UAAA;KACA,MAAK;KACL,OAAO,EAAE,iCAAiC;KAC1C,UAAU;KACV,eAAe,KAAK,QAAQ;IAC7B,CAAA;GACE;;EACL,oBAAC,MAAM,YAAP,EAAA,UAAmB,EAAE,4BAA4B,EAAoB,CAAA;CAC3D,EAAA,CAAA,CAED,EAAA,CAAA;AAEnB;AAEA,eAAe,cAAc"}
1
+ {"version":3,"file":"chunk-FileProperties.mjs","names":[],"sources":["../../src/containers/FileProperties/FileProperties.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport React, { useCallback, useEffect, useState } from 'react';\n\nimport type * as AppSurface from '@dxos/app-toolkit/AppSurface';\nimport { Blob, Database, Obj } from '@dxos/echo';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { Form } from '@dxos/react-ui-form';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport { File } from '@dxos/types';\n\nimport { meta } from '#meta';\n\nexport type FilePropertiesProps = AppSurface.ObjectPropertiesProps<File.File>;\n\n/**\n * Properties for a {@link File}: where its bytes actually live.\n *\n * Two values, because they answer different questions and have different lifetimes. The **reference**\n * is the stored URI — `s3://<bucket>/<space>/<hash>` or `ni:///sha-256;…` — which names the backend\n * and survives forever; it is what to quote when asking \"where did this go?\". The **URL** is what a\n * browser can fetch right now, and for a private bucket that is a presigned URL which expires, hence\n * the regenerate control rather than a value presented as permanent.\n */\nexport const FileProperties = ({ subject: file }: FilePropertiesProps) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const [reference, setReference] = useState<string | undefined>(undefined);\n const [url, setUrl] = useState<string | undefined>(undefined);\n const [pending, setPending] = useState(false);\n\n const resolve = useCallback(async () => {\n const db = Obj.getDatabase(file);\n if (!db) {\n return;\n }\n\n setPending(true);\n const program = Effect.gen(function* () {\n const blob = yield* Database.load(file.data);\n const urlOption = yield* Blob.url(blob);\n return {\n // `inline` blobs carry bytes rather than a URI; there is no reference to show.\n reference: blob.data._tag === 'external' ? blob.data.uri : undefined,\n url: Option.getOrUndefined(urlOption),\n };\n }).pipe(\n Effect.provide(Database.layer(db)),\n Effect.catch(() => Effect.succeed(undefined)),\n );\n\n const result = await EffectEx.runPromise(program);\n setReference(result?.reference);\n setUrl(result?.url);\n setPending(false);\n }, [file]);\n\n // Keyed on `file.id`, not `file.data`: ECHO's proxy returns a fresh `Ref` wrapper on every access,\n // so depending on the ref itself would re-resolve on every render.\n useEffect(() => {\n void resolve();\n }, [file.id]);\n\n if (!reference && !url) {\n return null;\n }\n\n return (\n <Form.FieldSet>\n {reference && (\n <Field.Root>\n <Field.Label>{t('properties.reference.label')}</Field.Label>\n <Layout.Flex gap='xs' classNames='w-full'>\n <Input.Root readOnly value={reference} classNames='grow' />\n <SystemButton.Clipboard iconOnly value={reference} label={t('properties.reference.copy.label')} />\n </Layout.Flex>\n </Field.Root>\n )}\n {url && (\n <Field.Root>\n <Field.Label>{t('properties.url.label')}</Field.Label>\n <Layout.Flex gap='xs' classNames='w-full'>\n <Input.Root readOnly value={url} classNames='grow' />\n <SystemButton.Clipboard iconOnly value={url} label={t('properties.url.copy.label')} />\n <Button.Root\n iconOnly\n icon='ph--arrows-clockwise--regular'\n label={t('properties.url.regenerate.label')}\n disabled={pending}\n onClick={() => void resolve()}\n />\n </Layout.Flex>\n <Field.HelperText>{t('properties.url.description')}</Field.HelperText>\n </Field.Root>\n )}\n </Form.FieldSet>\n );\n};\n\nFileProperties.displayName = 'FileProperties';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,kBAAkB,EAAE,SAAS,WAAgC;CACxE,MAAM,EAAE,MAAM,MAAM,eAAe,KAAK,QAAQ,GAAG;CACnD,MAAM,CAAC,WAAW,gBAAgB,SAA6B,KAAA,CAAS;CACxE,MAAM,CAAC,KAAK,UAAU,SAA6B,KAAA,CAAS;CAC5D,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,UAAU,YAAY,YAAY;EACtC,MAAM,KAAK,IAAI,YAAY,IAAI;EAC/B,IAAI,CAAC,IACH;EAGF,WAAW,IAAI;EACf,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,IAAI;GAC3C,MAAM,YAAY,OAAO,KAAK,IAAI,IAAI;GACtC,OAAO;IAEL,WAAW,KAAK,KAAK,SAAS,aAAa,KAAK,KAAK,MAAM,KAAA;IAC3D,KAAK,OAAO,eAAe,SAAS;GACtC;EACF,CAAC,CAAC,CAAC,KACD,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAC,GACjC,OAAO,YAAY,OAAO,QAAQ,KAAA,CAAS,CAAC,CAC9C;EAEA,MAAM,SAAS,MAAM,SAAS,WAAW,OAAO;EAChD,aAAa,QAAQ,SAAS;EAC9B,OAAO,QAAQ,GAAG;EAClB,WAAW,KAAK;CAClB,GAAG,CAAC,IAAI,CAAC;CAIT,gBAAgB;EACd,QAAa;CACf,GAAG,CAAC,KAAK,EAAE,CAAC;CAEZ,IAAI,CAAC,aAAa,CAAC,KACjB,OAAO;CAGT,OACE,qBAAC,KAAK,UAAN,EAAA,UAAA,CACG,aACC,qBAAC,MAAM,MAAP,EAAA,UAAA,CACE,oBAAC,MAAM,OAAP,EAAA,UAAc,EAAE,4BAA4B,EAAe,CAAA,GAC3D,qBAAC,OAAO,MAAR;EAAa,KAAI;EAAK,YAAW;EAAjC,UAAA,CACE,oBAAC,MAAM,MAAP;GAAY,UAAA;GAAS,OAAO;GAAW,YAAW;EAAQ,CAAA,GAC1D,oBAAC,aAAa,WAAd;GAAwB,UAAA;GAAS,OAAO;GAAW,OAAO,EAAE,iCAAiC;EAAI,CAAA,CACtF;CACH,CAAA,CAAA,EAAA,CAAA,GAEb,OACC,qBAAC,MAAM,MAAP,EAAA,UAAA;EACE,oBAAC,MAAM,OAAP,EAAA,UAAc,EAAE,sBAAsB,EAAe,CAAA;EACrD,qBAAC,OAAO,MAAR;GAAa,KAAI;GAAK,YAAW;GAAjC,UAAA;IACE,oBAAC,MAAM,MAAP;KAAY,UAAA;KAAS,OAAO;KAAK,YAAW;IAAQ,CAAA;IACpD,oBAAC,aAAa,WAAd;KAAwB,UAAA;KAAS,OAAO;KAAK,OAAO,EAAE,2BAA2B;IAAI,CAAA;IACrF,oBAAC,OAAO,MAAR;KACE,UAAA;KACA,MAAK;KACL,OAAO,EAAE,iCAAiC;KAC1C,UAAU;KACV,eAAe,KAAK,QAAQ;IAC7B,CAAA;GACU;;EACb,oBAAC,MAAM,YAAP,EAAA,UAAmB,EAAE,4BAA4B,EAAoB,CAAA;CAC3D,EAAA,CAAA,CAED,EAAA,CAAA;AAEnB;AAEA,eAAe,cAAc"}
@@ -13,6 +13,7 @@ import * as Button from "@dxos/react-ui/Button";
13
13
  import * as Field from "@dxos/react-ui/Field";
14
14
  import * as Icon from "@dxos/react-ui/Icon";
15
15
  import * as Input from "@dxos/react-ui/Input";
16
+ import * as Layout from "@dxos/react-ui/Layout";
16
17
  import * as Media from "@dxos/react-ui/Media";
17
18
  import * as Toolbar from "@dxos/react-ui/Toolbar";
18
19
  //#region src/components/FileInput/FileInput.tsx
@@ -789,8 +790,11 @@ var PreviewContent = Util.composable((props, forwardedRef) => {
789
790
  return /* @__PURE__ */ jsx("div", {
790
791
  ...Util.composableProps(props, { classNames: "grid place-items-center dx-fill p-8" }),
791
792
  ref: forwardedRef,
792
- children: /* @__PURE__ */ jsxs("div", {
793
- className: "flex flex-col items-center gap-2 text-center",
793
+ children: /* @__PURE__ */ jsxs(Layout.Flex, {
794
+ column: true,
795
+ align: "center",
796
+ gap: "sm",
797
+ classNames: "text-center",
794
798
  children: [
795
799
  /* @__PURE__ */ jsx(Icon.Icon, {
796
800
  icon: "ph--file--regular",
@@ -1 +1 @@
1
- {"version":3,"file":"components.mjs","names":[],"sources":["../../src/components/FileInput/FileInput.tsx","../../src/components/PdfCanvas/pdf-search.ts","../../src/components/PdfCanvas/PdfCanvas.tsx","../../src/components/Preview/PreviewContext.ts","../../src/components/Preview/Preview.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback } from 'react';\nimport { useDropzone } from 'react-dropzone';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport { mx } from '@dxos/ui-theme';\n\nimport { meta } from '#meta';\n\nexport type FileInputProps = {\n onChange: (file: File) => void;\n};\n\nexport const FileInput = ({ onChange }: FileInputProps) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n\n const onDropAccepted = useCallback((files: File[]) => onChange?.(files[0]), [onChange]);\n\n // No client-side size or type filter: the active backend determines the size limit (inline is\n // capped, edge/wnfs are not) and `op:Create` enforces it; every type is accepted.\n const { acceptedFiles, getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({\n multiple: false,\n onDropAccepted,\n });\n\n return (\n <div\n {...getRootProps()}\n className={mx(\n 'flex flex-col items-center p-8 border border-separator',\n isFocused && 'focus-ring',\n isDragAccept && 'bg-attention-surface',\n isDragReject && 'border-rose-bg',\n )}\n >\n <input {...getInputProps()} />\n {acceptedFiles[0] ? <p>{acceptedFiles[0].name}</p> : <p>{t('file-input.placeholder')}</p>}\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n/** One occurrence of the query, located well enough to scroll to and highlight. */\nexport type PdfMatch = {\n /** 1-based. */\n page: number;\n /** Index into the page's text items, which is also the index into the text layer's spans. */\n item: number;\n /** Character offset of the match within that item's string. */\n offset: number;\n length: number;\n};\n\nexport type PdfPageText = {\n items: string[];\n};\n\n/**\n * Every occurrence of `query`, case-insensitively.\n *\n * Matches are found per text item rather than across the page's concatenated text. A match spanning\n * two items is therefore missed — pdf.js splits items on font and positioning changes, so that\n * happens mid-word in justified text. Accepted deliberately: locating a cross-item match means\n * mapping an offset back through the join, and the failure mode here is a missed hit rather than a\n * highlight drawn in the wrong place.\n */\nexport const findMatches = (pages: PdfPageText[], query: string): PdfMatch[] => {\n const needle = query.trim().toLowerCase();\n if (!needle) {\n return [];\n }\n\n const matches: PdfMatch[] = [];\n pages.forEach((page, pageIndex) => {\n page.items.forEach((item, itemIndex) => {\n const haystack = item.toLowerCase();\n let offset = haystack.indexOf(needle);\n while (offset !== -1) {\n matches.push({ page: pageIndex + 1, item: itemIndex, offset, length: needle.length });\n offset = haystack.indexOf(needle, offset + needle.length);\n }\n });\n });\n return matches;\n};\n\n/**\n * Rewrites a text-layer span so its matched substrings sit inside `<mark>` elements.\n *\n * Highlighting in the DOM rather than as positioned overlays, because pdf.js gives each span an\n * `scaleX` transform that stretches it to the glyph widths the PDF specifies. Rectangles measured\n * with `Range.getClientRects()` do not map through that transform, so an overlay lands narrower\n * than the word and drifts left — visibly wrong on any proportional font. A `<mark>` inside the\n * span is laid out by the browser under the same transform, so it cannot disagree.\n */\nexport const markSpan = (\n span: HTMLElement,\n text: string,\n ranges: { offset: number; length: number; active: boolean }[],\n): void => {\n if (ranges.length === 0) {\n // Only rewrite when the span actually carries markup; pdf.js reuses these nodes, and replacing\n // identical text on every keystroke would churn the whole layer.\n if (span.firstElementChild) {\n span.textContent = text;\n }\n return;\n }\n\n const fragment = document.createDocumentFragment();\n let cursor = 0;\n for (const { offset, length, active } of [...ranges].sort((a, b) => a.offset - b.offset)) {\n if (offset > cursor) {\n fragment.append(text.slice(cursor, offset));\n }\n const mark = document.createElement('mark');\n mark.className = 'dx-pdf-highlight';\n mark.dataset.active = String(active);\n mark.textContent = text.slice(offset, offset + length);\n fragment.append(mark);\n cursor = offset + length;\n }\n if (cursor < text.length) {\n fragment.append(text.slice(cursor));\n }\n span.replaceChildren(fragment);\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport './text-layer.css';\n\n// The `legacy` build, not the default one: the default calls `Map.prototype.getOrInsertComputed`,\n// which is new enough that the Chromium the storybook tests run in throws on it. Legacy targets a\n// wider baseline and is the variant pdf.js publishes for exactly this.\nimport type { PDFDocumentLoadingTask, PDFDocumentProxy, RenderTask } from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport workerUrl from 'pdfjs-dist/legacy/build/pdf.worker.min.mjs?url';\nimport React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\n\nimport { log } from '@dxos/log';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { meta } from '#meta';\n\nimport { type PdfMatch, type PdfPageText, findMatches, markSpan } from './pdf-search.ts';\n\n/**\n * pdf.js, loaded on first use rather than on import.\n *\n * At module scope it joins the plugin's own module graph, so every consumer of the components barrel\n * pays for the whole PDF engine at load — enough to push `plugin-file` past its activation timeout\n * and take plugins that depend on it down with it. Memoised, so the cost is paid once.\n *\n * The worker is bundled and served by us rather than fetched from a CDN: the app must render a PDF\n * offline, and a worker on another origin is also a CSP entry we would have to keep in step.\n */\nlet pdfjs: Promise<typeof import('pdfjs-dist/legacy/build/pdf.mjs')> | undefined;\n\nconst loadPdfjs = () => {\n pdfjs ??= import('pdfjs-dist/legacy/build/pdf.mjs').then((module) => {\n module.GlobalWorkerOptions.workerSrc = workerUrl;\n return module;\n });\n return pdfjs;\n};\n\n/** How a page is scaled to the viewport. */\nexport type PdfFit = 'width' | 'page';\n\n/** Imperative surface the toolbar drives, published through {@link PdfCanvasProps.apiRef}. */\nexport type PdfApi = {\n goToPage: (page: number, behavior?: ScrollBehavior) => void;\n /** Steps by `delta` from the pending target, so repeated clicks accumulate mid-scroll. */\n stepPage: (delta: number) => void;\n search: (query: string) => void;\n goToMatch: (index: number) => void;\n};\n\nexport type PdfCanvasState = {\n pageCount: number;\n currentPage: number;\n matches: number;\n activeMatch: number;\n};\n\nexport type PdfCanvasProps = {\n url: string;\n fit?: PdfFit;\n onLoad?: (pageCount: number) => void;\n onStateChange?: (state: PdfCanvasState) => void;\n apiRef?: React.Ref<PdfApi>;\n};\n\n/** One pass of the windowed renderer; `cancelled` unwinds it when a resize or unmount supersedes it. */\ntype RenderRun = { cancelled: boolean; task?: RenderTask };\n\ntype PageRefs = {\n page: HTMLDivElement | null;\n canvas: HTMLCanvasElement | null;\n text: HTMLDivElement | null;\n};\n\n/**\n * Renders a PDF to stacked canvases, one per page, each with a selectable text layer over it.\n *\n * Replaces an `<iframe>` pointed at the browser's built-in viewer. That viewer lives in its own\n * origin — a `chrome-extension://` document — so nothing about it can be styled or driven. Owning\n * the render means the toolbar can page, fit and search, and the pages can be themed.\n *\n * The text layer is what makes the page more than a picture: it is transparent text positioned over\n * the canvas, so the browser can select it and search matches can be highlighted by wrapping them\n * in the layer's own spans rather than by positioning overlays over the page.\n *\n * Every page is rendered rather than one at a time: the iframe it replaces scrolled through the\n * whole document. Large documents therefore cost their full page count up front — virtualization is\n * the obvious next step if that becomes a problem.\n */\nexport const PdfCanvas = Util.composable<HTMLDivElement, PdfCanvasProps>(\n ({ url, fit = 'width', onLoad, onStateChange, apiRef, ...props }, forwardedRef) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const containerRef = useRef<HTMLDivElement>(null);\n const refs = useRef<PageRefs[]>([]);\n const documentRef = useRef<PDFDocumentProxy | undefined>(undefined);\n const textRef = useRef<PdfPageText[]>([]);\n /** Unscaled page dimensions, so a page can be sized before it is drawn. */\n const sizesRef = useRef<({ width: number; height: number } | undefined)[]>([]);\n /** Scale each page was last drawn at, so scrolling back does not re-rasterise it. */\n const renderedRef = useRef<(number | undefined)[]>([]);\n // The live query, so a search typed before the text index finished loading can be re-run once it\n // is ready. Without this, searching a large document during its first seconds silently returns\n // nothing and never corrects itself.\n const queryRef = useRef('');\n // The page a scroll is heading towards. Stepping reads this rather than the observed page, so\n // clicking next twice quickly advances two pages instead of re-targeting the same one — smooth\n // scrolling means the observed page has not moved yet on the second click.\n const targetRef = useRef<number | undefined>(undefined);\n // Read by the imperative navigation callbacks, which must stay stable for `useImperativeHandle`\n // while still seeing the current fit mode and page count.\n const fitRef = useRef<PdfFit>(fit);\n const pageCountRef = useRef(0);\n const [pageCount, setPageCount] = useState(0);\n const [currentPage, setCurrentPage] = useState(1);\n const [matches, setMatches] = useState<PdfMatch[]>([]);\n const [activeMatch, setActiveMatch] = useState(0);\n const [layoutVersion, setLayoutVersion] = useState(0);\n const [error, setError] = useState(false);\n\n // Tracks the in-flight render so a resize or an unmount can cancel it. Without this, tearing the\n // document down mid-render leaves `page.render(…).promise` and `getPage` rejecting with\n // `RenderingCancelledException` / `Transport destroyed` and nothing to catch them.\n const renderRef = useRef<RenderRun | undefined>(undefined);\n\n const cancelRender = useCallback(() => {\n const current = renderRef.current;\n if (current) {\n current.cancelled = true;\n current.task?.cancel();\n renderRef.current = undefined;\n }\n }, []);\n\n const getRefs = useCallback(\n (index: number): PageRefs => (refs.current[index] ??= { page: null, canvas: null, text: null }),\n [],\n );\n\n const runSearch = useCallback((query: string) => {\n const found = findMatches(textRef.current, query);\n setMatches(found);\n setActiveMatch(found.length > 0 ? 1 : 0);\n }, []);\n\n /** Page size at scale 1, resolved once so placeholders can be sized without rendering. */\n const sizePage = useCallback((index: number): { width: number; height: number } | undefined => {\n return sizesRef.current[index];\n }, []);\n\n /** Scale that fits a page to the container under the current fit mode. */\n const scaleFor = useCallback((size: { width: number; height: number }, fitMode: PdfFit): number => {\n const container = containerRef.current;\n if (!container) {\n return 1;\n }\n const widthScale = (container.clientWidth - PAGE_MARGIN * 2) / size.width;\n if (fitMode === 'width') {\n return widthScale;\n }\n // Fit-page must fit BOTH axes, which is what makes a landscape page usable: fitting width\n // alone leaves a wide page taller than the viewport and it reads as if fit did nothing.\n return Math.min(widthScale, (container.clientHeight - PAGE_MARGIN * 2) / size.height);\n }, []);\n\n // Renders one page at device resolution, so text stays sharp on a HiDPI display — a canvas\n // sized in CSS pixels renders visibly soft.\n const renderPage = useCallback(\n async (pdf: PDFDocumentProxy, pageNumber: number, fitMode: PdfFit, run: RenderRun) => {\n const { canvas, text } = getRefs(pageNumber - 1);\n const size = sizePage(pageNumber - 1);\n if (!canvas || !size || run.cancelled) {\n return;\n }\n\n const scale = scaleFor(size, fitMode);\n // Skip a page already drawn at this scale; scrolling back over it must not re-rasterise.\n if (renderedRef.current[pageNumber - 1] === scale) {\n return;\n }\n\n const page = await pdf.getPage(pageNumber);\n if (run.cancelled) {\n return;\n }\n\n const viewport = page.getViewport({ scale });\n const ratio = globalThis.devicePixelRatio ?? 1;\n canvas.width = Math.floor(viewport.width * ratio);\n canvas.height = Math.floor(viewport.height * ratio);\n canvas.style.width = `${Math.floor(viewport.width)}px`;\n canvas.style.height = `${Math.floor(viewport.height)}px`;\n\n const context = canvas.getContext('2d');\n if (!context) {\n return;\n }\n const task = page.render({ canvas, canvasContext: context, viewport, transform: [ratio, 0, 0, ratio, 0, 0] });\n run.task = task;\n await task.promise;\n if (run.cancelled) {\n return;\n }\n\n if (text) {\n text.replaceChildren();\n // `--total-scale-factor` is the property pdf.js' own stylesheet reads to size and place\n // each span; `--scale-factor` alone leaves every glyph at the default 16px.\n text.style.setProperty('--total-scale-factor', String(scale));\n text.style.setProperty('--scale-factor', String(scale));\n const { TextLayer } = await loadPdfjs();\n const layer = new TextLayer({\n textContentSource: await page.getTextContent(),\n container: text,\n viewport,\n });\n await layer.render();\n }\n\n renderedRef.current[pageNumber - 1] = scale;\n },\n [getRefs, scaleFor, sizePage],\n );\n\n /**\n * Renders the pages currently near the viewport.\n *\n * Only those, because rendering a whole document up front does not survive contact with a real\n * one: a few hundred pages is a few hundred rasterisations and text layers held at once, which\n * takes the tab down. Pages outside the window keep their placeholder box, so scroll position\n * and page numbering stay correct.\n *\n * Each page is isolated: pdf.js raises on individual pages (a JBIG2 image it cannot decode, a\n * broken font), and one such page must not stop every page after it from drawing.\n */\n const renderVisible = useCallback(\n async (pdf: PDFDocumentProxy, fitMode: PdfFit) => {\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n cancelRender();\n const run: RenderRun = { cancelled: false };\n renderRef.current = run;\n\n const origin = container.getBoundingClientRect();\n const window = origin.height * RENDER_WINDOW;\n const wanted: number[] = [];\n for (let index = 0; index < pdf.numPages; index++) {\n const element = refs.current[index]?.page;\n if (!element) {\n continue;\n }\n const rect = element.getBoundingClientRect();\n if (rect.bottom >= origin.top - window && rect.top <= origin.bottom + window) {\n wanted.push(index + 1);\n }\n }\n\n for (const pageNumber of wanted) {\n if (run.cancelled) {\n break;\n }\n try {\n await renderPage(pdf, pageNumber, fitMode, run);\n } catch (error) {\n if (run.cancelled) {\n break;\n }\n // Logged rather than thrown: a page pdf.js cannot draw is a property of the document,\n // and the rest of it is still worth reading.\n log.warn('failed to render page', { pageNumber, error });\n }\n }\n\n if (renderRef.current === run) {\n renderRef.current = undefined;\n }\n if (!run.cancelled) {\n // Highlights are measured from the rendered spans, so they can only be placed once the\n // text layers above exist at their final size.\n setLayoutVersion((version) => version + 1);\n }\n },\n [cancelRender, renderPage],\n );\n\n useEffect(() => {\n let cancelled = false;\n setError(false);\n setPageCount(0);\n setMatches([]);\n\n let task: PDFDocumentLoadingTask | undefined;\n void loadPdfjs().then(({ getDocument }) => {\n if (cancelled) {\n return;\n }\n task = getDocument({ url });\n return task.promise.then(\n async (pdf) => {\n // The loading task owns teardown; the cleanup below destroys it and the document with it.\n if (cancelled) {\n return;\n }\n documentRef.current = pdf;\n sizesRef.current = [];\n renderedRef.current = [];\n\n // Dimensions first, and only dimensions: they size every placeholder so the scrollbar is\n // honest from the outset, and they cost one cheap `getPage` each rather than a raster.\n for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {\n if (cancelled) {\n return;\n }\n const { width, height } = (await pdf.getPage(pageNumber)).getViewport({ scale: 1 });\n sizesRef.current[pageNumber - 1] = { width, height };\n }\n if (cancelled) {\n return;\n }\n setPageCount(pdf.numPages);\n onLoad?.(pdf.numPages);\n\n // Text of every page, resolved once. Searching re-reads it on every keystroke, and\n // `getTextContent` is a worker round-trip per page — fine once, not once per character.\n // After the sizes, so the document is on screen before the index is built.\n const pages: PdfPageText[] = [];\n for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {\n if (cancelled) {\n return;\n }\n try {\n const content = await (await pdf.getPage(pageNumber)).getTextContent();\n pages.push({ items: content.items.map((item) => ('str' in item ? item.str : '')) });\n } catch (error) {\n // A page whose text cannot be extracted is still worth showing; it just never matches.\n log.warn('failed to read page text', { pageNumber, error });\n pages.push({ items: [] });\n }\n }\n if (!cancelled) {\n textRef.current = pages;\n if (queryRef.current.trim()) {\n runSearch(queryRef.current);\n }\n }\n },\n () => {\n if (!cancelled) {\n setError(true);\n }\n },\n );\n });\n\n return () => {\n cancelled = true;\n // Cancel before destroying: `task.destroy()` tears down the transport, and any render still\n // awaiting it would reject with `Transport destroyed` after this effect is gone.\n cancelRender();\n void task?.destroy();\n documentRef.current = undefined;\n textRef.current = [];\n };\n // `onLoad` is deliberately not a dependency: callers pass an inline function, which would\n // re-fetch the document on every render.\n }, [url, cancelRender, runSearch]);\n\n // Runs after the canvases for `pageCount` exist, and again on resize or a fit change so pages\n // keep filling the available space.\n useEffect(() => {\n const pdf = documentRef.current;\n const container = containerRef.current;\n if (!pdf || !container || pageCount === 0) {\n return;\n }\n\n // A resize or fit change invalidates every drawn page, since the scale moved.\n renderedRef.current = [];\n void renderVisible(pdf, fit);\n\n // Scrolling brings new pages into the window; coalesced to a frame so a fling does not queue\n // one pass per scroll event.\n let frame = 0;\n const onScroll = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => {\n const current = documentRef.current;\n if (current) {\n void renderVisible(current, fit);\n }\n });\n };\n container.addEventListener('scroll', onScroll, { passive: true });\n\n const observer =\n typeof ResizeObserver === 'undefined'\n ? undefined\n : new ResizeObserver(() => {\n const current = documentRef.current;\n if (current) {\n renderedRef.current = [];\n void renderVisible(current, fit);\n }\n });\n observer?.observe(container);\n return () => {\n cancelAnimationFrame(frame);\n container.removeEventListener('scroll', onScroll);\n observer?.disconnect();\n cancelRender();\n };\n }, [pageCount, fit, currentPage, renderVisible, cancelRender]);\n\n // The page under the top of the viewport is \"current\" — the same rule the browser's own viewer\n // uses, and the one that matches what a reader considers the page they are on.\n useEffect(() => {\n const container = containerRef.current;\n // In fit-page the visible page IS `currentPage`, so deriving it from scroll would fight the\n // pager: there is no scrolling, and every page sits at the same offset.\n if (!container || pageCount === 0 || fit === 'page') {\n return;\n }\n\n // Measured from bounding rects rather than `offsetTop`: that is relative to the nearest\n // *positioned* ancestor, which is not necessarily this scroll container, and comparing it to\n // `scrollTop` reported the wrong page at rest.\n const onScroll = () => {\n const origin = container.getBoundingClientRect().top;\n let page = 1;\n for (let index = 0; index < pageCount; index++) {\n const element = refs.current[index]?.page;\n if (element && element.getBoundingClientRect().top - origin <= PAGE_MARGIN + 8) {\n page = index + 1;\n }\n }\n setCurrentPage(page);\n if (targetRef.current === page) {\n targetRef.current = undefined;\n }\n };\n\n container.addEventListener('scroll', onScroll, { passive: true });\n onScroll();\n return () => container.removeEventListener('scroll', onScroll);\n }, [pageCount, fit]);\n\n const goToPageByScroll = useCallback((page: number, behavior: ScrollBehavior = 'smooth') => {\n const element = refs.current[page - 1]?.page;\n const container = containerRef.current;\n if (element && container) {\n targetRef.current = page;\n // Relative scroll for the same reason `onScroll` measures rects: the page's `offsetTop` is\n // not necessarily expressed in this container's coordinates.\n const delta = element.getBoundingClientRect().top - container.getBoundingClientRect().top;\n container.scrollTo({ top: container.scrollTop + delta - PAGE_MARGIN, behavior });\n }\n }, []);\n\n const goToPage = useCallback((page: number, behavior: ScrollBehavior = 'smooth') => {\n const clamped = Math.min(Math.max(page, 1), pageCountRef.current || 1);\n // Fit-page shows one page at a time, so navigating is a state change; there is nothing to\n // scroll within.\n if (fitRef.current === 'page') {\n targetRef.current = undefined;\n setCurrentPage(clamped);\n return;\n }\n goToPageByScroll(clamped, behavior);\n }, []);\n\n const stepPage = useCallback(\n (delta: number) => {\n const base = targetRef.current ?? currentPage;\n goToPage(Math.min(Math.max(base + delta, 1), pageCount));\n },\n [currentPage, pageCount, goToPage],\n );\n\n const search = useCallback(\n (query: string) => {\n queryRef.current = query;\n runSearch(query);\n },\n [runSearch],\n );\n\n const goToMatch = useCallback(\n (index: number) => {\n if (matches.length === 0) {\n return;\n }\n // Wraps in both directions, so `next` on the last match returns to the first.\n const next = ((index - 1 + matches.length) % matches.length) + 1;\n setActiveMatch(next);\n goToPage(matches[next - 1].page);\n },\n [matches, goToPage],\n );\n\n useEffect(() => {\n fitRef.current = fit;\n }, [fit]);\n\n useEffect(() => {\n pageCountRef.current = pageCount;\n }, [pageCount]);\n\n useImperativeHandle(apiRef, () => ({ goToPage, stepPage, search, goToMatch }), [\n goToPage,\n stepPage,\n search,\n goToMatch,\n ]);\n\n // Rewrites the spans in place whenever the matches, the active one, or the layout change. The\n // layout dependency matters: a re-render replaces the spans, discarding any marks on them.\n useEffect(() => {\n refs.current.forEach((pageRefs, pageIndex) => {\n const layer = pageRefs?.text;\n const items = textRef.current[pageIndex]?.items;\n if (!layer || !items) {\n return;\n }\n items.forEach((text, itemIndex) => {\n // The layer alternates spans and `<br>`: pdf.js emits one child per text item, a `<br>`\n // for the end-of-line ones, so child index tracks item index. Skip the breaks explicitly\n // rather than relying on their empty text never matching.\n const span = layer.children[itemIndex];\n if (!(span instanceof HTMLElement) || span.tagName !== 'SPAN') {\n return;\n }\n const ranges = matches.flatMap((match, index) =>\n match.page === pageIndex + 1 && match.item === itemIndex\n ? [{ offset: match.offset, length: match.length, active: index + 1 === activeMatch }]\n : [],\n );\n markSpan(span, text, ranges);\n });\n });\n }, [matches, activeMatch, layoutVersion]);\n\n useEffect(() => {\n onStateChange?.({ pageCount, currentPage, matches: matches.length, activeMatch });\n // `onStateChange` is excluded for the same reason as `onLoad` above.\n }, [pageCount, currentPage, matches.length, activeMatch]);\n\n if (error) {\n return (\n <div {...Util.composableProps(props, { classNames: 'dx-fill overflow-auto' })} ref={forwardedRef}>\n <div role='alert' className='p-4 text-sm text-error-text'>\n {t('pdf-error.message')}\n </div>\n </div>\n );\n }\n\n const placeholderStyle = (index: number) => {\n const size = sizesRef.current[index];\n if (!size) {\n return undefined;\n }\n const scale = scaleFor(size, fit);\n return { width: Math.floor(size.width * scale), height: Math.floor(size.height * scale) };\n };\n\n // Fit-page is a paged view, not a scrolled one: the whole point of fitting both axes is that\n // exactly one page is on screen, so it is centred and the pager moves between pages rather than\n // scrolling within a column. Fit-width keeps the continuous column, where scrolling is the\n // natural motion.\n const single = fit === 'page';\n const shown = single ? [currentPage - 1] : Array.from({ length: pageCount }, (_, index) => index);\n\n return (\n <div\n data-pdf-canvas=''\n {...Util.composableProps(props, {\n classNames: [\n 'dx-fill bg-deck-surface select-text',\n single ? 'overflow-hidden grid place-items-center' : 'overflow-auto',\n ],\n })}\n // Two refs on one node: `containerRef` measures the available width for the page scale, and\n // `forwardedRef` belongs to whatever slotted this in.\n ref={Hooks.composeRefs(containerRef, forwardedRef)}\n >\n <div className={single ? 'contents' : 'flex flex-col items-center gap-4 py-4'}>\n {shown.map((index) => (\n <div\n key={index}\n data-page={index + 1}\n // Sized from the page's own dimensions rather than from its canvas, so a page outside\n // the render window still occupies its true height and the scrollbar does not lurch as\n // pages draw.\n className='relative w-fit shadow-md bg-white'\n style={placeholderStyle(index)}\n ref={(element) => {\n getRefs(index).page = element;\n }}\n >\n <canvas\n className='block'\n ref={(element) => {\n getRefs(index).canvas = element;\n }}\n />\n <div\n className='dx-pdf-text-layer'\n ref={(element) => {\n getRefs(index).text = element;\n }}\n />\n </div>\n ))}\n </div>\n </div>\n );\n },\n);\n\n/** Viewport heights rendered either side of the visible area, so a scroll finds pages ready. */\nconst RENDER_WINDOW = 1;\n\n/** Space around and between pages, in CSS pixels; matches the `gap-4`/`py-4` on the page column. */\nconst PAGE_MARGIN = 16;\n\nPdfCanvas.displayName = 'PdfCanvas';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext, useContext } from 'react';\n\nimport { type PdfApi, type PdfCanvasState, type PdfFit } from '../PdfCanvas/index.ts';\n\n// Kept out of `Preview.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\n/**\n * What the toolbar needs from a paged document. Present only while such a document is displayed —\n * an image has no pages to step through and no text to search.\n */\nexport type PreviewPaged = PdfCanvasState & {\n api: PdfApi | undefined;\n fit: PdfFit;\n setFit: (fit: PdfFit) => void;\n};\n\nexport type PreviewContextValue = {\n type: string;\n url: string;\n /** Attendable id of the surrounding article, when there is one; scopes the toolbar's shortcuts. */\n attendableId?: string;\n /** Filename, when known; used for the download affordance and the unsupported-type details. */\n name?: string;\n size?: number;\n paged?: PreviewPaged;\n setPaged: (paged: PreviewPaged | undefined) => void;\n};\n\nexport const PreviewContext = createContext<PreviewContextValue | undefined>(undefined);\n\nexport const usePreview = (consumer: string): PreviewContextValue => {\n const context = useContext(PreviewContext);\n if (!context) {\n throw new Error(`${consumer} must be used within a Preview.Root.`);\n }\n return context;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useHotkeys } from '@dxos/react-focus';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Media from '@dxos/react-ui/Media';\nimport * as Toolbar from '@dxos/react-ui/Toolbar';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { meta } from '#meta';\n\nimport { type PdfApi, PdfCanvas, type PdfCanvasState, type PdfFit } from '../PdfCanvas/index.ts';\nimport { PreviewContext, type PreviewPaged, usePreview } from './PreviewContext.ts';\n\n//\n// Root\n//\n\nexport type PreviewRootProps = {\n type: string;\n url: string;\n name?: string;\n size?: number;\n /** Attendable id of the surrounding article; without it the toolbar binds no shortcuts. */\n attendableId?: string;\n children?: React.ReactNode;\n};\n\n/**\n * Preview of a file's content, composed rather than configured: `Preview.Root` holds the source and\n * whatever the content learns about it, `Preview.Content` renders by media type, and\n * `Preview.Toolbar` sits above with controls.\n *\n * Split this way because the toolbar needs facts only the content discovers — a PDF's page count,\n * current page and search matches are known after pdf.js parses the document, not before — and a\n * single component taking a `toolbar` prop would have to lift that state through props it does not\n * own.\n *\n * The root renders no element of its own. Its parts are placed into slots that belong to whatever\n * surrounds them — `Panel.Toolbar` and `Panel.Content` in the file article — so a wrapper would\n * break the panel's grid by putting a div between it and its areas.\n */\nconst PreviewRoot = ({ type, url, name, size, attendableId, children }: PreviewRootProps) => {\n const [paged, setPaged] = useState<PreviewPaged | undefined>(undefined);\n const value = useMemo(\n () => ({ type, url, name, size, attendableId, paged, setPaged }),\n [type, url, name, size, attendableId, paged],\n );\n\n return <PreviewContext.Provider value={value}>{children}</PreviewContext.Provider>;\n};\n\nPreviewRoot.displayName = 'Preview.Root';\n\n//\n// Toolbar\n//\n\n/**\n * Controls above the content: page stepping, fit and search for a paged document, and a download\n * for every type. Always renders, so the row is reserved and the content does not shift when a\n * document finishes parsing and its controls appear.\n *\n * `composable` rather than a plain component: this is placed in `Panel.Toolbar asChild`, which\n * forwards its own className and ref through Slot, and a plain component would silently drop both.\n */\nconst PreviewToolbar = Util.composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const { name, url, paged, attendableId } = usePreview('Preview.Toolbar');\n const [query, setQuery] = useState('');\n const searchRef = useRef<HTMLInputElement>(null);\n const { hasAttention } = useAttention(attendableId);\n\n // Gated on the article having attention rather than bound globally, so the shortcut belongs to\n // whichever plank the reader is in — the same mechanism `useArticleKeyboardNavigation` uses.\n useHotkeys({\n id: `${attendableId}:search`,\n commands: [\n {\n hotkey: 'meta+f',\n label: t('search-shortcut.label'),\n enabled: () => !!attendableId && hasAttention && !!paged,\n // Reachable from anywhere in the article, including when focus is already in the field,\n // where selecting the text is the useful outcome.\n options: { enableOnFormTags: true, enableOnContentEditable: true },\n action: () => {\n searchRef.current?.focus();\n searchRef.current?.select();\n },\n },\n ],\n });\n\n const handleSearch = useCallback(\n (next: string) => {\n setQuery(next);\n paged?.api?.search(next);\n },\n [paged],\n );\n\n return (\n <Toolbar.Root {...Util.composableProps(props, { classNames: '@container' })} ref={forwardedRef}>\n {children}\n {paged && (\n <>\n <Button.Root\n iconOnly\n icon={paged.fit === 'width' ? 'ph--arrows-out-line-horizontal--regular' : 'ph--corners-out--regular'}\n label={paged.fit === 'width' ? t('fit-page.label') : t('fit-width.label')}\n onClick={() => paged.setFit(paged.fit === 'width' ? 'page' : 'width')}\n />\n <Button.Root\n compact\n iconOnly\n icon='ph--caret-line-left--regular'\n classNames='hidden @md:flex transition-none'\n label={t('first-page.label')}\n disabled={paged.currentPage <= 1}\n onClick={() => paged.api?.goToPage(1, 'instant')}\n />\n <Button.Root\n iconOnly\n icon='ph--caret-left--regular'\n label={t('previous-page.label')}\n disabled={paged.currentPage <= 1}\n onClick={() => paged.api?.stepPage(-1)}\n />\n {/* Two copies stacked in one grid cell: an invisible one of the widest value reserves the\n box, so stepping 9 → 10 does not shove the buttons beside it. Padding with spaces\n cannot do this (HTML collapses them) and zero-padding shows a leading zero.\n `Toolbar.Text` also truncates by default — right for a label, wrong for a counter. */}\n <Toolbar.Text classNames='grid justify-items-end shrink-0 overflow-visible text-nowrap text-sm tabular-nums'>\n <span aria-hidden className='invisible col-start-1 row-start-1'>\n {t('page-of.label', { page: paged.pageCount, count: paged.pageCount })}\n </span>\n <span className='col-start-1 row-start-1'>\n {t('page-of.label', { page: paged.currentPage, count: paged.pageCount })}\n </span>\n </Toolbar.Text>\n <Button.Root\n iconOnly\n icon='ph--caret-right--regular'\n label={t('next-page.label')}\n disabled={paged.currentPage >= paged.pageCount}\n onClick={() => paged.api?.stepPage(1)}\n />\n <Button.Root\n compact\n iconOnly\n icon='ph--caret-line-right--regular'\n classNames='hidden @md:flex transition-none'\n label={t('last-page.label')}\n disabled={paged.currentPage >= paged.pageCount}\n onClick={() => paged.api?.goToPage(paged.pageCount, 'instant')}\n />\n <Toolbar.Separator />\n <Field.Root>\n <Input.Root\n ref={searchRef}\n placeholder={t('search.placeholder')}\n value={query}\n classNames='grow'\n spellCheck={false}\n autoCorrect='off'\n autoCapitalize='off'\n end={<Icon.Icon icon='ph--magnifying-glass--regular' size='md' />}\n onChange={(event) => handleSearch(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n paged.api?.goToMatch(event.shiftKey ? paged.activeMatch - 1 : paged.activeMatch + 1);\n }\n }}\n />\n </Field.Root>\n {query.trim().length > 0 && (\n <>\n <Toolbar.Text classNames='shrink-0 overflow-visible text-nowrap tabular-nums'>\n {paged.matches === 0\n ? t('no-matches.label')\n : t('match-of.label', { match: paged.activeMatch, count: paged.matches })}\n </Toolbar.Text>\n <Button.Root\n iconOnly\n icon='ph--caret-up--regular'\n label={t('previous-match.label')}\n disabled={paged.matches === 0}\n onClick={() => paged.api?.goToMatch(paged.activeMatch - 1)}\n />\n <Button.Root\n iconOnly\n icon='ph--caret-down--regular'\n label={t('next-match.label')}\n disabled={paged.matches === 0}\n onClick={() => paged.api?.goToMatch(paged.activeMatch + 1)}\n />\n </>\n )}\n </>\n )}\n <Toolbar.Separator />\n {/* An anchor rather than a button: `download` is what makes the browser save instead of\n navigate, and it works for the `data:`/`blob:`/presigned URLs every backend produces. */}\n <Toolbar.Link href={url} download={name ?? true} aria-label={t('download.label')} title={t('download.label')}>\n <Icon.Icon icon='ph--download-simple--regular' size='lg' />\n </Toolbar.Link>\n </Toolbar.Root>\n );\n});\n\nPreviewToolbar.displayName = 'Preview.Toolbar';\n\n//\n// Content\n//\n\n/**\n * Renders the source by media type: PDFs through pdf.js, images/video/audio through `MediaPlayer`,\n * and anything else as a description of what it is — the download lives in the toolbar, where it is\n * available for every type rather than only the ones with no preview.\n *\n * `composable` for the same reason as the toolbar — it is placed in `Panel.Content asChild`.\n */\nconst PreviewContent = Util.composable<HTMLDivElement>((props, forwardedRef) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const { type, url, name, size, setPaged } = usePreview('Preview.Content');\n const apiRef = useRef<PdfApi>(null);\n const [fit, setFit] = useState<PdfFit>('width');\n const [state, setState] = useState<PdfCanvasState>({ pageCount: 0, currentPage: 1, matches: 0, activeMatch: 0 });\n const { className } = Util.composableProps(props);\n const paged = type === 'application/pdf';\n\n // Publishes the paged controls up to the toolbar, and withdraws them when the content is no\n // longer a paged document so the toolbar does not offer page stepping for an image.\n useEffect(() => {\n if (!paged) {\n setPaged(undefined);\n return;\n }\n setPaged({ ...state, api: apiRef.current ?? undefined, fit, setFit });\n return () => setPaged(undefined);\n }, [paged, state, fit, setPaged]);\n\n if (paged) {\n return (\n <PdfCanvas\n classNames={className}\n url={url}\n fit={fit}\n apiRef={apiRef}\n onStateChange={setState}\n ref={forwardedRef}\n />\n );\n }\n\n if (type.startsWith('image/') || type.startsWith('video/') || type.startsWith('audio/')) {\n return (\n <div {...Util.composableProps(props, { classNames: 'grid dx-fill min-h-0' })} ref={forwardedRef}>\n <Media.Player\n classNames='dx-fill'\n src={url}\n // `kind` is set explicitly for audio and video because the URL is a `data:`/`blob:`/\n // presigned one with no usable extension, which is all `detectMediaKind` has to go on.\n // Images take no `kind` — `MediaKind` has no image member, and the unset path is `<img>`.\n kind={type.startsWith('audio/') ? 'audio' : type.startsWith('video/') ? 'video' : undefined}\n fit='contain'\n />\n </div>\n );\n }\n\n return (\n <div {...Util.composableProps(props, { classNames: 'grid place-items-center dx-fill p-8' })} ref={forwardedRef}>\n <div className='flex flex-col items-center gap-2 text-center'>\n <Icon.Icon icon='ph--file--regular' size='xl' tone='subtle' />\n {name && <span className='text-sm'>{name}</span>}\n <span className='text-xs text-fg-subtle'>\n {size === undefined ? type : t('file-details.label', { type, size: formatBytes(size) })}\n </span>\n <span className='text-xs text-fg-subtle'>{t('no-preview.message')}</span>\n </div>\n </div>\n );\n});\n\nPreviewContent.displayName = 'Preview.Content';\n\nconst UNITS = ['B', 'kB', 'MB', 'GB'];\n\n/** Decimal units, matching what a file manager shows rather than what a disk reports. */\nconst formatBytes = (bytes: number): string => {\n let value = bytes;\n let unit = 0;\n while (value >= 1000 && unit < UNITS.length - 1) {\n value /= 1000;\n unit++;\n }\n return `${unit === 0 ? value : value.toFixed(1)} ${UNITS[unit]}`;\n};\n\nexport const Preview = {\n Root: PreviewRoot,\n Toolbar: PreviewToolbar,\n Content: PreviewContent,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"components.mjs","names":[],"sources":["../../src/components/FileInput/FileInput.tsx","../../src/components/PdfCanvas/pdf-search.ts","../../src/components/PdfCanvas/PdfCanvas.tsx","../../src/components/Preview/PreviewContext.ts","../../src/components/Preview/Preview.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback } from 'react';\nimport { useDropzone } from 'react-dropzone';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport { mx } from '@dxos/ui-theme';\n\nimport { meta } from '#meta';\n\nexport type FileInputProps = {\n onChange: (file: File) => void;\n};\n\nexport const FileInput = ({ onChange }: FileInputProps) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n\n const onDropAccepted = useCallback((files: File[]) => onChange?.(files[0]), [onChange]);\n\n // No client-side size or type filter: the active backend determines the size limit (inline is\n // capped, edge/wnfs are not) and `op:Create` enforces it; every type is accepted.\n const { acceptedFiles, getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({\n multiple: false,\n onDropAccepted,\n });\n\n return (\n <div\n {...getRootProps()}\n className={mx(\n 'flex flex-col items-center p-8 border border-separator',\n isFocused && 'focus-ring',\n isDragAccept && 'bg-attention-surface',\n isDragReject && 'border-rose-bg',\n )}\n >\n <input {...getInputProps()} />\n {acceptedFiles[0] ? <p>{acceptedFiles[0].name}</p> : <p>{t('file-input.placeholder')}</p>}\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n/** One occurrence of the query, located well enough to scroll to and highlight. */\nexport type PdfMatch = {\n /** 1-based. */\n page: number;\n /** Index into the page's text items, which is also the index into the text layer's spans. */\n item: number;\n /** Character offset of the match within that item's string. */\n offset: number;\n length: number;\n};\n\nexport type PdfPageText = {\n items: string[];\n};\n\n/**\n * Every occurrence of `query`, case-insensitively.\n *\n * Matches are found per text item rather than across the page's concatenated text. A match spanning\n * two items is therefore missed — pdf.js splits items on font and positioning changes, so that\n * happens mid-word in justified text. Accepted deliberately: locating a cross-item match means\n * mapping an offset back through the join, and the failure mode here is a missed hit rather than a\n * highlight drawn in the wrong place.\n */\nexport const findMatches = (pages: PdfPageText[], query: string): PdfMatch[] => {\n const needle = query.trim().toLowerCase();\n if (!needle) {\n return [];\n }\n\n const matches: PdfMatch[] = [];\n pages.forEach((page, pageIndex) => {\n page.items.forEach((item, itemIndex) => {\n const haystack = item.toLowerCase();\n let offset = haystack.indexOf(needle);\n while (offset !== -1) {\n matches.push({ page: pageIndex + 1, item: itemIndex, offset, length: needle.length });\n offset = haystack.indexOf(needle, offset + needle.length);\n }\n });\n });\n return matches;\n};\n\n/**\n * Rewrites a text-layer span so its matched substrings sit inside `<mark>` elements.\n *\n * Highlighting in the DOM rather than as positioned overlays, because pdf.js gives each span an\n * `scaleX` transform that stretches it to the glyph widths the PDF specifies. Rectangles measured\n * with `Range.getClientRects()` do not map through that transform, so an overlay lands narrower\n * than the word and drifts left — visibly wrong on any proportional font. A `<mark>` inside the\n * span is laid out by the browser under the same transform, so it cannot disagree.\n */\nexport const markSpan = (\n span: HTMLElement,\n text: string,\n ranges: { offset: number; length: number; active: boolean }[],\n): void => {\n if (ranges.length === 0) {\n // Only rewrite when the span actually carries markup; pdf.js reuses these nodes, and replacing\n // identical text on every keystroke would churn the whole layer.\n if (span.firstElementChild) {\n span.textContent = text;\n }\n return;\n }\n\n const fragment = document.createDocumentFragment();\n let cursor = 0;\n for (const { offset, length, active } of [...ranges].sort((a, b) => a.offset - b.offset)) {\n if (offset > cursor) {\n fragment.append(text.slice(cursor, offset));\n }\n const mark = document.createElement('mark');\n mark.className = 'dx-pdf-highlight';\n mark.dataset.active = String(active);\n mark.textContent = text.slice(offset, offset + length);\n fragment.append(mark);\n cursor = offset + length;\n }\n if (cursor < text.length) {\n fragment.append(text.slice(cursor));\n }\n span.replaceChildren(fragment);\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport './text-layer.css';\n\n// The `legacy` build, not the default one: the default calls `Map.prototype.getOrInsertComputed`,\n// which is new enough that the Chromium the storybook tests run in throws on it. Legacy targets a\n// wider baseline and is the variant pdf.js publishes for exactly this.\nimport type { PDFDocumentLoadingTask, PDFDocumentProxy, RenderTask } from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport workerUrl from 'pdfjs-dist/legacy/build/pdf.worker.min.mjs?url';\nimport React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\n\nimport { log } from '@dxos/log';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { meta } from '#meta';\n\nimport { type PdfMatch, type PdfPageText, findMatches, markSpan } from './pdf-search.ts';\n\n/**\n * pdf.js, loaded on first use rather than on import.\n *\n * At module scope it joins the plugin's own module graph, so every consumer of the components barrel\n * pays for the whole PDF engine at load — enough to push `plugin-file` past its activation timeout\n * and take plugins that depend on it down with it. Memoised, so the cost is paid once.\n *\n * The worker is bundled and served by us rather than fetched from a CDN: the app must render a PDF\n * offline, and a worker on another origin is also a CSP entry we would have to keep in step.\n */\nlet pdfjs: Promise<typeof import('pdfjs-dist/legacy/build/pdf.mjs')> | undefined;\n\nconst loadPdfjs = () => {\n pdfjs ??= import('pdfjs-dist/legacy/build/pdf.mjs').then((module) => {\n module.GlobalWorkerOptions.workerSrc = workerUrl;\n return module;\n });\n return pdfjs;\n};\n\n/** How a page is scaled to the viewport. */\nexport type PdfFit = 'width' | 'page';\n\n/** Imperative surface the toolbar drives, published through {@link PdfCanvasProps.apiRef}. */\nexport type PdfApi = {\n goToPage: (page: number, behavior?: ScrollBehavior) => void;\n /** Steps by `delta` from the pending target, so repeated clicks accumulate mid-scroll. */\n stepPage: (delta: number) => void;\n search: (query: string) => void;\n goToMatch: (index: number) => void;\n};\n\nexport type PdfCanvasState = {\n pageCount: number;\n currentPage: number;\n matches: number;\n activeMatch: number;\n};\n\nexport type PdfCanvasProps = {\n url: string;\n fit?: PdfFit;\n onLoad?: (pageCount: number) => void;\n onStateChange?: (state: PdfCanvasState) => void;\n apiRef?: React.Ref<PdfApi>;\n};\n\n/** One pass of the windowed renderer; `cancelled` unwinds it when a resize or unmount supersedes it. */\ntype RenderRun = { cancelled: boolean; task?: RenderTask };\n\ntype PageRefs = {\n page: HTMLDivElement | null;\n canvas: HTMLCanvasElement | null;\n text: HTMLDivElement | null;\n};\n\n/**\n * Renders a PDF to stacked canvases, one per page, each with a selectable text layer over it.\n *\n * Replaces an `<iframe>` pointed at the browser's built-in viewer. That viewer lives in its own\n * origin — a `chrome-extension://` document — so nothing about it can be styled or driven. Owning\n * the render means the toolbar can page, fit and search, and the pages can be themed.\n *\n * The text layer is what makes the page more than a picture: it is transparent text positioned over\n * the canvas, so the browser can select it and search matches can be highlighted by wrapping them\n * in the layer's own spans rather than by positioning overlays over the page.\n *\n * Every page is rendered rather than one at a time: the iframe it replaces scrolled through the\n * whole document. Large documents therefore cost their full page count up front — virtualization is\n * the obvious next step if that becomes a problem.\n */\nexport const PdfCanvas = Util.composable<HTMLDivElement, PdfCanvasProps>(\n ({ url, fit = 'width', onLoad, onStateChange, apiRef, ...props }, forwardedRef) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const containerRef = useRef<HTMLDivElement>(null);\n const refs = useRef<PageRefs[]>([]);\n const documentRef = useRef<PDFDocumentProxy | undefined>(undefined);\n const textRef = useRef<PdfPageText[]>([]);\n /** Unscaled page dimensions, so a page can be sized before it is drawn. */\n const sizesRef = useRef<({ width: number; height: number } | undefined)[]>([]);\n /** Scale each page was last drawn at, so scrolling back does not re-rasterise it. */\n const renderedRef = useRef<(number | undefined)[]>([]);\n // The live query, so a search typed before the text index finished loading can be re-run once it\n // is ready. Without this, searching a large document during its first seconds silently returns\n // nothing and never corrects itself.\n const queryRef = useRef('');\n // The page a scroll is heading towards. Stepping reads this rather than the observed page, so\n // clicking next twice quickly advances two pages instead of re-targeting the same one — smooth\n // scrolling means the observed page has not moved yet on the second click.\n const targetRef = useRef<number | undefined>(undefined);\n // Read by the imperative navigation callbacks, which must stay stable for `useImperativeHandle`\n // while still seeing the current fit mode and page count.\n const fitRef = useRef<PdfFit>(fit);\n const pageCountRef = useRef(0);\n const [pageCount, setPageCount] = useState(0);\n const [currentPage, setCurrentPage] = useState(1);\n const [matches, setMatches] = useState<PdfMatch[]>([]);\n const [activeMatch, setActiveMatch] = useState(0);\n const [layoutVersion, setLayoutVersion] = useState(0);\n const [error, setError] = useState(false);\n\n // Tracks the in-flight render so a resize or an unmount can cancel it. Without this, tearing the\n // document down mid-render leaves `page.render(…).promise` and `getPage` rejecting with\n // `RenderingCancelledException` / `Transport destroyed` and nothing to catch them.\n const renderRef = useRef<RenderRun | undefined>(undefined);\n\n const cancelRender = useCallback(() => {\n const current = renderRef.current;\n if (current) {\n current.cancelled = true;\n current.task?.cancel();\n renderRef.current = undefined;\n }\n }, []);\n\n const getRefs = useCallback(\n (index: number): PageRefs => (refs.current[index] ??= { page: null, canvas: null, text: null }),\n [],\n );\n\n const runSearch = useCallback((query: string) => {\n const found = findMatches(textRef.current, query);\n setMatches(found);\n setActiveMatch(found.length > 0 ? 1 : 0);\n }, []);\n\n /** Page size at scale 1, resolved once so placeholders can be sized without rendering. */\n const sizePage = useCallback((index: number): { width: number; height: number } | undefined => {\n return sizesRef.current[index];\n }, []);\n\n /** Scale that fits a page to the container under the current fit mode. */\n const scaleFor = useCallback((size: { width: number; height: number }, fitMode: PdfFit): number => {\n const container = containerRef.current;\n if (!container) {\n return 1;\n }\n const widthScale = (container.clientWidth - PAGE_MARGIN * 2) / size.width;\n if (fitMode === 'width') {\n return widthScale;\n }\n // Fit-page must fit BOTH axes, which is what makes a landscape page usable: fitting width\n // alone leaves a wide page taller than the viewport and it reads as if fit did nothing.\n return Math.min(widthScale, (container.clientHeight - PAGE_MARGIN * 2) / size.height);\n }, []);\n\n // Renders one page at device resolution, so text stays sharp on a HiDPI display — a canvas\n // sized in CSS pixels renders visibly soft.\n const renderPage = useCallback(\n async (pdf: PDFDocumentProxy, pageNumber: number, fitMode: PdfFit, run: RenderRun) => {\n const { canvas, text } = getRefs(pageNumber - 1);\n const size = sizePage(pageNumber - 1);\n if (!canvas || !size || run.cancelled) {\n return;\n }\n\n const scale = scaleFor(size, fitMode);\n // Skip a page already drawn at this scale; scrolling back over it must not re-rasterise.\n if (renderedRef.current[pageNumber - 1] === scale) {\n return;\n }\n\n const page = await pdf.getPage(pageNumber);\n if (run.cancelled) {\n return;\n }\n\n const viewport = page.getViewport({ scale });\n const ratio = globalThis.devicePixelRatio ?? 1;\n canvas.width = Math.floor(viewport.width * ratio);\n canvas.height = Math.floor(viewport.height * ratio);\n canvas.style.width = `${Math.floor(viewport.width)}px`;\n canvas.style.height = `${Math.floor(viewport.height)}px`;\n\n const context = canvas.getContext('2d');\n if (!context) {\n return;\n }\n const task = page.render({ canvas, canvasContext: context, viewport, transform: [ratio, 0, 0, ratio, 0, 0] });\n run.task = task;\n await task.promise;\n if (run.cancelled) {\n return;\n }\n\n if (text) {\n text.replaceChildren();\n // `--total-scale-factor` is the property pdf.js' own stylesheet reads to size and place\n // each span; `--scale-factor` alone leaves every glyph at the default 16px.\n text.style.setProperty('--total-scale-factor', String(scale));\n text.style.setProperty('--scale-factor', String(scale));\n const { TextLayer } = await loadPdfjs();\n const layer = new TextLayer({\n textContentSource: await page.getTextContent(),\n container: text,\n viewport,\n });\n await layer.render();\n }\n\n renderedRef.current[pageNumber - 1] = scale;\n },\n [getRefs, scaleFor, sizePage],\n );\n\n /**\n * Renders the pages currently near the viewport.\n *\n * Only those, because rendering a whole document up front does not survive contact with a real\n * one: a few hundred pages is a few hundred rasterisations and text layers held at once, which\n * takes the tab down. Pages outside the window keep their placeholder box, so scroll position\n * and page numbering stay correct.\n *\n * Each page is isolated: pdf.js raises on individual pages (a JBIG2 image it cannot decode, a\n * broken font), and one such page must not stop every page after it from drawing.\n */\n const renderVisible = useCallback(\n async (pdf: PDFDocumentProxy, fitMode: PdfFit) => {\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n cancelRender();\n const run: RenderRun = { cancelled: false };\n renderRef.current = run;\n\n const origin = container.getBoundingClientRect();\n const window = origin.height * RENDER_WINDOW;\n const wanted: number[] = [];\n for (let index = 0; index < pdf.numPages; index++) {\n const element = refs.current[index]?.page;\n if (!element) {\n continue;\n }\n const rect = element.getBoundingClientRect();\n if (rect.bottom >= origin.top - window && rect.top <= origin.bottom + window) {\n wanted.push(index + 1);\n }\n }\n\n for (const pageNumber of wanted) {\n if (run.cancelled) {\n break;\n }\n try {\n await renderPage(pdf, pageNumber, fitMode, run);\n } catch (error) {\n if (run.cancelled) {\n break;\n }\n // Logged rather than thrown: a page pdf.js cannot draw is a property of the document,\n // and the rest of it is still worth reading.\n log.warn('failed to render page', { pageNumber, error });\n }\n }\n\n if (renderRef.current === run) {\n renderRef.current = undefined;\n }\n if (!run.cancelled) {\n // Highlights are measured from the rendered spans, so they can only be placed once the\n // text layers above exist at their final size.\n setLayoutVersion((version) => version + 1);\n }\n },\n [cancelRender, renderPage],\n );\n\n useEffect(() => {\n let cancelled = false;\n setError(false);\n setPageCount(0);\n setMatches([]);\n\n let task: PDFDocumentLoadingTask | undefined;\n void loadPdfjs().then(({ getDocument }) => {\n if (cancelled) {\n return;\n }\n task = getDocument({ url });\n return task.promise.then(\n async (pdf) => {\n // The loading task owns teardown; the cleanup below destroys it and the document with it.\n if (cancelled) {\n return;\n }\n documentRef.current = pdf;\n sizesRef.current = [];\n renderedRef.current = [];\n\n // Dimensions first, and only dimensions: they size every placeholder so the scrollbar is\n // honest from the outset, and they cost one cheap `getPage` each rather than a raster.\n for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {\n if (cancelled) {\n return;\n }\n const { width, height } = (await pdf.getPage(pageNumber)).getViewport({ scale: 1 });\n sizesRef.current[pageNumber - 1] = { width, height };\n }\n if (cancelled) {\n return;\n }\n setPageCount(pdf.numPages);\n onLoad?.(pdf.numPages);\n\n // Text of every page, resolved once. Searching re-reads it on every keystroke, and\n // `getTextContent` is a worker round-trip per page — fine once, not once per character.\n // After the sizes, so the document is on screen before the index is built.\n const pages: PdfPageText[] = [];\n for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {\n if (cancelled) {\n return;\n }\n try {\n const content = await (await pdf.getPage(pageNumber)).getTextContent();\n pages.push({ items: content.items.map((item) => ('str' in item ? item.str : '')) });\n } catch (error) {\n // A page whose text cannot be extracted is still worth showing; it just never matches.\n log.warn('failed to read page text', { pageNumber, error });\n pages.push({ items: [] });\n }\n }\n if (!cancelled) {\n textRef.current = pages;\n if (queryRef.current.trim()) {\n runSearch(queryRef.current);\n }\n }\n },\n () => {\n if (!cancelled) {\n setError(true);\n }\n },\n );\n });\n\n return () => {\n cancelled = true;\n // Cancel before destroying: `task.destroy()` tears down the transport, and any render still\n // awaiting it would reject with `Transport destroyed` after this effect is gone.\n cancelRender();\n void task?.destroy();\n documentRef.current = undefined;\n textRef.current = [];\n };\n // `onLoad` is deliberately not a dependency: callers pass an inline function, which would\n // re-fetch the document on every render.\n }, [url, cancelRender, runSearch]);\n\n // Runs after the canvases for `pageCount` exist, and again on resize or a fit change so pages\n // keep filling the available space.\n useEffect(() => {\n const pdf = documentRef.current;\n const container = containerRef.current;\n if (!pdf || !container || pageCount === 0) {\n return;\n }\n\n // A resize or fit change invalidates every drawn page, since the scale moved.\n renderedRef.current = [];\n void renderVisible(pdf, fit);\n\n // Scrolling brings new pages into the window; coalesced to a frame so a fling does not queue\n // one pass per scroll event.\n let frame = 0;\n const onScroll = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => {\n const current = documentRef.current;\n if (current) {\n void renderVisible(current, fit);\n }\n });\n };\n container.addEventListener('scroll', onScroll, { passive: true });\n\n const observer =\n typeof ResizeObserver === 'undefined'\n ? undefined\n : new ResizeObserver(() => {\n const current = documentRef.current;\n if (current) {\n renderedRef.current = [];\n void renderVisible(current, fit);\n }\n });\n observer?.observe(container);\n return () => {\n cancelAnimationFrame(frame);\n container.removeEventListener('scroll', onScroll);\n observer?.disconnect();\n cancelRender();\n };\n }, [pageCount, fit, currentPage, renderVisible, cancelRender]);\n\n // The page under the top of the viewport is \"current\" — the same rule the browser's own viewer\n // uses, and the one that matches what a reader considers the page they are on.\n useEffect(() => {\n const container = containerRef.current;\n // In fit-page the visible page IS `currentPage`, so deriving it from scroll would fight the\n // pager: there is no scrolling, and every page sits at the same offset.\n if (!container || pageCount === 0 || fit === 'page') {\n return;\n }\n\n // Measured from bounding rects rather than `offsetTop`: that is relative to the nearest\n // *positioned* ancestor, which is not necessarily this scroll container, and comparing it to\n // `scrollTop` reported the wrong page at rest.\n const onScroll = () => {\n const origin = container.getBoundingClientRect().top;\n let page = 1;\n for (let index = 0; index < pageCount; index++) {\n const element = refs.current[index]?.page;\n if (element && element.getBoundingClientRect().top - origin <= PAGE_MARGIN + 8) {\n page = index + 1;\n }\n }\n setCurrentPage(page);\n if (targetRef.current === page) {\n targetRef.current = undefined;\n }\n };\n\n container.addEventListener('scroll', onScroll, { passive: true });\n onScroll();\n return () => container.removeEventListener('scroll', onScroll);\n }, [pageCount, fit]);\n\n const goToPageByScroll = useCallback((page: number, behavior: ScrollBehavior = 'smooth') => {\n const element = refs.current[page - 1]?.page;\n const container = containerRef.current;\n if (element && container) {\n targetRef.current = page;\n // Relative scroll for the same reason `onScroll` measures rects: the page's `offsetTop` is\n // not necessarily expressed in this container's coordinates.\n const delta = element.getBoundingClientRect().top - container.getBoundingClientRect().top;\n container.scrollTo({ top: container.scrollTop + delta - PAGE_MARGIN, behavior });\n }\n }, []);\n\n const goToPage = useCallback((page: number, behavior: ScrollBehavior = 'smooth') => {\n const clamped = Math.min(Math.max(page, 1), pageCountRef.current || 1);\n // Fit-page shows one page at a time, so navigating is a state change; there is nothing to\n // scroll within.\n if (fitRef.current === 'page') {\n targetRef.current = undefined;\n setCurrentPage(clamped);\n return;\n }\n goToPageByScroll(clamped, behavior);\n }, []);\n\n const stepPage = useCallback(\n (delta: number) => {\n const base = targetRef.current ?? currentPage;\n goToPage(Math.min(Math.max(base + delta, 1), pageCount));\n },\n [currentPage, pageCount, goToPage],\n );\n\n const search = useCallback(\n (query: string) => {\n queryRef.current = query;\n runSearch(query);\n },\n [runSearch],\n );\n\n const goToMatch = useCallback(\n (index: number) => {\n if (matches.length === 0) {\n return;\n }\n // Wraps in both directions, so `next` on the last match returns to the first.\n const next = ((index - 1 + matches.length) % matches.length) + 1;\n setActiveMatch(next);\n goToPage(matches[next - 1].page);\n },\n [matches, goToPage],\n );\n\n useEffect(() => {\n fitRef.current = fit;\n }, [fit]);\n\n useEffect(() => {\n pageCountRef.current = pageCount;\n }, [pageCount]);\n\n useImperativeHandle(apiRef, () => ({ goToPage, stepPage, search, goToMatch }), [\n goToPage,\n stepPage,\n search,\n goToMatch,\n ]);\n\n // Rewrites the spans in place whenever the matches, the active one, or the layout change. The\n // layout dependency matters: a re-render replaces the spans, discarding any marks on them.\n useEffect(() => {\n refs.current.forEach((pageRefs, pageIndex) => {\n const layer = pageRefs?.text;\n const items = textRef.current[pageIndex]?.items;\n if (!layer || !items) {\n return;\n }\n items.forEach((text, itemIndex) => {\n // The layer alternates spans and `<br>`: pdf.js emits one child per text item, a `<br>`\n // for the end-of-line ones, so child index tracks item index. Skip the breaks explicitly\n // rather than relying on their empty text never matching.\n const span = layer.children[itemIndex];\n if (!(span instanceof HTMLElement) || span.tagName !== 'SPAN') {\n return;\n }\n const ranges = matches.flatMap((match, index) =>\n match.page === pageIndex + 1 && match.item === itemIndex\n ? [{ offset: match.offset, length: match.length, active: index + 1 === activeMatch }]\n : [],\n );\n markSpan(span, text, ranges);\n });\n });\n }, [matches, activeMatch, layoutVersion]);\n\n useEffect(() => {\n onStateChange?.({ pageCount, currentPage, matches: matches.length, activeMatch });\n // `onStateChange` is excluded for the same reason as `onLoad` above.\n }, [pageCount, currentPage, matches.length, activeMatch]);\n\n if (error) {\n return (\n <div {...Util.composableProps(props, { classNames: 'dx-fill overflow-auto' })} ref={forwardedRef}>\n <div role='alert' className='p-4 text-sm text-error-text'>\n {t('pdf-error.message')}\n </div>\n </div>\n );\n }\n\n const placeholderStyle = (index: number) => {\n const size = sizesRef.current[index];\n if (!size) {\n return undefined;\n }\n const scale = scaleFor(size, fit);\n return { width: Math.floor(size.width * scale), height: Math.floor(size.height * scale) };\n };\n\n // Fit-page is a paged view, not a scrolled one: the whole point of fitting both axes is that\n // exactly one page is on screen, so it is centred and the pager moves between pages rather than\n // scrolling within a column. Fit-width keeps the continuous column, where scrolling is the\n // natural motion.\n const single = fit === 'page';\n const shown = single ? [currentPage - 1] : Array.from({ length: pageCount }, (_, index) => index);\n\n return (\n <div\n data-pdf-canvas=''\n {...Util.composableProps(props, {\n classNames: [\n 'dx-fill bg-deck-surface select-text',\n single ? 'overflow-hidden grid place-items-center' : 'overflow-auto',\n ],\n })}\n // Two refs on one node: `containerRef` measures the available width for the page scale, and\n // `forwardedRef` belongs to whatever slotted this in.\n ref={Hooks.composeRefs(containerRef, forwardedRef)}\n >\n <div className={single ? 'contents' : 'flex flex-col items-center gap-4 py-4'}>\n {shown.map((index) => (\n <div\n key={index}\n data-page={index + 1}\n // Sized from the page's own dimensions rather than from its canvas, so a page outside\n // the render window still occupies its true height and the scrollbar does not lurch as\n // pages draw.\n className='relative w-fit shadow-md bg-white'\n style={placeholderStyle(index)}\n ref={(element) => {\n getRefs(index).page = element;\n }}\n >\n <canvas\n className='block'\n ref={(element) => {\n getRefs(index).canvas = element;\n }}\n />\n <div\n className='dx-pdf-text-layer'\n ref={(element) => {\n getRefs(index).text = element;\n }}\n />\n </div>\n ))}\n </div>\n </div>\n );\n },\n);\n\n/** Viewport heights rendered either side of the visible area, so a scroll finds pages ready. */\nconst RENDER_WINDOW = 1;\n\n/** Space around and between pages, in CSS pixels; matches the `gap-4`/`py-4` on the page column. */\nconst PAGE_MARGIN = 16;\n\nPdfCanvas.displayName = 'PdfCanvas';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext, useContext } from 'react';\n\nimport { type PdfApi, type PdfCanvasState, type PdfFit } from '../PdfCanvas/index.ts';\n\n// Kept out of `Preview.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\n/**\n * What the toolbar needs from a paged document. Present only while such a document is displayed —\n * an image has no pages to step through and no text to search.\n */\nexport type PreviewPaged = PdfCanvasState & {\n api: PdfApi | undefined;\n fit: PdfFit;\n setFit: (fit: PdfFit) => void;\n};\n\nexport type PreviewContextValue = {\n type: string;\n url: string;\n /** Attendable id of the surrounding article, when there is one; scopes the toolbar's shortcuts. */\n attendableId?: string;\n /** Filename, when known; used for the download affordance and the unsupported-type details. */\n name?: string;\n size?: number;\n paged?: PreviewPaged;\n setPaged: (paged: PreviewPaged | undefined) => void;\n};\n\nexport const PreviewContext = createContext<PreviewContextValue | undefined>(undefined);\n\nexport const usePreview = (consumer: string): PreviewContextValue => {\n const context = useContext(PreviewContext);\n if (!context) {\n throw new Error(`${consumer} must be used within a Preview.Root.`);\n }\n return context;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useHotkeys } from '@dxos/react-focus';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Media from '@dxos/react-ui/Media';\nimport * as Toolbar from '@dxos/react-ui/Toolbar';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { meta } from '#meta';\n\nimport { type PdfApi, PdfCanvas, type PdfCanvasState, type PdfFit } from '../PdfCanvas/index.ts';\nimport { PreviewContext, type PreviewPaged, usePreview } from './PreviewContext.ts';\n\n//\n// Root\n//\n\nexport type PreviewRootProps = {\n type: string;\n url: string;\n name?: string;\n size?: number;\n /** Attendable id of the surrounding article; without it the toolbar binds no shortcuts. */\n attendableId?: string;\n children?: React.ReactNode;\n};\n\n/**\n * Preview of a file's content, composed rather than configured: `Preview.Root` holds the source and\n * whatever the content learns about it, `Preview.Content` renders by media type, and\n * `Preview.Toolbar` sits above with controls.\n *\n * Split this way because the toolbar needs facts only the content discovers — a PDF's page count,\n * current page and search matches are known after pdf.js parses the document, not before — and a\n * single component taking a `toolbar` prop would have to lift that state through props it does not\n * own.\n *\n * The root renders no element of its own. Its parts are placed into slots that belong to whatever\n * surrounds them — `Panel.Toolbar` and `Panel.Content` in the file article — so a wrapper would\n * break the panel's grid by putting a div between it and its areas.\n */\nconst PreviewRoot = ({ type, url, name, size, attendableId, children }: PreviewRootProps) => {\n const [paged, setPaged] = useState<PreviewPaged | undefined>(undefined);\n const value = useMemo(\n () => ({ type, url, name, size, attendableId, paged, setPaged }),\n [type, url, name, size, attendableId, paged],\n );\n\n return <PreviewContext.Provider value={value}>{children}</PreviewContext.Provider>;\n};\n\nPreviewRoot.displayName = 'Preview.Root';\n\n//\n// Toolbar\n//\n\n/**\n * Controls above the content: page stepping, fit and search for a paged document, and a download\n * for every type. Always renders, so the row is reserved and the content does not shift when a\n * document finishes parsing and its controls appear.\n *\n * `composable` rather than a plain component: this is placed in `Panel.Toolbar asChild`, which\n * forwards its own className and ref through Slot, and a plain component would silently drop both.\n */\nconst PreviewToolbar = Util.composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const { name, url, paged, attendableId } = usePreview('Preview.Toolbar');\n const [query, setQuery] = useState('');\n const searchRef = useRef<HTMLInputElement>(null);\n const { hasAttention } = useAttention(attendableId);\n\n // Gated on the article having attention rather than bound globally, so the shortcut belongs to\n // whichever plank the reader is in — the same mechanism `useArticleKeyboardNavigation` uses.\n useHotkeys({\n id: `${attendableId}:search`,\n commands: [\n {\n hotkey: 'meta+f',\n label: t('search-shortcut.label'),\n enabled: () => !!attendableId && hasAttention && !!paged,\n // Reachable from anywhere in the article, including when focus is already in the field,\n // where selecting the text is the useful outcome.\n options: { enableOnFormTags: true, enableOnContentEditable: true },\n action: () => {\n searchRef.current?.focus();\n searchRef.current?.select();\n },\n },\n ],\n });\n\n const handleSearch = useCallback(\n (next: string) => {\n setQuery(next);\n paged?.api?.search(next);\n },\n [paged],\n );\n\n return (\n <Toolbar.Root {...Util.composableProps(props, { classNames: '@container' })} ref={forwardedRef}>\n {children}\n {paged && (\n <>\n <Button.Root\n iconOnly\n icon={paged.fit === 'width' ? 'ph--arrows-out-line-horizontal--regular' : 'ph--corners-out--regular'}\n label={paged.fit === 'width' ? t('fit-page.label') : t('fit-width.label')}\n onClick={() => paged.setFit(paged.fit === 'width' ? 'page' : 'width')}\n />\n <Button.Root\n compact\n iconOnly\n icon='ph--caret-line-left--regular'\n classNames='hidden @md:flex transition-none'\n label={t('first-page.label')}\n disabled={paged.currentPage <= 1}\n onClick={() => paged.api?.goToPage(1, 'instant')}\n />\n <Button.Root\n iconOnly\n icon='ph--caret-left--regular'\n label={t('previous-page.label')}\n disabled={paged.currentPage <= 1}\n onClick={() => paged.api?.stepPage(-1)}\n />\n {/* Two copies stacked in one grid cell: an invisible one of the widest value reserves the\n box, so stepping 9 → 10 does not shove the buttons beside it. Padding with spaces\n cannot do this (HTML collapses them) and zero-padding shows a leading zero.\n `Toolbar.Text` also truncates by default — right for a label, wrong for a counter. */}\n <Toolbar.Text classNames='grid justify-items-end shrink-0 overflow-visible text-nowrap text-sm tabular-nums'>\n <span aria-hidden className='invisible col-start-1 row-start-1'>\n {t('page-of.label', { page: paged.pageCount, count: paged.pageCount })}\n </span>\n <span className='col-start-1 row-start-1'>\n {t('page-of.label', { page: paged.currentPage, count: paged.pageCount })}\n </span>\n </Toolbar.Text>\n <Button.Root\n iconOnly\n icon='ph--caret-right--regular'\n label={t('next-page.label')}\n disabled={paged.currentPage >= paged.pageCount}\n onClick={() => paged.api?.stepPage(1)}\n />\n <Button.Root\n compact\n iconOnly\n icon='ph--caret-line-right--regular'\n classNames='hidden @md:flex transition-none'\n label={t('last-page.label')}\n disabled={paged.currentPage >= paged.pageCount}\n onClick={() => paged.api?.goToPage(paged.pageCount, 'instant')}\n />\n <Toolbar.Separator />\n <Field.Root>\n <Input.Root\n ref={searchRef}\n placeholder={t('search.placeholder')}\n value={query}\n classNames='grow'\n spellCheck={false}\n autoCorrect='off'\n autoCapitalize='off'\n end={<Icon.Icon icon='ph--magnifying-glass--regular' size='md' />}\n onChange={(event) => handleSearch(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n paged.api?.goToMatch(event.shiftKey ? paged.activeMatch - 1 : paged.activeMatch + 1);\n }\n }}\n />\n </Field.Root>\n {query.trim().length > 0 && (\n <>\n <Toolbar.Text classNames='shrink-0 overflow-visible text-nowrap tabular-nums'>\n {paged.matches === 0\n ? t('no-matches.label')\n : t('match-of.label', { match: paged.activeMatch, count: paged.matches })}\n </Toolbar.Text>\n <Button.Root\n iconOnly\n icon='ph--caret-up--regular'\n label={t('previous-match.label')}\n disabled={paged.matches === 0}\n onClick={() => paged.api?.goToMatch(paged.activeMatch - 1)}\n />\n <Button.Root\n iconOnly\n icon='ph--caret-down--regular'\n label={t('next-match.label')}\n disabled={paged.matches === 0}\n onClick={() => paged.api?.goToMatch(paged.activeMatch + 1)}\n />\n </>\n )}\n </>\n )}\n <Toolbar.Separator />\n {/* An anchor rather than a button: `download` is what makes the browser save instead of\n navigate, and it works for the `data:`/`blob:`/presigned URLs every backend produces. */}\n <Toolbar.Link href={url} download={name ?? true} aria-label={t('download.label')} title={t('download.label')}>\n <Icon.Icon icon='ph--download-simple--regular' size='lg' />\n </Toolbar.Link>\n </Toolbar.Root>\n );\n});\n\nPreviewToolbar.displayName = 'Preview.Toolbar';\n\n//\n// Content\n//\n\n/**\n * Renders the source by media type: PDFs through pdf.js, images/video/audio through `MediaPlayer`,\n * and anything else as a description of what it is — the download lives in the toolbar, where it is\n * available for every type rather than only the ones with no preview.\n *\n * `composable` for the same reason as the toolbar — it is placed in `Panel.Content asChild`.\n */\nconst PreviewContent = Util.composable<HTMLDivElement>((props, forwardedRef) => {\n const { t } = Hooks.useTranslation(meta.profile.key);\n const { type, url, name, size, setPaged } = usePreview('Preview.Content');\n const apiRef = useRef<PdfApi>(null);\n const [fit, setFit] = useState<PdfFit>('width');\n const [state, setState] = useState<PdfCanvasState>({ pageCount: 0, currentPage: 1, matches: 0, activeMatch: 0 });\n const { className } = Util.composableProps(props);\n const paged = type === 'application/pdf';\n\n // Publishes the paged controls up to the toolbar, and withdraws them when the content is no\n // longer a paged document so the toolbar does not offer page stepping for an image.\n useEffect(() => {\n if (!paged) {\n setPaged(undefined);\n return;\n }\n setPaged({ ...state, api: apiRef.current ?? undefined, fit, setFit });\n return () => setPaged(undefined);\n }, [paged, state, fit, setPaged]);\n\n if (paged) {\n return (\n <PdfCanvas\n classNames={className}\n url={url}\n fit={fit}\n apiRef={apiRef}\n onStateChange={setState}\n ref={forwardedRef}\n />\n );\n }\n\n if (type.startsWith('image/') || type.startsWith('video/') || type.startsWith('audio/')) {\n return (\n <div {...Util.composableProps(props, { classNames: 'grid dx-fill min-h-0' })} ref={forwardedRef}>\n <Media.Player\n classNames='dx-fill'\n src={url}\n // `kind` is set explicitly for audio and video because the URL is a `data:`/`blob:`/\n // presigned one with no usable extension, which is all `detectMediaKind` has to go on.\n // Images take no `kind` — `MediaKind` has no image member, and the unset path is `<img>`.\n kind={type.startsWith('audio/') ? 'audio' : type.startsWith('video/') ? 'video' : undefined}\n fit='contain'\n />\n </div>\n );\n }\n\n return (\n <div {...Util.composableProps(props, { classNames: 'grid place-items-center dx-fill p-8' })} ref={forwardedRef}>\n <Layout.Flex column align='center' gap='sm' classNames='text-center'>\n <Icon.Icon icon='ph--file--regular' size='xl' tone='subtle' />\n {name && <span className='text-sm'>{name}</span>}\n <span className='text-xs text-fg-subtle'>\n {size === undefined ? type : t('file-details.label', { type, size: formatBytes(size) })}\n </span>\n <span className='text-xs text-fg-subtle'>{t('no-preview.message')}</span>\n </Layout.Flex>\n </div>\n );\n});\n\nPreviewContent.displayName = 'Preview.Content';\n\nconst UNITS = ['B', 'kB', 'MB', 'GB'];\n\n/** Decimal units, matching what a file manager shows rather than what a disk reports. */\nconst formatBytes = (bytes: number): string => {\n let value = bytes;\n let unit = 0;\n while (value >= 1000 && unit < UNITS.length - 1) {\n value /= 1000;\n unit++;\n }\n return `${unit === 0 ? value : value.toFixed(1)} ${UNITS[unit]}`;\n};\n\nexport const Preview = {\n Root: PreviewRoot,\n Toolbar: PreviewToolbar,\n Content: PreviewContent,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../../../../src/components/Preview/Preview.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAsBjF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,WAAW;wDAAuD,gBAAgB;;CAQvF,CAAC;AA2PF,eAAO,MAAM,OAAO;IAClB,IAAI;IACJ,OAAO;;;IACP,OAAO;;;CACR,CAAC"}
1
+ {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../../../../src/components/Preview/Preview.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAuBjF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,WAAW;wDAAuD,gBAAgB;;CAQvF,CAAC;AA2PF,eAAO,MAAM,OAAO;IAClB,IAAI;IACJ,OAAO;;;IACP,OAAO;;;CACR,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileProperties.d.ts","sourceRoot":"","sources":["../../../../../src/containers/FileProperties/FileProperties.tsx"],"names":[],"mappings":"AAMA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAEhE,OAAO,KAAK,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAShE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAInC,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;uCAW/C,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,mBAAmB"}
1
+ {"version":3,"file":"FileProperties.d.ts","sourceRoot":"","sources":["../../../../../src/containers/FileProperties/FileProperties.tsx"],"names":[],"mappings":"AAMA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAEhE,OAAO,KAAK,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAUhE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAInC,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;uCAW/C,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,mBAAmB"}