@springbrand/file-renderers 0.0.0-stage → 0.1.0-alpha.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.
- package/README.md +266 -2
- package/dist/file-renderers/src/attachment-format.d.ts +17 -0
- package/dist/file-renderers/src/attachment-format.js +180 -0
- package/dist/file-renderers/src/attachment.d.ts +17 -0
- package/dist/file-renderers/src/attachment.js +96 -0
- package/dist/file-renderers/src/code/index.d.ts +19 -0
- package/dist/file-renderers/src/code/index.js +72 -0
- package/dist/file-renderers/src/code/mime.d.ts +4 -0
- package/dist/file-renderers/src/code/mime.js +48 -0
- package/dist/file-renderers/src/code/source.module.css +7 -0
- package/dist/file-renderers/src/config/index.d.ts +30 -0
- package/dist/file-renderers/src/config/index.js +88 -0
- package/dist/file-renderers/src/config/limits.d.ts +14 -0
- package/dist/file-renderers/src/config/limits.js +39 -0
- package/dist/file-renderers/src/config/mime.d.ts +4 -0
- package/dist/file-renderers/src/config/mime.js +25 -0
- package/dist/file-renderers/src/config/toml.d.ts +2 -0
- package/dist/file-renderers/src/config/toml.js +24 -0
- package/dist/file-renderers/src/config/xml.d.ts +2 -0
- package/dist/file-renderers/src/config/xml.js +59 -0
- package/dist/file-renderers/src/config/yaml.d.ts +10 -0
- package/dist/file-renderers/src/config/yaml.js +60 -0
- package/dist/file-renderers/src/contracts.d.ts +155 -0
- package/dist/file-renderers/src/contracts.js +25 -0
- package/dist/file-renderers/src/csv/csv.module.css +62 -0
- package/dist/file-renderers/src/csv/index.d.ts +48 -0
- package/dist/file-renderers/src/csv/index.js +345 -0
- package/dist/file-renderers/src/csv/mime.d.ts +4 -0
- package/dist/file-renderers/src/csv/mime.js +19 -0
- package/dist/file-renderers/src/docx/accessible-view.d.ts +4 -0
- package/dist/file-renderers/src/docx/accessible-view.js +90 -0
- package/dist/file-renderers/src/docx/index.d.ts +25 -0
- package/dist/file-renderers/src/docx/index.js +124 -0
- package/dist/file-renderers/src/docx/mime.d.ts +4 -0
- package/dist/file-renderers/src/docx/mime.js +16 -0
- package/dist/file-renderers/src/drawio/index.d.ts +16 -0
- package/dist/file-renderers/src/drawio/index.js +106 -0
- package/dist/file-renderers/src/drawio/mime.d.ts +6 -0
- package/dist/file-renderers/src/drawio/mime.js +24 -0
- package/dist/file-renderers/src/drawio/parse.d.ts +33 -0
- package/dist/file-renderers/src/drawio/parse.js +213 -0
- package/dist/file-renderers/src/excalidraw/index.d.ts +23 -0
- package/dist/file-renderers/src/excalidraw/index.js +128 -0
- package/dist/file-renderers/src/excalidraw/mime.d.ts +6 -0
- package/dist/file-renderers/src/excalidraw/mime.js +20 -0
- package/dist/file-renderers/src/file-format.d.ts +11 -0
- package/dist/file-renderers/src/file-format.js +117 -0
- package/dist/file-renderers/src/file-name.d.ts +2 -0
- package/dist/file-renderers/src/file-name.js +10 -0
- package/dist/file-renderers/src/glb/index.d.ts +17 -0
- package/dist/file-renderers/src/glb/index.js +108 -0
- package/dist/file-renderers/src/glb/mime.d.ts +6 -0
- package/dist/file-renderers/src/glb/mime.js +17 -0
- package/dist/file-renderers/src/glb/parse.d.ts +4 -0
- package/dist/file-renderers/src/glb/parse.js +170 -0
- package/dist/file-renderers/src/html/index.d.ts +15 -0
- package/dist/file-renderers/src/html/index.js +43 -0
- package/dist/file-renderers/src/html/mime.d.ts +4 -0
- package/dist/file-renderers/src/html/mime.js +20 -0
- package/dist/file-renderers/src/html/preview-project.d.ts +16 -0
- package/dist/file-renderers/src/html/preview-project.js +531 -0
- package/dist/file-renderers/src/html/preview.d.ts +8 -0
- package/dist/file-renderers/src/html/preview.js +29 -0
- package/dist/file-renderers/src/image/index.d.ts +20 -0
- package/dist/file-renderers/src/image/index.js +75 -0
- package/dist/file-renderers/src/image/mime.d.ts +5 -0
- package/dist/file-renderers/src/image/mime.js +34 -0
- package/dist/file-renderers/src/index.d.ts +27 -0
- package/dist/file-renderers/src/index.js +27 -0
- package/dist/file-renderers/src/json/index.d.ts +22 -0
- package/dist/file-renderers/src/json/index.js +69 -0
- package/dist/file-renderers/src/json/json.typecheck.d.ts +1 -0
- package/dist/file-renderers/src/json/json.typecheck.js +24 -0
- package/dist/file-renderers/src/json/jsonl.d.ts +15 -0
- package/dist/file-renderers/src/json/jsonl.js +43 -0
- package/dist/file-renderers/src/json/mime.d.ts +10 -0
- package/dist/file-renderers/src/json/mime.js +39 -0
- package/dist/file-renderers/src/json/parse.d.ts +52 -0
- package/dist/file-renderers/src/json/parse.js +179 -0
- package/dist/file-renderers/src/markdown/index.d.ts +19 -0
- package/dist/file-renderers/src/markdown/index.js +235 -0
- package/dist/file-renderers/src/markdown/mime.d.ts +3 -0
- package/dist/file-renderers/src/markdown/mime.js +12 -0
- package/dist/file-renderers/src/media/index.d.ts +25 -0
- package/dist/file-renderers/src/media/index.js +80 -0
- package/dist/file-renderers/src/media/mime.d.ts +8 -0
- package/dist/file-renderers/src/media/mime.js +61 -0
- package/dist/file-renderers/src/media/validate.d.ts +2 -0
- package/dist/file-renderers/src/media/validate.js +225 -0
- package/dist/file-renderers/src/mermaid/asset.d.ts +4 -0
- package/dist/file-renderers/src/mermaid/frame.d.ts +2 -0
- package/dist/file-renderers/src/mermaid/frame.js +65 -0
- package/dist/file-renderers/src/mermaid/index.d.ts +16 -0
- package/dist/file-renderers/src/mermaid/index.js +44 -0
- package/dist/file-renderers/src/mermaid/mime.d.ts +6 -0
- package/dist/file-renderers/src/mermaid/mime.js +19 -0
- package/dist/file-renderers/src/notebook/index.d.ts +24 -0
- package/dist/file-renderers/src/notebook/index.js +65 -0
- package/dist/file-renderers/src/notebook/mime.d.ts +4 -0
- package/dist/file-renderers/src/notebook/mime.js +9 -0
- package/dist/file-renderers/src/notebook/notebook.module.css +82 -0
- package/dist/file-renderers/src/notebook/parse.d.ts +33 -0
- package/dist/file-renderers/src/notebook/parse.js +138 -0
- package/dist/file-renderers/src/office/preview.d.ts +19 -0
- package/dist/file-renderers/src/office/preview.js +69 -0
- package/dist/file-renderers/src/office/preview.module.css +143 -0
- package/dist/file-renderers/src/office/test-fixture.d.ts +1 -0
- package/dist/file-renderers/src/office/test-fixture.js +8 -0
- package/dist/file-renderers/src/pdf/index.d.ts +23 -0
- package/dist/file-renderers/src/pdf/index.js +95 -0
- package/dist/file-renderers/src/pdf/mime.d.ts +4 -0
- package/dist/file-renderers/src/pdf/mime.js +17 -0
- package/dist/file-renderers/src/pdf/pdf.module.css +6 -0
- package/dist/file-renderers/src/ppt/index.d.ts +21 -0
- package/dist/file-renderers/src/ppt/index.js +69 -0
- package/dist/file-renderers/src/ppt/mime.d.ts +4 -0
- package/dist/file-renderers/src/ppt/mime.js +16 -0
- package/dist/file-renderers/src/preview-content.d.ts +20 -0
- package/dist/file-renderers/src/preview-content.js +128 -0
- package/dist/file-renderers/src/preview-error-boundary.d.ts +16 -0
- package/dist/file-renderers/src/preview-error-boundary.js +20 -0
- package/dist/file-renderers/src/preview-host.d.ts +97 -0
- package/dist/file-renderers/src/preview-host.js +358 -0
- package/dist/file-renderers/src/preview-surface.d.ts +26 -0
- package/dist/file-renderers/src/preview-surface.js +61 -0
- package/dist/file-renderers/src/registry.d.ts +31 -0
- package/dist/file-renderers/src/registry.js +29 -0
- package/dist/file-renderers/src/static-svg-frame.d.ts +4 -0
- package/dist/file-renderers/src/static-svg-frame.js +14 -0
- package/dist/file-renderers/src/structured-tree/tree.d.ts +22 -0
- package/dist/file-renderers/src/structured-tree/tree.js +62 -0
- package/dist/file-renderers/src/structured-tree/tree.module.css +157 -0
- package/dist/file-renderers/src/styles.d.ts +6 -0
- package/dist/file-renderers/src/svg/index.d.ts +17 -0
- package/dist/file-renderers/src/svg/index.js +43 -0
- package/dist/file-renderers/src/svg/mime.d.ts +6 -0
- package/dist/file-renderers/src/svg/mime.js +17 -0
- package/dist/file-renderers/src/svg/parse.d.ts +9 -0
- package/dist/file-renderers/src/svg/parse.js +218 -0
- package/dist/file-renderers/src/svg/svg.typecheck.d.ts +1 -0
- package/dist/file-renderers/src/svg/svg.typecheck.js +16 -0
- package/dist/file-renderers/src/wasm/index.d.ts +17 -0
- package/dist/file-renderers/src/wasm/index.js +44 -0
- package/dist/file-renderers/src/wasm/mime.d.ts +6 -0
- package/dist/file-renderers/src/wasm/mime.js +17 -0
- package/dist/file-renderers/src/wasm/parse.d.ts +16 -0
- package/dist/file-renderers/src/wasm/parse.js +154 -0
- package/dist/file-renderers/src/wasm/wasm.typecheck.d.ts +1 -0
- package/dist/file-renderers/src/wasm/wasm.typecheck.js +16 -0
- package/dist/file-renderers/src/xlsx/accessible-view.d.ts +4 -0
- package/dist/file-renderers/src/xlsx/accessible-view.js +181 -0
- package/dist/file-renderers/src/xlsx/index.d.ts +21 -0
- package/dist/file-renderers/src/xlsx/index.js +102 -0
- package/dist/file-renderers/src/xlsx/mime.d.ts +4 -0
- package/dist/file-renderers/src/xlsx/mime.js +16 -0
- package/dist/file-renderers/src/zip/index.d.ts +22 -0
- package/dist/file-renderers/src/zip/index.js +88 -0
- package/dist/file-renderers/src/zip/mime.d.ts +4 -0
- package/dist/file-renderers/src/zip/mime.js +10 -0
- package/dist/file-renderers/src/zip/parse.d.ts +17 -0
- package/dist/file-renderers/src/zip/parse.js +54 -0
- package/dist/utils/src/attachment.d.ts +12 -0
- package/dist/utils/src/attachment.js +50 -0
- package/dist/utils/src/base64.d.ts +3 -0
- package/dist/utils/src/base64.js +27 -0
- package/dist/utils/src/content-hash.d.ts +5 -0
- package/dist/utils/src/content-hash.js +27 -0
- package/dist/utils/src/database.d.ts +1 -0
- package/dist/utils/src/database.js +9 -0
- package/dist/utils/src/html.d.ts +2 -0
- package/dist/utils/src/html.js +9 -0
- package/dist/utils/src/image.d.ts +33 -0
- package/dist/utils/src/image.js +35 -0
- package/dist/utils/src/index.d.ts +12 -0
- package/dist/utils/src/index.js +12 -0
- package/dist/utils/src/ip-address.d.ts +6 -0
- package/dist/utils/src/ip-address.js +11 -0
- package/dist/utils/src/json.d.ts +6 -0
- package/dist/utils/src/json.js +18 -0
- package/dist/utils/src/ooxml.d.ts +14 -0
- package/dist/utils/src/ooxml.js +170 -0
- package/dist/utils/src/serialized-size.d.ts +9 -0
- package/dist/utils/src/serialized-size.js +11 -0
- package/dist/utils/src/strict-zip.d.ts +68 -0
- package/dist/utils/src/strict-zip.js +177 -0
- package/dist/utils/src/uuid.d.ts +9 -0
- package/dist/utils/src/uuid.js +27 -0
- package/node_modules/@silurus/ooxml/LICENSE +21 -0
- package/node_modules/@silurus/ooxml/README.md +1169 -0
- package/node_modules/@silurus/ooxml/THIRD_PARTY_NOTICES.md +369 -0
- package/node_modules/@silurus/ooxml/dist/assets/render-worker-1uPAxMZr.js +31 -0
- package/node_modules/@silurus/ooxml/dist/assets/render-worker-BlvJybji.js +10 -0
- package/node_modules/@silurus/ooxml/dist/assets/render-worker-CzmiJfFL.js +11 -0
- package/node_modules/@silurus/ooxml/dist/bounded-raw-part-cache-Cg1BsSZQ.js +119 -0
- package/node_modules/@silurus/ooxml/dist/canvas-viewer-mechanics-DbLz0rYW.js +1384 -0
- package/node_modules/@silurus/ooxml/dist/chart-ex.mjs +982 -0
- package/node_modules/@silurus/ooxml/dist/chunk-DmhlhrBa.js +11 -0
- package/node_modules/@silurus/ooxml/dist/cjk-fallback-bLI3z70h.js +46 -0
- package/node_modules/@silurus/ooxml/dist/comment-occurrence-C8Y2uoVr.js +6 -0
- package/node_modules/@silurus/ooxml/dist/comment-ui-runtime-BsK8pmUT.js +2 -0
- package/node_modules/@silurus/ooxml/dist/comment-ui-runtime-CWGqn5Vr.js +112 -0
- package/node_modules/@silurus/ooxml/dist/comment-ui-runtime-DcJOYlw-.js +171 -0
- package/node_modules/@silurus/ooxml/dist/comments-Diid5ujN.js +246 -0
- package/node_modules/@silurus/ooxml/dist/delimited-text-worker-host-BVY-Ecni.js +27 -0
- package/node_modules/@silurus/ooxml/dist/document-pull-client-B9PTM-pL.js +27280 -0
- package/node_modules/@silurus/ooxml/dist/docx-BF9WXrYH.js +3103 -0
- package/node_modules/@silurus/ooxml/dist/docx.mjs +7 -0
- package/node_modules/@silurus/ooxml/dist/docx_parser_bg.wasm +0 -0
- package/node_modules/@silurus/ooxml/dist/dom-interaction-boundary-Cnm8DSBN.js +223 -0
- package/node_modules/@silurus/ooxml/dist/highlight-rect-C5hkRqUr.js +13 -0
- package/node_modules/@silurus/ooxml/dist/hyperlink-Cxzhet30.js +36446 -0
- package/node_modules/@silurus/ooxml/dist/index.mjs +4 -0
- package/node_modules/@silurus/ooxml/dist/line-distribute-D1WNHKz_.js +1023 -0
- package/node_modules/@silurus/ooxml/dist/line-metrics-B60C86uh.js +60 -0
- package/node_modules/@silurus/ooxml/dist/math.mjs +56 -0
- package/node_modules/@silurus/ooxml/dist/mathjax-DoyWZbSp.js +21 -0
- package/node_modules/@silurus/ooxml/dist/mathjax-stix2.js +15 -0
- package/node_modules/@silurus/ooxml/dist/node.mjs +1327 -0
- package/node_modules/@silurus/ooxml/dist/pixel-budget-9P63Bsk5.js +37 -0
- package/node_modules/@silurus/ooxml/dist/plot-area-frame-DLTYKHNP.js +4354 -0
- package/node_modules/@silurus/ooxml/dist/pptx-DkyYBYoL.js +3065 -0
- package/node_modules/@silurus/ooxml/dist/pptx.mjs +7 -0
- package/node_modules/@silurus/ooxml/dist/pptx_parser_bg.wasm +0 -0
- package/node_modules/@silurus/ooxml/dist/raster-target-ojDdQizC.js +91 -0
- package/node_modules/@silurus/ooxml/dist/read-only-comment-decoration-Dz7eTQ4d.js +96 -0
- package/node_modules/@silurus/ooxml/dist/read-only-comment-margin-DkgzASAl.js +189 -0
- package/node_modules/@silurus/ooxml/dist/region-map.mjs +12714 -0
- package/node_modules/@silurus/ooxml/dist/render-D3iqVKCo.js +69 -0
- package/node_modules/@silurus/ooxml/dist/render-worker-host-B5wQfB7U.js +9 -0
- package/node_modules/@silurus/ooxml/dist/render-worker-host-D4123J27.js +9 -0
- package/node_modules/@silurus/ooxml/dist/render-worker-host-Di3LxLc0.js +9 -0
- package/node_modules/@silurus/ooxml/dist/renderer-B4GZb5We.js +6492 -0
- package/node_modules/@silurus/ooxml/dist/renderer-module-contract-D91rkYHH.js +32 -0
- package/node_modules/@silurus/ooxml/dist/resource-measurement-SHQCbkh2.js +150 -0
- package/node_modules/@silurus/ooxml/dist/session-BGq81gRQ.js +743 -0
- package/node_modules/@silurus/ooxml/dist/slide-pull-client-DiPnm240.js +6253 -0
- package/node_modules/@silurus/ooxml/dist/source-key-DIJG8BA0.js +42 -0
- package/node_modules/@silurus/ooxml/dist/three-d-DJBl8g8M.js +1162 -0
- package/node_modules/@silurus/ooxml/dist/three-d.mjs +2767 -0
- package/node_modules/@silurus/ooxml/dist/tiff.mjs +710 -0
- package/node_modules/@silurus/ooxml/dist/transfer-p6iCC2R7.js +6 -0
- package/node_modules/@silurus/ooxml/dist/types/chart-ex.d.ts +1087 -0
- package/node_modules/@silurus/ooxml/dist/types/docx.d.ts +2701 -0
- package/node_modules/@silurus/ooxml/dist/types/index.d.ts +5031 -0
- package/node_modules/@silurus/ooxml/dist/types/math.d.ts +16 -0
- package/node_modules/@silurus/ooxml/dist/types/node.d.ts +3255 -0
- package/node_modules/@silurus/ooxml/dist/types/pptx.d.ts +2419 -0
- package/node_modules/@silurus/ooxml/dist/types/region-map.d.ts +1087 -0
- package/node_modules/@silurus/ooxml/dist/types/three-d.d.ts +1087 -0
- package/node_modules/@silurus/ooxml/dist/types/tiff.d.ts +19 -0
- package/node_modules/@silurus/ooxml/dist/types/xlsx.d.ts +2709 -0
- package/node_modules/@silurus/ooxml/dist/units-BzZ0gAxs.js +4 -0
- package/node_modules/@silurus/ooxml/dist/visible-index-BgiHEc1z.js +17 -0
- package/node_modules/@silurus/ooxml/dist/worksheet-pull-client-BM8ywvbu.js +458 -0
- package/node_modules/@silurus/ooxml/dist/xlsx-C9NVjgaV.js +8484 -0
- package/node_modules/@silurus/ooxml/dist/xlsx.mjs +7 -0
- package/node_modules/@silurus/ooxml/dist/xlsx_parser_bg.wasm +0 -0
- package/node_modules/@silurus/ooxml/package.json +155 -0
- package/package.json +270 -4
package/README.md
CHANGED
|
@@ -1,3 +1,267 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@springbrand/file-renderers`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The shared semantic renderer boundary for Workspace files in SP's first-class
|
|
4
|
+
File Canvas.
|
|
5
|
+
|
|
6
|
+
This package contains the reviewed contracts, typed registry, and the first
|
|
7
|
+
read-only Markdown, code, image, SVG, HTML, media, CSV, JSON, JSONL, YAML,
|
|
8
|
+
TOML, XML, Notebook, ZIP, PDF, DOCX, XLSX, PPTX, GLB, WASM, Drawio,
|
|
9
|
+
Excalidraw, and Mermaid
|
|
10
|
+
renderers. SP's File Canvas consumes them through its Workspace host adapter.
|
|
11
|
+
The package is designed for the observation phase while keeping a typed
|
|
12
|
+
boundary for later editing.
|
|
13
|
+
|
|
14
|
+
## Ownership
|
|
15
|
+
|
|
16
|
+
- SP/Rapp owns the File Canvas controller and consumes the shared renderer
|
|
17
|
+
implementations.
|
|
18
|
+
- This package owns Workspace file format semantics only.
|
|
19
|
+
- `fileFormatFor` selects a renderer format; `fileContentKindFor` classifies
|
|
20
|
+
code and JSON-like files without expanding renderer support.
|
|
21
|
+
Compound formats such as `.excalidraw.json` and `.drawio.xml` keep their
|
|
22
|
+
renderer identity before generic suffix classification.
|
|
23
|
+
- Chat Attachment admission consumes this package's `attachmentFormatFor` and
|
|
24
|
+
`validateAttachmentBytes` boundary. A non-`unknown` Renderer format is
|
|
25
|
+
uploadable; HEIC/HEIF are input-only browser conversion aliases that become
|
|
26
|
+
JPEG before upload.
|
|
27
|
+
- Workspace/Space owns authorization, file versions, locks, leases, and writes.
|
|
28
|
+
- The package never accesses Workspace HTTP, WebSocket, tokens, or storage.
|
|
29
|
+
|
|
30
|
+
The former Doop iframe projection and protocol have been removed. SP owns the
|
|
31
|
+
only production File Canvas, using its existing Severn connection and Workspace
|
|
32
|
+
client. Renderer behavior is integrated through the SP Canvas controller, not
|
|
33
|
+
through a second runtime.
|
|
34
|
+
|
|
35
|
+
Workspace identity and version facts belong to SP/Space. The Canvas controller
|
|
36
|
+
keeps the authoritative snapshot next to the parsed renderer document instead
|
|
37
|
+
of copying those facts into this package. `RendererInput`, document models, and
|
|
38
|
+
resource requests therefore contain no Workspace scope, path, resource ID,
|
|
39
|
+
content version, or mutation revision.
|
|
40
|
+
|
|
41
|
+
## Observation flow
|
|
42
|
+
|
|
43
|
+
Every renderer is bound to one format and one model type:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface FileRenderer<
|
|
47
|
+
F extends FileRendererFormat,
|
|
48
|
+
Model extends RendererModel<F>,
|
|
49
|
+
> {
|
|
50
|
+
readonly format: F;
|
|
51
|
+
parse(
|
|
52
|
+
input: RendererInput<F>,
|
|
53
|
+
context: RendererParseContext,
|
|
54
|
+
): Promise<ParsedDocument<F, Model>>;
|
|
55
|
+
render(
|
|
56
|
+
document: ParsedDocument<F, Model>,
|
|
57
|
+
context: RendererRenderContext,
|
|
58
|
+
): ReactNode;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`RendererInput<F>` contains only format semantics: format, name, MIME type, and
|
|
63
|
+
payload. It binds the payload encoding to the format. The registry keeps that
|
|
64
|
+
format and model relationship when a renderer is looked up. A Markdown renderer
|
|
65
|
+
cannot receive an XLSX input or render a PDF document at the type boundary.
|
|
66
|
+
|
|
67
|
+
The parsed model is a discriminated union:
|
|
68
|
+
|
|
69
|
+
- `structure: "frame"` carries renderer-specific frame data and never has a
|
|
70
|
+
component `root`.
|
|
71
|
+
- `structure: "structured"` carries a component tree and never has a frame
|
|
72
|
+
payload.
|
|
73
|
+
|
|
74
|
+
A format may choose either shape for a particular renderer. A PDF renderer can
|
|
75
|
+
start as a Frame renderer and a later version can provide a structured page
|
|
76
|
+
model without changing the package-wide contract.
|
|
77
|
+
|
|
78
|
+
`createFileRendererRegistry()` builds an immutable registry from an ordered
|
|
79
|
+
renderer list. It preserves the concrete format/model pair at typed lookups,
|
|
80
|
+
copies the registration order, and rejects duplicate formats instead of making
|
|
81
|
+
the winner depend on import order.
|
|
82
|
+
|
|
83
|
+
Parsing has one lifecycle contract: `parse()` resolves a `ParsedDocument`,
|
|
84
|
+
observes the host `AbortSignal`, or throws. Request generation, stale-result
|
|
85
|
+
discarding, retries, error presentation, and snapshot caching remain in the SP
|
|
86
|
+
Canvas controller; the shared package does not create a parallel task state
|
|
87
|
+
machine.
|
|
88
|
+
|
|
89
|
+
## Host boundary
|
|
90
|
+
|
|
91
|
+
Parsing and rendering receive a host context containing:
|
|
92
|
+
|
|
93
|
+
- `signal`, so a superseded parse or resource request can be cancelled;
|
|
94
|
+
- `basePath`, the Workspace-relative resolution base;
|
|
95
|
+
- `resources.resolve()`, the only way a renderer asks for linked resources.
|
|
96
|
+
|
|
97
|
+
The shared renderer classifies references and their purpose (`image`,
|
|
98
|
+
`stylesheet`, `script`, and so on). The host creates a resolver already bound to
|
|
99
|
+
the authoritative file snapshot. That resolver owns authentication, Workspace
|
|
100
|
+
access, authorization, caching, and the decision to return a safe URL,
|
|
101
|
+
text/bytes, `missing`, or `blocked`. Renderers never call Workspace APIs
|
|
102
|
+
directly, and hosts do not duplicate format-specific reference parsing. The
|
|
103
|
+
Markdown renderer preserves relative `href` and `src` references through its
|
|
104
|
+
Streamdown pipeline before resolving them through this boundary, so the browser
|
|
105
|
+
cannot reinterpret them against the SP page origin. Notebook Markdown cells
|
|
106
|
+
reuse the same renderer and resolver context.
|
|
107
|
+
|
|
108
|
+
Attachment admission and Renderer parsing are deliberately separate stages.
|
|
109
|
+
The API validates authoritative bytes enough to reject format mismatches,
|
|
110
|
+
unsafe OOXML containers, and invalid binary signatures, then stores the
|
|
111
|
+
canonical media type. A malformed but recognized text document may still be
|
|
112
|
+
uploaded so its Renderer can return a source-preserving parse diagnostic. The
|
|
113
|
+
Agent's optional Understanding pipeline remains narrower than upload or
|
|
114
|
+
preview support and does not mark unsupported evidence as a failed upload.
|
|
115
|
+
|
|
116
|
+
## One capability source
|
|
117
|
+
|
|
118
|
+
`ParsedDocument.capability` is the only capability declaration. There is no
|
|
119
|
+
second `lockScope` field on the model, no static capability copy on the
|
|
120
|
+
renderer, and no separate output capability object.
|
|
121
|
+
|
|
122
|
+
The capability is one of:
|
|
123
|
+
|
|
124
|
+
- `mode: "observe"` for the current read-only phase;
|
|
125
|
+
- `mode: "edit", lock.scope: "frame"` with a whole-document serialization
|
|
126
|
+
contract;
|
|
127
|
+
- `mode: "edit", lock.scope: "node"` for a structured model only.
|
|
128
|
+
|
|
129
|
+
The node branch is structurally required to provide all four prerequisites:
|
|
130
|
+
|
|
131
|
+
1. stable node identity;
|
|
132
|
+
2. a serialization boundary locator and serializer;
|
|
133
|
+
3. comparable node versions;
|
|
134
|
+
4. a disjoint-mutation checker.
|
|
135
|
+
|
|
136
|
+
The type system cannot manufacture a node capability from boolean flags, and a
|
|
137
|
+
Frame model cannot receive the node capability. These interfaces reserve the
|
|
138
|
+
future edit seam; they do not acquire locks, call CAS, write files, or decide
|
|
139
|
+
whether an operation is authorized.
|
|
140
|
+
|
|
141
|
+
## Format slots
|
|
142
|
+
|
|
143
|
+
Markdown, code, image, SVG, HTML, media, CSV, PDF, DOCX, XLSX, PPTX, GLB,
|
|
144
|
+
WASM, Drawio, Excalidraw, Mermaid, JSON, JSONL, YAML, TOML, XML, Notebook,
|
|
145
|
+
and ZIP are implemented as read-only renderers following SP's current preview
|
|
146
|
+
semantics.
|
|
147
|
+
SP's live Canvas and revision history use the same preview surface for every
|
|
148
|
+
implemented format. The host retains the exact text payload for Preview/Source
|
|
149
|
+
switching in both views, including syntax-highlighted Code; binary formats
|
|
150
|
+
offer the original download without fabricating text Source. The host's model
|
|
151
|
+
map is checked against every `FileRendererFormat`, and dispatch is exhaustive
|
|
152
|
+
for recognized formats so a new slot cannot silently fall back to a blank Canvas.
|
|
153
|
+
Most deliberately use Frame models until stable structured identity and
|
|
154
|
+
lossless serialization are reviewed. Valid JSON has a source-preserving
|
|
155
|
+
structured preview, invalid JSON a diagnostic Frame, and JSONL a per-record
|
|
156
|
+
Frame; neither advertises node editing. The code renderer recognizes Python,
|
|
157
|
+
TypeScript/TSX, JavaScript/JSX, CSS/SCSS, SQL, and Bash while preserving exact
|
|
158
|
+
source. SVG is a separate text format with a static sanitized image preview
|
|
159
|
+
and exact original Source in both Canvas views. WASM is a binary metadata-only
|
|
160
|
+
preview with download, never generated source or module execution. Other text
|
|
161
|
+
and binary formats continue through the host fallback. The shared
|
|
162
|
+
`isBinaryFileFormat()` policy lets current and historical Canvas reads agree
|
|
163
|
+
on payload encoding. DOCX, XLSX, and PPTX use SP's shared strict OOXML guard
|
|
164
|
+
before retaining original bytes in read-only Frames. Each format then loads its
|
|
165
|
+
`@silurus/ooxml` Canvas viewer from a separate entry point when mounted. DOCX
|
|
166
|
+
and XLSX reuse their viewer's parsed model for a semantic HTML companion:
|
|
167
|
+
DOCX exposes headings, lists, tables, and validated HTTP(S) links; XLSX exposes
|
|
168
|
+
worksheet tables with row and column headers. The former separate extractors
|
|
169
|
+
remain removed. Viewer-owned workers and canvases are destroyed on unmount or
|
|
170
|
+
render cancellation; failures show a download fallback.
|
|
171
|
+
PDF uses PDF.js for a local canvas viewer and reports interactive forms,
|
|
172
|
+
attachments, JavaScript, and rich-media objects as static-preview limitations;
|
|
173
|
+
it never executes or exposes those embedded objects.
|
|
174
|
+
SP bounds both current and historical previews of these formats to 20 MiB
|
|
175
|
+
before parsing. PPTX uses the `ppt` slot only for modern `.pptx` files and does
|
|
176
|
+
not attempt legacy `.ppt` decoding. The viewers render locally without
|
|
177
|
+
resolving Workspace resources, media playback, or remote fonts. DOCX allows
|
|
178
|
+
validated HTTP(S) links; XLSX and PPTX hyperlinks remain disabled. The shared
|
|
179
|
+
OOXML guard remains a separate security inspection, not a second display
|
|
180
|
+
engine; viewer resource budgets match its ZIP limits.
|
|
181
|
+
Other directories reserve sibling format slots; a slot is not an implementation
|
|
182
|
+
promise until its parser, security policy, resource behavior, model,
|
|
183
|
+
serialization, and tests are reviewed.
|
|
184
|
+
|
|
185
|
+
HTML uses the shared bounded resource projector for both live and historical
|
|
186
|
+
previews. The live view reads linked assets through the Chat-scoped Workspace
|
|
187
|
+
client; history resolves the same links only within the selected revision's
|
|
188
|
+
manifest root, never from the current Workspace. The host keeps its sandboxed,
|
|
189
|
+
opaque-origin preview separate from the exact original Source and download.
|
|
190
|
+
Switching files or revisions aborts outstanding linked-resource requests.
|
|
191
|
+
|
|
192
|
+
Format selection is suffix-first. A supported filename suffix is authoritative;
|
|
193
|
+
MIME is used as a fallback when the filename has no recognized suffix, including
|
|
194
|
+
an unknown suffix. Renderer parsing still validates the actual payload before a
|
|
195
|
+
preview is shown, so MIME metadata never bypasses a renderer's content checks.
|
|
196
|
+
CSV additionally detects UTF-8 and UTF-16 with or without a BOM, rejects invalid
|
|
197
|
+
or unreliable encodings, and recognizes comma, semicolon, tab, and pipe
|
|
198
|
+
delimiters from consistent parsed rows. Ambiguous or low-confidence multi-column
|
|
199
|
+
input fails rather than being silently split with the wrong delimiter; a file
|
|
200
|
+
that contains no reliable delimiter remains a single column.
|
|
201
|
+
JSON and JSONL use the same parser and tree view but separate format contracts.
|
|
202
|
+
The paged tree presentation is also reused by configuration formats and ZIP;
|
|
203
|
+
each format keeps its own parsing and identity semantics.
|
|
204
|
+
The parser preserves duplicate keys and exact numeric source tokens; it bounds
|
|
205
|
+
JSON to 512 KiB and JSONL to 2 MiB. SP's existing bounded read path applies
|
|
206
|
+
those limits to both live and historical previews. Specialized
|
|
207
|
+
`.excalidraw.json` continues to take precedence over generic `.json`.
|
|
208
|
+
YAML, TOML, and XML share the JSON tree's paged presentation, not its parser or
|
|
209
|
+
model. YAML preserves source ranges without expanding aliases; TOML keeps large
|
|
210
|
+
integers as BigInt and does not invent per-value source ranges; XML is displayed
|
|
211
|
+
as inert text nodes with DOCTYPE and processing instructions rejected. All three
|
|
212
|
+
have a 512 KiB limit, bounded tree depth and node count, and diagnostic Frames
|
|
213
|
+
for invalid files. Their component IDs are view-local only: node editing and
|
|
214
|
+
lossless structured serialization are not claimed.
|
|
215
|
+
|
|
216
|
+
The Notebook renderer accepts `.ipynb` or an extensionless notebook MIME as
|
|
217
|
+
bounded JSON text. It reuses the package's Markdown, Code, and raster Image
|
|
218
|
+
surfaces for cells and safe outputs, while never running code or rendering rich
|
|
219
|
+
HTML/SVG outputs. It caps the input at 2 MiB, displays at most 200 cells and
|
|
220
|
+
20 outputs per cell, and bounds displayed text and embedded images. Both Canvas
|
|
221
|
+
views offer exact Source and the original download.
|
|
222
|
+
|
|
223
|
+
The ZIP renderer accepts `.zip` or an extensionless ZIP MIME as binary bytes.
|
|
224
|
+
It reuses SP's strict ZIP inspector for path, entry, CRC, compression, and
|
|
225
|
+
expanded-size checks. A 10 MiB input limit, 1,000 entries, 64 MiB total
|
|
226
|
+
expansion, and 16-level path ceiling bound the preview. Its Frame contains
|
|
227
|
+
only directory paths and sizes, presented through the shared paged tree; it
|
|
228
|
+
never keeps entry contents, extracts into the Workspace, or executes files.
|
|
229
|
+
ZIP has no text Source tab; the original archive remains downloadable. Invalid
|
|
230
|
+
and excessive archives show diagnostic Frames instead of partial listings.
|
|
231
|
+
|
|
232
|
+
The media renderer accepts browser-native audio and video formats as binary Frame
|
|
233
|
+
models. It uses local Blob URLs for the lifetime of the rendered component, keeps
|
|
234
|
+
native playback controls enabled, and never starts playback automatically. Media
|
|
235
|
+
bytes are not decoded by the package and no remote resource is resolved.
|
|
236
|
+
|
|
237
|
+
The GLB renderer accepts only self-contained glTF 2.0 binary models. It
|
|
238
|
+
validates bounded GLB chunks and embedded references before lazily loading
|
|
239
|
+
`<model-viewer>` with camera controls. SP limits current and historical GLB
|
|
240
|
+
reads to 10 MiB using the renderer's own size constant. External references,
|
|
241
|
+
decoder extensions, and `.gltf` JSON remain unsupported; the model is an
|
|
242
|
+
observable Frame, not a stable editable scene graph.
|
|
243
|
+
|
|
244
|
+
The diagram renderers are also read-only Frames. Drawio accepts bounded plain
|
|
245
|
+
or compressed multi-page XML and displays a simplified semantic diagram;
|
|
246
|
+
Excalidraw uses its official exporter to produce a static drawing from bounded
|
|
247
|
+
JSON and embedded raster assets; Mermaid uses its official strict renderer.
|
|
248
|
+
Excalidraw SVG is displayed in an inert CSP-limited iframe. Mermaid renders
|
|
249
|
+
inside a separate script-enabled, opaque-origin iframe with a restrictive CSP;
|
|
250
|
+
only its packaged runtime script and local data images/fonts are permitted.
|
|
251
|
+
Generated navigation is removed. None resolves remote or Workspace-linked
|
|
252
|
+
assets, starts an editor, or declares node locks. The Canvas offers the
|
|
253
|
+
original source alongside the diagram preview.
|
|
254
|
+
|
|
255
|
+
## Type tests
|
|
256
|
+
|
|
257
|
+
`type-tests/contracts.typecheck.ts` is compiled by the package check:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
pnpm --filter @springbrand/file-renderers check
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The file uses `@ts-expect-error` assertions to ensure these invalid contracts
|
|
264
|
+
remain rejected: cross-format parse/render, missing host cancellation context,
|
|
265
|
+
bytes supplied to a text format, Workspace identity/version facts inserted into
|
|
266
|
+
renderer contracts, a Frame model with `root`, an incomplete node lock, and a
|
|
267
|
+
node lock attached to a Frame model.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type DetectedFileFormat, type FileFormatInput } from "./file-format";
|
|
2
|
+
/**
|
|
3
|
+
* Formats which can cross the Attachment boundary. `text` is the Canvas
|
|
4
|
+
* source fallback for plain text files; `unknown` never crosses the boundary.
|
|
5
|
+
*/
|
|
6
|
+
export type AttachmentFormat = Exclude<DetectedFileFormat, "unknown">;
|
|
7
|
+
/**
|
|
8
|
+
* These are the canonical media types returned by the upload boundary. The
|
|
9
|
+
* list is intentionally owned by the renderer package so HTTP, Web, and Agent
|
|
10
|
+
* cannot grow separate format lists.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ATTACHMENT_MEDIA_TYPES: readonly ["application/json", "application/javascript", "application/pdf", "application/sql", "application/toml", "application/typescript", "application/xhtml+xml", "application/vnd.excalidraw+json", "application/vnd.jgraph.mxfile", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/wasm", "application/x-ipynb+json", "application/x-ndjson", "application/x-python", "application/x-python-code", "application/x-sh", "application/x-sql", "application/x-typescript", "application/xml", "application/yaml", "application/zip", "audio/aac", "audio/flac", "audio/mp4", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm", "image/apng", "image/avif", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/webp", "model/gltf-binary", "text/csv", "text/css", "text/html", "text/javascript", "text/markdown", "text/plain", "text/python", "text/typescript", "text/vnd.mermaid", "text/x-python", "text/x-scss", "text/x-sh", "text/x-sql", "video/mp4", "video/ogg", "video/webm"];
|
|
13
|
+
export type AttachmentMediaType = (typeof ATTACHMENT_MEDIA_TYPES)[number];
|
|
14
|
+
export declare function attachmentFormatFor(input: FileFormatInput): AttachmentFormat | null;
|
|
15
|
+
export declare function isSupportedAttachmentInput(input: FileFormatInput): boolean;
|
|
16
|
+
export declare function isAttachmentMediaType(value: string): value is AttachmentMediaType;
|
|
17
|
+
export declare function attachmentMediaTypeFor(input: FileFormatInput, format?: AttachmentFormat | null): AttachmentMediaType | null;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { codeLanguageFor, normalizedCodeMimeType, } from "./code/mime";
|
|
2
|
+
import { configMimeTypeFor } from "./config/mime";
|
|
3
|
+
import { csvMimeTypeFor } from "./csv/mime";
|
|
4
|
+
import { docxMimeTypeFor } from "./docx/mime";
|
|
5
|
+
import { drawioMimeTypeFor } from "./drawio/mime";
|
|
6
|
+
import { excalidrawMimeTypeFor } from "./excalidraw/mime";
|
|
7
|
+
import { fileFormatFor } from "./file-format";
|
|
8
|
+
import { glbMimeTypeFor } from "./glb/mime";
|
|
9
|
+
import { htmlMimeTypeFor } from "./html/mime";
|
|
10
|
+
import { imageMimeTypeFor, isSupportedImageMimeType } from "./image/mime";
|
|
11
|
+
import { jsonlMimeTypeFor, jsonMimeTypeFor } from "./json/mime";
|
|
12
|
+
import { isSupportedMarkdownInput } from "./markdown/mime";
|
|
13
|
+
import { canonicalMediaMimeType, mediaMimeTypeFor } from "./media/mime";
|
|
14
|
+
import { mermaidMimeTypeFor } from "./mermaid/mime";
|
|
15
|
+
import { NOTEBOOK_MIME_TYPE } from "./notebook/mime";
|
|
16
|
+
import { pdfMimeTypeFor } from "./pdf/mime";
|
|
17
|
+
import { pptMimeTypeFor } from "./ppt/mime";
|
|
18
|
+
import { svgMimeTypeFor } from "./svg/mime";
|
|
19
|
+
import { wasmMimeTypeFor } from "./wasm/mime";
|
|
20
|
+
import { xlsxMimeTypeFor } from "./xlsx/mime";
|
|
21
|
+
import { ZIP_MIME_TYPE, isSupportedZipInput } from "./zip/mime";
|
|
22
|
+
/**
|
|
23
|
+
* These are the canonical media types returned by the upload boundary. The
|
|
24
|
+
* list is intentionally owned by the renderer package so HTTP, Web, and Agent
|
|
25
|
+
* cannot grow separate format lists.
|
|
26
|
+
*/
|
|
27
|
+
export const ATTACHMENT_MEDIA_TYPES = [
|
|
28
|
+
"application/json",
|
|
29
|
+
"application/javascript",
|
|
30
|
+
"application/pdf",
|
|
31
|
+
"application/sql",
|
|
32
|
+
"application/toml",
|
|
33
|
+
"application/typescript",
|
|
34
|
+
"application/xhtml+xml",
|
|
35
|
+
"application/vnd.excalidraw+json",
|
|
36
|
+
"application/vnd.jgraph.mxfile",
|
|
37
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
38
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
39
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
40
|
+
"application/wasm",
|
|
41
|
+
"application/x-ipynb+json",
|
|
42
|
+
"application/x-ndjson",
|
|
43
|
+
"application/x-python",
|
|
44
|
+
"application/x-python-code",
|
|
45
|
+
"application/x-sh",
|
|
46
|
+
"application/x-sql",
|
|
47
|
+
"application/x-typescript",
|
|
48
|
+
"application/xml",
|
|
49
|
+
"application/yaml",
|
|
50
|
+
"application/zip",
|
|
51
|
+
"audio/aac",
|
|
52
|
+
"audio/flac",
|
|
53
|
+
"audio/mp4",
|
|
54
|
+
"audio/mpeg",
|
|
55
|
+
"audio/ogg",
|
|
56
|
+
"audio/wav",
|
|
57
|
+
"audio/webm",
|
|
58
|
+
"image/apng",
|
|
59
|
+
"image/avif",
|
|
60
|
+
"image/gif",
|
|
61
|
+
"image/jpeg",
|
|
62
|
+
"image/png",
|
|
63
|
+
"image/svg+xml",
|
|
64
|
+
"image/webp",
|
|
65
|
+
"model/gltf-binary",
|
|
66
|
+
"text/csv",
|
|
67
|
+
"text/css",
|
|
68
|
+
"text/html",
|
|
69
|
+
"text/javascript",
|
|
70
|
+
"text/markdown",
|
|
71
|
+
"text/plain",
|
|
72
|
+
"text/python",
|
|
73
|
+
"text/typescript",
|
|
74
|
+
"text/vnd.mermaid",
|
|
75
|
+
"text/x-python",
|
|
76
|
+
"text/x-scss",
|
|
77
|
+
"text/x-sh",
|
|
78
|
+
"text/x-sql",
|
|
79
|
+
"video/mp4",
|
|
80
|
+
"video/ogg",
|
|
81
|
+
"video/webm",
|
|
82
|
+
];
|
|
83
|
+
const ATTACHMENT_MEDIA_TYPE_SET = new Set(ATTACHMENT_MEDIA_TYPES);
|
|
84
|
+
export function attachmentFormatFor(input) {
|
|
85
|
+
const format = fileFormatFor(input);
|
|
86
|
+
return format === "unknown" ? null : format;
|
|
87
|
+
}
|
|
88
|
+
export function isSupportedAttachmentInput(input) {
|
|
89
|
+
return attachmentFormatFor(input) !== null;
|
|
90
|
+
}
|
|
91
|
+
export function isAttachmentMediaType(value) {
|
|
92
|
+
return ATTACHMENT_MEDIA_TYPE_SET.has(value);
|
|
93
|
+
}
|
|
94
|
+
export function attachmentMediaTypeFor(input, format = attachmentFormatFor(input)) {
|
|
95
|
+
if (!format)
|
|
96
|
+
return null;
|
|
97
|
+
const mimeInput = { name: "", mimeType: input.mimeType };
|
|
98
|
+
switch (format) {
|
|
99
|
+
case "image": {
|
|
100
|
+
const mediaType = imageMimeTypeFor(input) ?? imageMimeTypeFor(mimeInput) ??
|
|
101
|
+
normalizedMimeType(input.mimeType);
|
|
102
|
+
return isSupportedImageMimeType(mediaType) ? asAttachmentMediaType(mediaType) : null;
|
|
103
|
+
}
|
|
104
|
+
case "media":
|
|
105
|
+
return asAttachmentMediaType(mediaMimeTypeFor(input) ?? mediaMimeTypeFor(mimeInput) ?? canonicalMediaMimeType(input.mimeType));
|
|
106
|
+
case "html":
|
|
107
|
+
return asAttachmentMediaType(htmlMimeTypeFor(input) ?? htmlMimeTypeFor(mimeInput));
|
|
108
|
+
case "markdown":
|
|
109
|
+
return isSupportedMarkdownInput(input) || isSupportedMarkdownInput(mimeInput)
|
|
110
|
+
? "text/markdown"
|
|
111
|
+
: null;
|
|
112
|
+
case "csv":
|
|
113
|
+
return asAttachmentMediaType(csvMimeTypeFor(input) ?? csvMimeTypeFor(mimeInput));
|
|
114
|
+
case "json":
|
|
115
|
+
return asAttachmentMediaType(jsonMimeTypeFor(input) ?? jsonMimeTypeFor(mimeInput));
|
|
116
|
+
case "jsonl":
|
|
117
|
+
return asAttachmentMediaType(jsonlMimeTypeFor(input) ?? jsonlMimeTypeFor(mimeInput));
|
|
118
|
+
case "docx":
|
|
119
|
+
return asAttachmentMediaType(docxMimeTypeFor(input) ?? docxMimeTypeFor(mimeInput));
|
|
120
|
+
case "xlsx":
|
|
121
|
+
return asAttachmentMediaType(xlsxMimeTypeFor(input) ?? xlsxMimeTypeFor(mimeInput));
|
|
122
|
+
case "ppt":
|
|
123
|
+
return asAttachmentMediaType(pptMimeTypeFor(input) ?? pptMimeTypeFor(mimeInput));
|
|
124
|
+
case "pdf":
|
|
125
|
+
return asAttachmentMediaType(pdfMimeTypeFor(input) ?? pdfMimeTypeFor(mimeInput));
|
|
126
|
+
case "glb":
|
|
127
|
+
return asAttachmentMediaType(glbMimeTypeFor(input) ?? glbMimeTypeFor(mimeInput));
|
|
128
|
+
case "wasm":
|
|
129
|
+
return asAttachmentMediaType(wasmMimeTypeFor(input) ?? wasmMimeTypeFor(mimeInput));
|
|
130
|
+
case "zip":
|
|
131
|
+
return isSupportedZipInput(input) || isSupportedZipInput(mimeInput) ? ZIP_MIME_TYPE : null;
|
|
132
|
+
case "drawio":
|
|
133
|
+
return asAttachmentMediaType(drawioMimeTypeFor(input) ?? drawioMimeTypeFor(mimeInput));
|
|
134
|
+
case "excalidraw":
|
|
135
|
+
return asAttachmentMediaType(excalidrawMimeTypeFor(input) ?? excalidrawMimeTypeFor(mimeInput));
|
|
136
|
+
case "svg":
|
|
137
|
+
return asAttachmentMediaType(svgMimeTypeFor(input) ?? svgMimeTypeFor(mimeInput));
|
|
138
|
+
case "mermaid":
|
|
139
|
+
return asAttachmentMediaType(mermaidMimeTypeFor(input) ?? mermaidMimeTypeFor(mimeInput));
|
|
140
|
+
case "notebook":
|
|
141
|
+
return NOTEBOOK_MIME_TYPE;
|
|
142
|
+
case "yaml":
|
|
143
|
+
case "toml":
|
|
144
|
+
case "xml": {
|
|
145
|
+
const configFormat = configMimeTypeFor(input) ?? configMimeTypeFor(mimeInput);
|
|
146
|
+
if (!configFormat)
|
|
147
|
+
return null;
|
|
148
|
+
return configFormat === "yaml"
|
|
149
|
+
? "application/yaml"
|
|
150
|
+
: configFormat === "toml"
|
|
151
|
+
? "application/toml"
|
|
152
|
+
: "application/xml";
|
|
153
|
+
}
|
|
154
|
+
case "code": {
|
|
155
|
+
if (!codeLanguageFor(input) && !codeLanguageFor(mimeInput))
|
|
156
|
+
return null;
|
|
157
|
+
const mediaType = normalizedCodeMimeType(input.mimeType);
|
|
158
|
+
return isCodeMimeType(mediaType) ? asAttachmentMediaType(mediaType) : "text/plain";
|
|
159
|
+
}
|
|
160
|
+
case "text":
|
|
161
|
+
return "text/plain";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function normalizedMimeType(value) {
|
|
165
|
+
return value?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
166
|
+
}
|
|
167
|
+
function asAttachmentMediaType(value) {
|
|
168
|
+
return value && ATTACHMENT_MEDIA_TYPE_SET.has(value)
|
|
169
|
+
? value
|
|
170
|
+
: null;
|
|
171
|
+
}
|
|
172
|
+
function isCodeMimeType(value) {
|
|
173
|
+
return value === "application/javascript" || value === "application/typescript" ||
|
|
174
|
+
value === "application/sql" || value === "text/css" || value === "text/javascript" ||
|
|
175
|
+
value === "text/python" || value === "text/typescript" || value === "text/x-python" ||
|
|
176
|
+
value === "text/x-scss" || value === "text/x-sh" || value === "text/x-sql" ||
|
|
177
|
+
value === "application/x-python" || value === "application/x-python-code" ||
|
|
178
|
+
value === "application/x-sh" || value === "application/x-sql" ||
|
|
179
|
+
value === "application/x-typescript";
|
|
180
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type AttachmentFormat, type AttachmentMediaType } from "./attachment-format";
|
|
2
|
+
import type { FileFormatInput } from "./file-format";
|
|
3
|
+
export * from "./attachment-format";
|
|
4
|
+
export type AttachmentImageMediaTypeReader = (bytes: Uint8Array) => Promise<string | null>;
|
|
5
|
+
export type ValidatedAttachment = {
|
|
6
|
+
readonly format: AttachmentFormat;
|
|
7
|
+
readonly mediaType: AttachmentMediaType;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Validates the byte-level facts needed before an Attachment becomes durable.
|
|
11
|
+
* Renderer parsing remains responsible for parse diagnostics and format-specific
|
|
12
|
+
* preview errors; upload admission only rejects a format mismatch or unsafe
|
|
13
|
+
* container.
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateAttachmentBytes(input: FileFormatInput & {
|
|
16
|
+
readonly bytes: Uint8Array;
|
|
17
|
+
}, readImageMediaType: AttachmentImageMediaTypeReader): Promise<ValidatedAttachment | null>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { hasZipSignature, inspectSafeOoxml, isStrictUtf8 } from "../../utils/src/index.js";
|
|
2
|
+
import { attachmentFormatFor, attachmentMediaTypeFor, isAttachmentMediaType, } from "./attachment-format";
|
|
3
|
+
import { isSupportedImageMimeType } from "./image/mime";
|
|
4
|
+
import { isValidMediaBytes } from "./media/validate";
|
|
5
|
+
export * from "./attachment-format";
|
|
6
|
+
/**
|
|
7
|
+
* Validates the byte-level facts needed before an Attachment becomes durable.
|
|
8
|
+
* Renderer parsing remains responsible for parse diagnostics and format-specific
|
|
9
|
+
* preview errors; upload admission only rejects a format mismatch or unsafe
|
|
10
|
+
* container.
|
|
11
|
+
*/
|
|
12
|
+
export async function validateAttachmentBytes(input, readImageMediaType) {
|
|
13
|
+
const format = attachmentFormatFor(input);
|
|
14
|
+
if (!format || input.bytes.byteLength === 0)
|
|
15
|
+
return null;
|
|
16
|
+
switch (format) {
|
|
17
|
+
case "image": {
|
|
18
|
+
const mediaType = normalizedMimeType(await readImageMediaType(input.bytes));
|
|
19
|
+
if (!isSupportedImageMimeType(mediaType) || !isAttachmentMediaType(mediaType))
|
|
20
|
+
return null;
|
|
21
|
+
return { format, mediaType };
|
|
22
|
+
}
|
|
23
|
+
case "docx":
|
|
24
|
+
if (!await safeOoxml(input.bytes, "docx"))
|
|
25
|
+
return null;
|
|
26
|
+
break;
|
|
27
|
+
case "xlsx":
|
|
28
|
+
if (!await safeOoxml(input.bytes, "xlsx"))
|
|
29
|
+
return null;
|
|
30
|
+
break;
|
|
31
|
+
case "ppt":
|
|
32
|
+
if (!await safeOoxml(input.bytes, "pptx"))
|
|
33
|
+
return null;
|
|
34
|
+
break;
|
|
35
|
+
case "pdf":
|
|
36
|
+
if (!hasAsciiPrefix(input.bytes, "%PDF-"))
|
|
37
|
+
return null;
|
|
38
|
+
break;
|
|
39
|
+
case "zip":
|
|
40
|
+
if (!hasZipSignature(input.bytes))
|
|
41
|
+
return null;
|
|
42
|
+
break;
|
|
43
|
+
case "glb":
|
|
44
|
+
if (!hasAsciiPrefix(input.bytes, "glTF") || input.bytes[4] !== 2)
|
|
45
|
+
return null;
|
|
46
|
+
break;
|
|
47
|
+
case "wasm":
|
|
48
|
+
if (!hasBytes(input.bytes, [0, 0x61, 0x73, 0x6d, 1, 0, 0, 0]))
|
|
49
|
+
return null;
|
|
50
|
+
break;
|
|
51
|
+
case "media":
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
if (!isTextPayload(input.bytes))
|
|
55
|
+
return null;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const mediaType = attachmentMediaTypeFor(input, format);
|
|
59
|
+
if (!mediaType)
|
|
60
|
+
return null;
|
|
61
|
+
if (format === "media" && !isValidMediaBytes(input.bytes, mediaType))
|
|
62
|
+
return null;
|
|
63
|
+
return { format, mediaType };
|
|
64
|
+
}
|
|
65
|
+
function normalizedMimeType(value) {
|
|
66
|
+
return value?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
67
|
+
}
|
|
68
|
+
function isTextPayload(bytes) {
|
|
69
|
+
const encoding = bytes[0] === 0xff && bytes[1] === 0xfe
|
|
70
|
+
? "utf-16le"
|
|
71
|
+
: bytes[0] === 0xfe && bytes[1] === 0xff
|
|
72
|
+
? "utf-16be"
|
|
73
|
+
: "utf-8";
|
|
74
|
+
try {
|
|
75
|
+
const text = new TextDecoder(encoding, { fatal: true, ignoreBOM: false }).decode(bytes);
|
|
76
|
+
return encoding !== "utf-8" || isStrictUtf8(bytes) ? !text.includes("\u0000") : false;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function hasAsciiPrefix(bytes, value) {
|
|
83
|
+
return hasBytes(bytes, [...value].map((character) => character.charCodeAt(0)));
|
|
84
|
+
}
|
|
85
|
+
function hasBytes(bytes, expected) {
|
|
86
|
+
return expected.every((value, index) => bytes[index] === value);
|
|
87
|
+
}
|
|
88
|
+
async function safeOoxml(bytes, kind) {
|
|
89
|
+
try {
|
|
90
|
+
await inspectSafeOoxml(bytes, kind);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FileRenderer, FrameDocumentModel, RendererInput } from "../contracts";
|
|
2
|
+
import { type CodeLanguage } from "./mime";
|
|
3
|
+
export { codeLanguageFor, type CodeLanguage } from "./mime";
|
|
4
|
+
type CodeFrame = {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly language: CodeLanguage;
|
|
7
|
+
};
|
|
8
|
+
export type CodeModel = FrameDocumentModel<"code", CodeFrame>;
|
|
9
|
+
export declare class UnsupportedCodeLanguageError extends Error {
|
|
10
|
+
readonly fileName: string;
|
|
11
|
+
readonly mimeType: string;
|
|
12
|
+
constructor(input: Pick<RendererInput<"code">, "name" | "mimeType">);
|
|
13
|
+
}
|
|
14
|
+
export declare function CodeDocument({ text, language, signal, }: {
|
|
15
|
+
readonly text: string;
|
|
16
|
+
readonly language: string;
|
|
17
|
+
readonly signal?: AbortSignal;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
export declare const codeRenderer: FileRenderer<"code", CodeModel>;
|