omni-viewer-core 0.1.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/LICENSE +21 -0
- package/README.md +80 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/dist/assets/audio-engine/audio_engine.mjs +2 -0
- package/dist/assets/audio-engine/audio_engine.wasm +0 -0
- package/dist/assets/pdfjs/pdf.worker.min.mjs +21 -0
- package/dist/host/index.d.ts +133 -0
- package/dist/host/index.js +10 -0
- package/dist/i18n/catalog.en.d.ts +1 -0
- package/dist/i18n/catalog.en.js +411 -0
- package/dist/i18n/catalog.ko.d.ts +1 -0
- package/dist/i18n/catalog.ko.js +68 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +41 -0
- package/dist/licenses/emscripten.txt +101 -0
- package/dist/licenses/kissfft.txt +28 -0
- package/dist/licenses/pdfjs-dist.txt +177 -0
- package/dist/parsers/a2l/index.d.ts +1 -0
- package/dist/parsers/a2l/index.js +1 -0
- package/dist/parsers/archive/index.d.ts +22 -0
- package/dist/parsers/archive/index.js +46 -0
- package/dist/parsers/archive/model.d.ts +29 -0
- package/dist/parsers/archive/model.js +1 -0
- package/dist/parsers/archive/worker.d.ts +21 -0
- package/dist/parsers/archive/worker.js +1 -0
- package/dist/parsers/arxml/index.d.ts +1 -0
- package/dist/parsers/arxml/index.js +1 -0
- package/dist/parsers/asc/index.d.ts +1 -0
- package/dist/parsers/asc/index.js +1 -0
- package/dist/parsers/audio/index.d.ts +9 -0
- package/dist/parsers/audio/index.js +46 -0
- package/dist/parsers/automotive/index.d.ts +33 -0
- package/dist/parsers/automotive/index.js +973 -0
- package/dist/parsers/blf/index.d.ts +1 -0
- package/dist/parsers/blf/index.js +1 -0
- package/dist/parsers/csv/delimiter.d.ts +31 -0
- package/dist/parsers/csv/delimiter.js +151 -0
- package/dist/parsers/csv/index.d.ts +30 -0
- package/dist/parsers/csv/index.js +108 -0
- package/dist/parsers/csv/parseText.d.ts +20 -0
- package/dist/parsers/csv/parseText.js +108 -0
- package/dist/parsers/csv/sort.d.ts +26 -0
- package/dist/parsers/csv/sort.js +224 -0
- package/dist/parsers/csv/statistics.d.ts +49 -0
- package/dist/parsers/csv/statistics.js +150 -0
- package/dist/parsers/dbc/index.d.ts +56 -0
- package/dist/parsers/dbc/index.js +94 -0
- package/dist/parsers/doc-binary/index.d.ts +35 -0
- package/dist/parsers/doc-binary/index.js +267 -0
- package/dist/parsers/doc-binary/vscode-parser.d.ts +142 -0
- package/dist/parsers/doc-binary/vscode-parser.js +3747 -0
- package/dist/parsers/excel/index.d.ts +17 -0
- package/dist/parsers/excel/index.js +406 -0
- package/dist/parsers/excel/model.d.ts +80 -0
- package/dist/parsers/excel/model.js +48 -0
- package/dist/parsers/excel/serialize.d.ts +3 -0
- package/dist/parsers/excel/serialize.js +78 -0
- package/dist/parsers/hdf5/index.d.ts +75 -0
- package/dist/parsers/hdf5/index.js +658 -0
- package/dist/parsers/hdf5/node.d.ts +21 -0
- package/dist/parsers/hdf5/node.js +95 -0
- package/dist/parsers/json/index.d.ts +15 -0
- package/dist/parsers/json/index.js +102 -0
- package/dist/parsers/json/model.d.ts +42 -0
- package/dist/parsers/json/model.js +140 -0
- package/dist/parsers/json/parse.d.ts +22 -0
- package/dist/parsers/json/parse.js +279 -0
- package/dist/parsers/json/tokenizer.d.ts +17 -0
- package/dist/parsers/json/tokenizer.js +175 -0
- package/dist/parsers/jsonl/index.d.ts +14 -0
- package/dist/parsers/jsonl/index.js +18 -0
- package/dist/parsers/jsonl/model.d.ts +23 -0
- package/dist/parsers/jsonl/model.js +1 -0
- package/dist/parsers/jsonl/parse.d.ts +7 -0
- package/dist/parsers/jsonl/parse.js +34 -0
- package/dist/parsers/markdown/index.d.ts +37 -0
- package/dist/parsers/markdown/index.js +59 -0
- package/dist/parsers/mat/index.d.ts +31 -0
- package/dist/parsers/mat/index.js +426 -0
- package/dist/parsers/mermaid/index.d.ts +5 -0
- package/dist/parsers/mermaid/index.js +3 -0
- package/dist/parsers/mf4/index.d.ts +1 -0
- package/dist/parsers/mf4/index.js +1 -0
- package/dist/parsers/parquet/index.d.ts +21 -0
- package/dist/parsers/parquet/index.js +68 -0
- package/dist/parsers/pcap/index.d.ts +1 -0
- package/dist/parsers/pcap/index.js +1 -0
- package/dist/parsers/pcapng/index.d.ts +1 -0
- package/dist/parsers/pcapng/index.js +1 -0
- package/dist/parsers/plantuml/index.d.ts +5 -0
- package/dist/parsers/plantuml/index.js +4 -0
- package/dist/parsers/ppt-binary/index.d.ts +5 -0
- package/dist/parsers/ppt-binary/index.js +62 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.d.ts +12 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.js +40 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.d.ts +4 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.js +174 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.d.ts +125 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.js +1 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.d.ts +9 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.js +510 -0
- package/dist/parsers/ppt-binary/vscode-parser.d.ts +116 -0
- package/dist/parsers/ppt-binary/vscode-parser.js +2853 -0
- package/dist/parsers/ppt-binary/vscode.d.ts +4 -0
- package/dist/parsers/ppt-binary/vscode.js +12 -0
- package/dist/parsers/pptx/index.d.ts +15 -0
- package/dist/parsers/pptx/index.js +88 -0
- package/dist/parsers/pptx/vscode-parser.d.ts +167 -0
- package/dist/parsers/pptx/vscode-parser.js +1503 -0
- package/dist/parsers/pptx/vscode.d.ts +4 -0
- package/dist/parsers/pptx/vscode.js +18 -0
- package/dist/parsers/pptx/zip-reader.d.ts +3 -0
- package/dist/parsers/pptx/zip-reader.js +20 -0
- package/dist/parsers/proto/index.d.ts +90 -0
- package/dist/parsers/proto/index.js +197 -0
- package/dist/parsers/psd/index.d.ts +16 -0
- package/dist/parsers/psd/index.js +10 -0
- package/dist/parsers/shapefile/dbf.d.ts +23 -0
- package/dist/parsers/shapefile/dbf.js +112 -0
- package/dist/parsers/shapefile/index.d.ts +44 -0
- package/dist/parsers/shapefile/index.js +119 -0
- package/dist/parsers/shapefile/reproject.d.ts +10 -0
- package/dist/parsers/shapefile/reproject.js +41 -0
- package/dist/parsers/slide-model.d.ts +106 -0
- package/dist/parsers/slide-model.js +1 -0
- package/dist/parsers/toml/index.d.ts +15 -0
- package/dist/parsers/toml/index.js +18 -0
- package/dist/parsers/toml/model.d.ts +15 -0
- package/dist/parsers/toml/model.js +1 -0
- package/dist/parsers/toml/parse.d.ts +16 -0
- package/dist/parsers/toml/parse.js +266 -0
- package/dist/parsers/types.d.ts +103 -0
- package/dist/parsers/types.js +106 -0
- package/dist/parsers/video/index.d.ts +6 -0
- package/dist/parsers/video/index.js +2 -0
- package/dist/parsers/yaml/index.d.ts +16 -0
- package/dist/parsers/yaml/index.js +58 -0
- package/dist/parsers/yaml/model.d.ts +22 -0
- package/dist/parsers/yaml/model.js +1 -0
- package/dist/parsers/yaml/self-loading.d.ts +3 -0
- package/dist/parsers/yaml/self-loading.js +19 -0
- package/dist/parsers/zip-scan.d.ts +8 -0
- package/dist/parsers/zip-scan.js +80 -0
- package/dist/registry/container.d.ts +17 -0
- package/dist/registry/container.js +49 -0
- package/dist/registry/index.d.ts +133 -0
- package/dist/registry/index.js +348 -0
- package/dist/registry/probe.d.ts +3 -0
- package/dist/registry/probe.js +177 -0
- package/dist/registry/sniff.d.ts +17 -0
- package/dist/registry/sniff.js +57 -0
- package/dist/styles/archive.css +10 -0
- package/dist/styles/audio.css +66 -0
- package/dist/styles/automotive.css +1 -0
- package/dist/styles/csv.css +262 -0
- package/dist/styles/excel.css +212 -0
- package/dist/styles/fallback.css +48 -0
- package/dist/styles/hdf5.css +3 -0
- package/dist/styles/hwp.css +12 -0
- package/dist/styles/image.css +115 -0
- package/dist/styles/json.css +342 -0
- package/dist/styles/jsonl.css +1 -0
- package/dist/styles/markdown.css +10 -0
- package/dist/styles/mat.css +2 -0
- package/dist/styles/mermaid.css +1 -0
- package/dist/styles/parquet.css +2 -0
- package/dist/styles/pdf.css +551 -0
- package/dist/styles/plantuml.css +1 -0
- package/dist/styles/ppt.css +1 -0
- package/dist/styles/proto.css +2 -0
- package/dist/styles/psd.css +1 -0
- package/dist/styles/shapefile.css +25 -0
- package/dist/styles/toml.css +1 -0
- package/dist/styles/video.css +74 -0
- package/dist/styles/word.css +685 -0
- package/dist/styles/yaml.css +1 -0
- package/dist/viewers/a2l/index.d.ts +15 -0
- package/dist/viewers/a2l/index.js +5 -0
- package/dist/viewers/archive/archive-preview-decoder.d.ts +4 -0
- package/dist/viewers/archive/archive-preview-decoder.js +223 -0
- package/dist/viewers/archive/controller.d.ts +24 -0
- package/dist/viewers/archive/controller.js +18 -0
- package/dist/viewers/archive/index.d.ts +25 -0
- package/dist/viewers/archive/index.js +230 -0
- package/dist/viewers/archive/preview.d.ts +2 -0
- package/dist/viewers/archive/preview.js +2 -0
- package/dist/viewers/archive/styles.d.ts +1 -0
- package/dist/viewers/archive/styles.js +11 -0
- package/dist/viewers/arxml/index.d.ts +15 -0
- package/dist/viewers/arxml/index.js +5 -0
- package/dist/viewers/asc/index.d.ts +15 -0
- package/dist/viewers/asc/index.js +5 -0
- package/dist/viewers/audio/controller.d.ts +33 -0
- package/dist/viewers/audio/controller.js +49 -0
- package/dist/viewers/audio/engine.d.ts +44 -0
- package/dist/viewers/audio/engine.js +108 -0
- package/dist/viewers/audio/index.d.ts +92 -0
- package/dist/viewers/audio/index.js +448 -0
- package/dist/viewers/audio/self-loading.d.ts +7 -0
- package/dist/viewers/audio/self-loading.js +42 -0
- package/dist/viewers/audio/styles.d.ts +1 -0
- package/dist/viewers/audio/styles.js +73 -0
- package/dist/viewers/automotive/index.d.ts +8 -0
- package/dist/viewers/automotive/index.js +152 -0
- package/dist/viewers/automotive/styles.d.ts +1 -0
- package/dist/viewers/automotive/styles.js +1 -0
- package/dist/viewers/avro/index.d.ts +15 -0
- package/dist/viewers/avro/index.js +5 -0
- package/dist/viewers/bag/index.d.ts +15 -0
- package/dist/viewers/bag/index.js +5 -0
- package/dist/viewers/blf/index.d.ts +15 -0
- package/dist/viewers/blf/index.js +5 -0
- package/dist/viewers/csv/controller.d.ts +126 -0
- package/dist/viewers/csv/controller.js +598 -0
- package/dist/viewers/csv/index.d.ts +48 -0
- package/dist/viewers/csv/index.js +846 -0
- package/dist/viewers/csv/styles.d.ts +1 -0
- package/dist/viewers/csv/styles.js +267 -0
- package/dist/viewers/db3/index.d.ts +15 -0
- package/dist/viewers/db3/index.js +5 -0
- package/dist/viewers/dbc/index.d.ts +15 -0
- package/dist/viewers/dbc/index.js +5 -0
- package/dist/viewers/diagram.d.ts +14 -0
- package/dist/viewers/diagram.js +95 -0
- package/dist/viewers/excel/controller.d.ts +108 -0
- package/dist/viewers/excel/controller.js +548 -0
- package/dist/viewers/excel/grid.d.ts +28 -0
- package/dist/viewers/excel/grid.js +99 -0
- package/dist/viewers/excel/index.d.ts +37 -0
- package/dist/viewers/excel/index.js +620 -0
- package/dist/viewers/excel/self-loading.d.ts +6 -0
- package/dist/viewers/excel/self-loading.js +13 -0
- package/dist/viewers/excel/styles.d.ts +1 -0
- package/dist/viewers/excel/styles.js +216 -0
- package/dist/viewers/fallback/index.d.ts +17 -0
- package/dist/viewers/fallback/index.js +130 -0
- package/dist/viewers/fallback/styles.d.ts +1 -0
- package/dist/viewers/fallback/styles.js +49 -0
- package/dist/viewers/hdf5/index.d.ts +19 -0
- package/dist/viewers/hdf5/index.js +170 -0
- package/dist/viewers/hdf5/styles.d.ts +1 -0
- package/dist/viewers/hdf5/styles.js +4 -0
- package/dist/viewers/hwp/controller.d.ts +19 -0
- package/dist/viewers/hwp/controller.js +22 -0
- package/dist/viewers/hwp/index.d.ts +39 -0
- package/dist/viewers/hwp/index.js +271 -0
- package/dist/viewers/hwp/self-loading.d.ts +2 -0
- package/dist/viewers/hwp/self-loading.js +6 -0
- package/dist/viewers/hwp/styles.d.ts +1 -0
- package/dist/viewers/hwp/styles.js +13 -0
- package/dist/viewers/image/controller.d.ts +210 -0
- package/dist/viewers/image/controller.js +336 -0
- package/dist/viewers/image/decode.d.ts +54 -0
- package/dist/viewers/image/decode.js +150 -0
- package/dist/viewers/image/index.d.ts +38 -0
- package/dist/viewers/image/index.js +749 -0
- package/dist/viewers/image/limits.d.ts +18 -0
- package/dist/viewers/image/limits.js +20 -0
- package/dist/viewers/image/styles.d.ts +1 -0
- package/dist/viewers/image/styles.js +119 -0
- package/dist/viewers/json/controller.d.ts +89 -0
- package/dist/viewers/json/controller.js +411 -0
- package/dist/viewers/json/index.d.ts +25 -0
- package/dist/viewers/json/index.js +503 -0
- package/dist/viewers/json/styles.d.ts +1 -0
- package/dist/viewers/json/styles.js +346 -0
- package/dist/viewers/json/transforms.d.ts +27 -0
- package/dist/viewers/json/transforms.js +253 -0
- package/dist/viewers/jsonl/controller.d.ts +48 -0
- package/dist/viewers/jsonl/controller.js +136 -0
- package/dist/viewers/jsonl/index.d.ts +22 -0
- package/dist/viewers/jsonl/index.js +184 -0
- package/dist/viewers/jsonl/styles.d.ts +1 -0
- package/dist/viewers/jsonl/styles.js +1 -0
- package/dist/viewers/markdown/controller.d.ts +50 -0
- package/dist/viewers/markdown/controller.js +62 -0
- package/dist/viewers/markdown/index.d.ts +64 -0
- package/dist/viewers/markdown/index.js +425 -0
- package/dist/viewers/markdown/math.d.ts +21 -0
- package/dist/viewers/markdown/math.js +100 -0
- package/dist/viewers/markdown/self-loading.d.ts +5 -0
- package/dist/viewers/markdown/self-loading.js +48 -0
- package/dist/viewers/markdown/styles.d.ts +1 -0
- package/dist/viewers/markdown/styles.js +11 -0
- package/dist/viewers/mat/index.d.ts +19 -0
- package/dist/viewers/mat/index.js +113 -0
- package/dist/viewers/mat/styles.d.ts +1 -0
- package/dist/viewers/mat/styles.js +3 -0
- package/dist/viewers/media.d.ts +9 -0
- package/dist/viewers/media.js +77 -0
- package/dist/viewers/mermaid/index.d.ts +17 -0
- package/dist/viewers/mermaid/index.js +6 -0
- package/dist/viewers/mermaid/self-loading.d.ts +2 -0
- package/dist/viewers/mermaid/self-loading.js +2 -0
- package/dist/viewers/mf4/index.d.ts +15 -0
- package/dist/viewers/mf4/index.js +5 -0
- package/dist/viewers/parquet/controller.d.ts +37 -0
- package/dist/viewers/parquet/controller.js +26 -0
- package/dist/viewers/parquet/index.d.ts +23 -0
- package/dist/viewers/parquet/index.js +134 -0
- package/dist/viewers/parquet/styles.d.ts +1 -0
- package/dist/viewers/parquet/styles.js +3 -0
- package/dist/viewers/pcap/index.d.ts +15 -0
- package/dist/viewers/pcap/index.js +5 -0
- package/dist/viewers/pcapng/index.d.ts +15 -0
- package/dist/viewers/pcapng/index.js +5 -0
- package/dist/viewers/pdf/controller.d.ts +120 -0
- package/dist/viewers/pdf/controller.js +191 -0
- package/dist/viewers/pdf/editing.d.ts +26 -0
- package/dist/viewers/pdf/editing.js +265 -0
- package/dist/viewers/pdf/index.d.ts +88 -0
- package/dist/viewers/pdf/index.js +1400 -0
- package/dist/viewers/pdf/self-loading.d.ts +6 -0
- package/dist/viewers/pdf/self-loading.js +14 -0
- package/dist/viewers/pdf/styles.d.ts +1 -0
- package/dist/viewers/pdf/styles.js +555 -0
- package/dist/viewers/plantuml/index.d.ts +17 -0
- package/dist/viewers/plantuml/index.js +6 -0
- package/dist/viewers/plantuml/self-loading.d.ts +2 -0
- package/dist/viewers/plantuml/self-loading.js +2 -0
- package/dist/viewers/ppt/chart.d.ts +8 -0
- package/dist/viewers/ppt/chart.js +83 -0
- package/dist/viewers/ppt/controller.d.ts +34 -0
- package/dist/viewers/ppt/controller.js +27 -0
- package/dist/viewers/ppt/index.d.ts +33 -0
- package/dist/viewers/ppt/index.js +101 -0
- package/dist/viewers/ppt/render.d.ts +5 -0
- package/dist/viewers/ppt/render.js +144 -0
- package/dist/viewers/ppt/self-loading.d.ts +9 -0
- package/dist/viewers/ppt/self-loading.js +4 -0
- package/dist/viewers/ppt/styles.d.ts +1 -0
- package/dist/viewers/ppt/styles.js +1 -0
- package/dist/viewers/proto/index.d.ts +17 -0
- package/dist/viewers/proto/index.js +170 -0
- package/dist/viewers/proto/styles.d.ts +1 -0
- package/dist/viewers/proto/styles.js +2 -0
- package/dist/viewers/psd/index.d.ts +27 -0
- package/dist/viewers/psd/index.js +39 -0
- package/dist/viewers/psd/self-loading.d.ts +10 -0
- package/dist/viewers/psd/self-loading.js +20 -0
- package/dist/viewers/psd/styles.d.ts +1 -0
- package/dist/viewers/psd/styles.js +1 -0
- package/dist/viewers/reqif/index.d.ts +15 -0
- package/dist/viewers/reqif/index.js +5 -0
- package/dist/viewers/shapefile/index.d.ts +35 -0
- package/dist/viewers/shapefile/index.js +336 -0
- package/dist/viewers/shapefile/self-loading.d.ts +7 -0
- package/dist/viewers/shapefile/self-loading.js +32 -0
- package/dist/viewers/shapefile/styles.d.ts +1 -0
- package/dist/viewers/shapefile/styles.js +28 -0
- package/dist/viewers/stp/index.d.ts +15 -0
- package/dist/viewers/stp/index.js +5 -0
- package/dist/viewers/structured-styles.d.ts +1 -0
- package/dist/viewers/structured-styles.js +1 -0
- package/dist/viewers/structured.d.ts +36 -0
- package/dist/viewers/structured.js +138 -0
- package/dist/viewers/toml/controller.d.ts +38 -0
- package/dist/viewers/toml/controller.js +61 -0
- package/dist/viewers/toml/index.d.ts +18 -0
- package/dist/viewers/toml/index.js +34 -0
- package/dist/viewers/types.d.ts +36 -0
- package/dist/viewers/types.js +9 -0
- package/dist/viewers/video/controller.d.ts +29 -0
- package/dist/viewers/video/controller.js +54 -0
- package/dist/viewers/video/index.d.ts +18 -0
- package/dist/viewers/video/index.js +305 -0
- package/dist/viewers/video/styles.d.ts +1 -0
- package/dist/viewers/video/styles.js +79 -0
- package/dist/viewers/word/controller.d.ts +19 -0
- package/dist/viewers/word/controller.js +22 -0
- package/dist/viewers/word/docx-preprocess.d.ts +63 -0
- package/dist/viewers/word/docx-preprocess.js +136 -0
- package/dist/viewers/word/index.d.ts +32 -0
- package/dist/viewers/word/index.js +187 -0
- package/dist/viewers/word/normalize-docx.d.ts +2 -0
- package/dist/viewers/word/normalize-docx.js +50 -0
- package/dist/viewers/word/paginate.d.ts +1 -0
- package/dist/viewers/word/paginate.js +1035 -0
- package/dist/viewers/word/self-loading.d.ts +2 -0
- package/dist/viewers/word/self-loading.js +7 -0
- package/dist/viewers/word/styles.d.ts +1 -0
- package/dist/viewers/word/styles.js +686 -0
- package/dist/viewers/yaml/controller.d.ts +42 -0
- package/dist/viewers/yaml/controller.js +41 -0
- package/dist/viewers/yaml/index.d.ts +21 -0
- package/dist/viewers/yaml/index.js +20 -0
- package/package.json +375 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Video viewer stylesheet. Single source for both delivery forms (DESIGN.md
|
|
2
|
+
// §6): the build emits dist/styles/video.css from this constant, and the
|
|
3
|
+
// renderer injects it into the shadow root. Only --omni-* tokens (with
|
|
4
|
+
// fallbacks) — never --vscode-* or platform variables.
|
|
5
|
+
export const videoViewerCss = `
|
|
6
|
+
/* Reverse-contamination guard (DESIGN.md §6). */
|
|
7
|
+
.omni-viewer--video {
|
|
8
|
+
all: initial;
|
|
9
|
+
}
|
|
10
|
+
.omni-viewer--video :where(button, select, option, input, label, video) {
|
|
11
|
+
all: revert;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:host, .omni-viewer--video {
|
|
15
|
+
display: block;
|
|
16
|
+
height: 100%;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
color: var(--omni-fg, #d4d4d4);
|
|
19
|
+
background: var(--omni-bg, #1e1e1e);
|
|
20
|
+
font-family: var(--omni-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
21
|
+
font-size: var(--omni-font-size, 13px);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.omni-video { display: flex; flex-direction: column; gap: 12px; box-sizing: border-box; height: 100%; padding: 16px; }
|
|
25
|
+
.omni-video * { box-sizing: border-box; }
|
|
26
|
+
|
|
27
|
+
.omni-video__header { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
|
|
28
|
+
.omni-video__title { font-size: 16px; font-weight: 650; overflow-wrap: anywhere; }
|
|
29
|
+
.omni-video__meta { color: var(--omni-muted, #9d9d9d); white-space: nowrap; }
|
|
30
|
+
|
|
31
|
+
.omni-video__info { display: flex; gap: 18px; padding: 6px 10px; border: 1px solid var(--omni-border, #444); border-radius: 6px; background: var(--omni-panel-bg, #252526); }
|
|
32
|
+
.omni-video__info[hidden] { display: none; }
|
|
33
|
+
.omni-video__info-item { display: flex; flex-direction: column; align-items: center; gap: 2px; }
|
|
34
|
+
.omni-video__info-label { font-size: 10px; opacity: 0.7; }
|
|
35
|
+
.omni-video__info-value { font-family: var(--omni-mono, ui-monospace, Menlo, monospace); font-weight: 500; }
|
|
36
|
+
|
|
37
|
+
.omni-video__controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 6px; border: 1px solid var(--omni-border, #444); border-radius: 8px; background: var(--omni-panel-bg, #252526); }
|
|
38
|
+
.omni-video__group { display: flex; align-items: center; gap: 5px; }
|
|
39
|
+
.omni-video__group-label { font-size: 12px; color: var(--omni-muted, #9d9d9d); }
|
|
40
|
+
|
|
41
|
+
.omni-video__btn {
|
|
42
|
+
background: var(--omni-accent, #0e639c); color: var(--omni-accent-fg, #fff);
|
|
43
|
+
border: none; padding: 5px 11px; border-radius: 4px; cursor: pointer; font-size: 12px; font-family: inherit;
|
|
44
|
+
}
|
|
45
|
+
.omni-video__btn:hover { background: var(--omni-accent-hover, #1177bb); }
|
|
46
|
+
.omni-video__btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
47
|
+
|
|
48
|
+
.omni-video__select {
|
|
49
|
+
background: var(--omni-input-bg, #3c3c3c); color: var(--omni-fg, #d4d4d4);
|
|
50
|
+
border: 1px solid var(--omni-border, #444); padding: 4px 8px; border-radius: 4px; font-size: 12px; font-family: inherit;
|
|
51
|
+
}
|
|
52
|
+
.omni-video__zoom-label { font-family: var(--omni-mono, ui-monospace, Menlo, monospace); font-size: 12px; min-width: 42px; text-align: center; color: var(--omni-muted, #9d9d9d); }
|
|
53
|
+
|
|
54
|
+
.omni-video__progress-row { display: flex; align-items: center; gap: 10px; padding: 4px 6px; border: 1px solid var(--omni-border, #444); border-radius: 8px; background: var(--omni-panel-bg, #252526); }
|
|
55
|
+
.omni-video__time { font-family: var(--omni-mono, ui-monospace, Menlo, monospace); font-size: 12px; min-width: 52px; text-align: center; }
|
|
56
|
+
.omni-video__bar { flex: 1; height: 8px; background: var(--omni-input-bg, #3c3c3c); border-radius: 4px; cursor: pointer; position: relative; }
|
|
57
|
+
.omni-video__bar:hover, .omni-video__bar--dragging { height: 12px; }
|
|
58
|
+
.omni-video__bar-filled { height: 100%; width: 0%; background: var(--omni-accent, #0e639c); border-radius: 4px; pointer-events: none; position: relative; }
|
|
59
|
+
.omni-video__bar-handle {
|
|
60
|
+
width: 14px; height: 14px; background: var(--omni-accent-fg, #fff); border: 2px solid var(--omni-accent, #0e639c);
|
|
61
|
+
border-radius: 50%; position: absolute; right: -7px; top: 50%; transform: translateY(-50%);
|
|
62
|
+
opacity: 0; transition: opacity 0.15s; pointer-events: none;
|
|
63
|
+
}
|
|
64
|
+
.omni-video__bar:hover .omni-video__bar-handle, .omni-video__bar--dragging .omni-video__bar-handle { opacity: 1; }
|
|
65
|
+
|
|
66
|
+
.omni-video__stage {
|
|
67
|
+
flex: 1; min-height: 240px; display: flex; justify-content: center; align-items: center;
|
|
68
|
+
border: 1px solid var(--omni-border, #444); border-radius: 8px; background: #000; overflow: hidden; position: relative;
|
|
69
|
+
}
|
|
70
|
+
.omni-video__stage--zoomed { overflow: auto; }
|
|
71
|
+
.omni-video__wrapper { transition: transform 0.2s ease; transform-origin: center center; }
|
|
72
|
+
.omni-video video { display: block; max-width: 100%; max-height: 70vh; background: #000; }
|
|
73
|
+
|
|
74
|
+
.omni-video__warning {
|
|
75
|
+
padding: 10px 12px; border: 1px solid #b98b2f; border-radius: 7px;
|
|
76
|
+
background: #3b2d10; color: #ffd98a; white-space: pre-wrap;
|
|
77
|
+
}
|
|
78
|
+
.omni-video__warning[hidden] { display: none; }
|
|
79
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface WordViewState {
|
|
2
|
+
zoom: number;
|
|
3
|
+
}
|
|
4
|
+
export type WordAction = {
|
|
5
|
+
type: 'zoom-in';
|
|
6
|
+
} | {
|
|
7
|
+
type: 'zoom-out';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'reset-zoom';
|
|
10
|
+
};
|
|
11
|
+
export interface WordController {
|
|
12
|
+
readonly state: WordViewState;
|
|
13
|
+
dispatch(action: WordAction): void;
|
|
14
|
+
subscribe(listener: (state: WordViewState) => void): () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const WORD_MIN_ZOOM = 0.25;
|
|
17
|
+
export declare const WORD_MAX_ZOOM = 2.5;
|
|
18
|
+
export declare const WORD_ZOOM_STEP = 0.1;
|
|
19
|
+
export declare function createWordController(): WordController;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const WORD_MIN_ZOOM = 0.25;
|
|
2
|
+
export const WORD_MAX_ZOOM = 2.5;
|
|
3
|
+
export const WORD_ZOOM_STEP = 0.1;
|
|
4
|
+
export function createWordController() {
|
|
5
|
+
let zoom = 1;
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
const snapshot = () => ({ zoom });
|
|
8
|
+
const emit = () => listeners.forEach((listener) => listener(snapshot()));
|
|
9
|
+
return {
|
|
10
|
+
get state() { return snapshot(); },
|
|
11
|
+
dispatch(action) {
|
|
12
|
+
if (action.type === 'zoom-in')
|
|
13
|
+
zoom = Math.min(WORD_MAX_ZOOM, Math.round((zoom + WORD_ZOOM_STEP) * 100) / 100);
|
|
14
|
+
if (action.type === 'zoom-out')
|
|
15
|
+
zoom = Math.max(WORD_MIN_ZOOM, Math.round((zoom - WORD_ZOOM_STEP) * 100) / 100);
|
|
16
|
+
if (action.type === 'reset-zoom')
|
|
17
|
+
zoom = 1;
|
|
18
|
+
emit();
|
|
19
|
+
},
|
|
20
|
+
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface ZipEntry {
|
|
2
|
+
dir: boolean;
|
|
3
|
+
async(type: 'string'): Promise<string>;
|
|
4
|
+
async(type: 'uint8array'): Promise<Uint8Array>;
|
|
5
|
+
}
|
|
6
|
+
export interface ZipArchive {
|
|
7
|
+
file(path: string): ZipEntry | null;
|
|
8
|
+
file(path: string, data: string): ZipArchive;
|
|
9
|
+
forEach(callback: (path: string, entry: ZipEntry) => void): void;
|
|
10
|
+
generateAsync(options: {
|
|
11
|
+
type: 'uint8array';
|
|
12
|
+
}): Promise<Uint8Array>;
|
|
13
|
+
}
|
|
14
|
+
export interface ZipModule {
|
|
15
|
+
loadAsync(data: Uint8Array): Promise<ZipArchive>;
|
|
16
|
+
}
|
|
17
|
+
export interface SheetModule {
|
|
18
|
+
read(data: Uint8Array, options: {
|
|
19
|
+
type: 'array';
|
|
20
|
+
}): {
|
|
21
|
+
SheetNames: string[];
|
|
22
|
+
Sheets: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
utils: {
|
|
25
|
+
sheet_to_json(sheet: unknown, options: {
|
|
26
|
+
header: 1;
|
|
27
|
+
blankrows: false;
|
|
28
|
+
}): unknown[][];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface ChartModel {
|
|
32
|
+
title: string;
|
|
33
|
+
categories: string[];
|
|
34
|
+
series: Array<{
|
|
35
|
+
name: string;
|
|
36
|
+
color: string;
|
|
37
|
+
values: number[];
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export type DocxPlaceholder = {
|
|
41
|
+
token: string;
|
|
42
|
+
kind: 'chart';
|
|
43
|
+
chart: ChartModel;
|
|
44
|
+
} | {
|
|
45
|
+
token: string;
|
|
46
|
+
kind: 'sheet';
|
|
47
|
+
title: string;
|
|
48
|
+
rows: string[][];
|
|
49
|
+
};
|
|
50
|
+
export interface DocxPreprocessResult {
|
|
51
|
+
data: Uint8Array;
|
|
52
|
+
placeholders: DocxPlaceholder[];
|
|
53
|
+
}
|
|
54
|
+
/** Default cap on declared/observed uncompressed bytes (input is already
|
|
55
|
+
* capped at 50 MB by the viewer; this bounds the expansion ratio). */
|
|
56
|
+
export declare const DOCX_MAX_DECOMPRESSED_BYTES: number;
|
|
57
|
+
/** Thrown before any inflate when the archive declares more data than the cap
|
|
58
|
+
* — the word viewer maps it to `diag.word.decompression-limit`. */
|
|
59
|
+
export declare class DocxDecompressionLimitError extends Error {
|
|
60
|
+
constructor();
|
|
61
|
+
}
|
|
62
|
+
export declare function preprocessDocx(data: Uint8Array, zipModule: ZipModule, sheetModule?: SheetModule, maxDecompressedBytes?: number): Promise<DocxPreprocessResult>;
|
|
63
|
+
export declare function parseChart(xml: string): ChartModel | null;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { declaredZipUncompressedBytes } from '../../parsers/zip-scan.js';
|
|
2
|
+
/** Default cap on declared/observed uncompressed bytes (input is already
|
|
3
|
+
* capped at 50 MB by the viewer; this bounds the expansion ratio). */
|
|
4
|
+
export const DOCX_MAX_DECOMPRESSED_BYTES = 1024 * 1024 * 1024;
|
|
5
|
+
/** Thrown before any inflate when the archive declares more data than the cap
|
|
6
|
+
* — the word viewer maps it to `diag.word.decompression-limit`. */
|
|
7
|
+
export class DocxDecompressionLimitError extends Error {
|
|
8
|
+
constructor() { super('DOCX declares more uncompressed data than the supported limit'); this.name = 'DocxDecompressionLimitError'; }
|
|
9
|
+
}
|
|
10
|
+
const entities = (value) => value.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&');
|
|
11
|
+
const resolvePath = (base, target) => { const stack = base.split('/').slice(0, -1); for (const part of target.split('/')) {
|
|
12
|
+
if (!part || part === '.')
|
|
13
|
+
continue;
|
|
14
|
+
if (part === '..')
|
|
15
|
+
stack.pop();
|
|
16
|
+
else
|
|
17
|
+
stack.push(part);
|
|
18
|
+
} return stack.join('/'); };
|
|
19
|
+
const attrs = (xml) => new Map([...xml.matchAll(/([\w:]+)="([^"]*)"/g)].map((match) => [match[1] ?? '', match[2] ?? '']));
|
|
20
|
+
export async function preprocessDocx(data, zipModule, sheetModule, maxDecompressedBytes = DOCX_MAX_DECOMPRESSED_BYTES) {
|
|
21
|
+
// Zip-bomb guard: reject on the central directory's declared total before
|
|
22
|
+
// any inflate, then keep a running budget over what we actually extract
|
|
23
|
+
// (headers can lie about per-entry sizes).
|
|
24
|
+
const declared = declaredZipUncompressedBytes(data);
|
|
25
|
+
if (declared !== null && declared > maxDecompressedBytes)
|
|
26
|
+
throw new DocxDecompressionLimitError();
|
|
27
|
+
let budget = maxDecompressedBytes;
|
|
28
|
+
const spend = (value) => { budget -= value.length; if (budget < 0)
|
|
29
|
+
throw new DocxDecompressionLimitError(); return value; };
|
|
30
|
+
const zip = await zipModule.loadAsync(data);
|
|
31
|
+
const placeholders = [];
|
|
32
|
+
let changed = false;
|
|
33
|
+
const xmlFiles = [];
|
|
34
|
+
zip.forEach((path, entry) => { if (!entry.dir && /^word\/.*\.xml$/i.test(path))
|
|
35
|
+
xmlFiles.push(path); });
|
|
36
|
+
for (const path of xmlFiles) {
|
|
37
|
+
const entry = zip.file(path);
|
|
38
|
+
if (!entry)
|
|
39
|
+
continue;
|
|
40
|
+
const xml = spend(await entry.async('string'));
|
|
41
|
+
const normalized = xml.replace(/<mc:AlternateContent[\s\S]*?<mc:Fallback>([\s\S]*?)<\/mc:Fallback>[\s\S]*?<\/mc:AlternateContent>/g, '$1');
|
|
42
|
+
if (normalized !== xml) {
|
|
43
|
+
zip.file(path, normalized);
|
|
44
|
+
changed = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const documentEntry = zip.file('word/document.xml'), relsEntry = zip.file('word/_rels/document.xml.rels');
|
|
48
|
+
if (!documentEntry || !relsEntry)
|
|
49
|
+
return { data, placeholders };
|
|
50
|
+
let documentXml = spend(await documentEntry.async('string'));
|
|
51
|
+
const relsXml = spend(await relsEntry.async('string'));
|
|
52
|
+
const relationships = new Map();
|
|
53
|
+
for (const match of relsXml.matchAll(/<Relationship\b[^>]*\/?\s*>/g)) {
|
|
54
|
+
const values = attrs(match[0]);
|
|
55
|
+
const id = values.get('Id'), target = values.get('Target');
|
|
56
|
+
if (id && target)
|
|
57
|
+
relationships.set(id, { target: resolvePath('word/document.xml', target), type: values.get('Type') ?? '' });
|
|
58
|
+
}
|
|
59
|
+
let index = 0;
|
|
60
|
+
const chartRegex = /<w:drawing[\s\S]*?<c:chart\b[^>]*r:id="([^"]+)"[\s\S]*?<\/w:drawing>/g;
|
|
61
|
+
let output = '', cursor = 0;
|
|
62
|
+
for (const match of documentXml.matchAll(chartRegex)) {
|
|
63
|
+
const start = match.index ?? 0;
|
|
64
|
+
output += documentXml.slice(cursor, start);
|
|
65
|
+
cursor = start + match[0].length;
|
|
66
|
+
const relation = relationships.get(match[1] ?? '');
|
|
67
|
+
const entry = relation ? zip.file(relation.target) : null;
|
|
68
|
+
const chart = entry ? parseChart(spend(await entry.async('string'))) : null;
|
|
69
|
+
if (!chart) {
|
|
70
|
+
output += match[0];
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const token = `__OMNI_WORD_CHART_${index++}__`;
|
|
74
|
+
placeholders.push({ token, kind: 'chart', chart });
|
|
75
|
+
output += `<w:r><w:t xml:space="preserve">${token}</w:t></w:r>`;
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
documentXml = output + documentXml.slice(cursor);
|
|
79
|
+
if (sheetModule) {
|
|
80
|
+
const objectRegex = /<w:object[\s\S]*?<o:OLEObject\b[^>]*r:id="([^"]+)"[\s\S]*?<\/w:object>/g;
|
|
81
|
+
output = '';
|
|
82
|
+
cursor = 0;
|
|
83
|
+
for (const match of documentXml.matchAll(objectRegex)) {
|
|
84
|
+
const start = match.index ?? 0;
|
|
85
|
+
output += documentXml.slice(cursor, start);
|
|
86
|
+
cursor = start + match[0].length;
|
|
87
|
+
const relation = relationships.get(match[1] ?? '');
|
|
88
|
+
const entry = relation ? zip.file(relation.target) : null;
|
|
89
|
+
if (!entry || !/package|oleObject/i.test(relation?.type ?? '')) {
|
|
90
|
+
output += match[0];
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const bytes = spend(await entry.async('uint8array'));
|
|
95
|
+
if ((declaredZipUncompressedBytes(bytes) ?? 0) > budget) {
|
|
96
|
+
output += match[0];
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const workbook = sheetModule.read(bytes, { type: 'array' });
|
|
100
|
+
const name = workbook.SheetNames[0];
|
|
101
|
+
const sheet = name ? workbook.Sheets[name] : undefined;
|
|
102
|
+
if (!name || !sheet) {
|
|
103
|
+
output += match[0];
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const rows = sheetModule.utils.sheet_to_json(sheet, { header: 1, blankrows: false }).slice(0, 10).map((row) => row.slice(0, 10).map((cell) => String(cell ?? '')));
|
|
107
|
+
const token = `__OMNI_WORD_SHEET_${index++}__`;
|
|
108
|
+
placeholders.push({ token, kind: 'sheet', title: name, rows });
|
|
109
|
+
output += `<w:r><w:t xml:space="preserve">${token}</w:t></w:r>`;
|
|
110
|
+
changed = true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
output += match[0];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
documentXml = output + documentXml.slice(cursor);
|
|
117
|
+
}
|
|
118
|
+
if (changed) {
|
|
119
|
+
zip.file('word/document.xml', documentXml);
|
|
120
|
+
return { data: await zip.generateAsync({ type: 'uint8array' }), placeholders };
|
|
121
|
+
}
|
|
122
|
+
return { data, placeholders };
|
|
123
|
+
}
|
|
124
|
+
export function parseChart(xml) {
|
|
125
|
+
const seriesXml = [...xml.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)].map((match) => match[1] ?? '');
|
|
126
|
+
if (!seriesXml.length)
|
|
127
|
+
return null;
|
|
128
|
+
const points = (block, numeric) => { const result = []; for (const match of block.matchAll(/<c:pt\b[^>]*idx="(\d+)"[^>]*>[\s\S]*?<c:v>([\s\S]*?)<\/c:v>[\s\S]*?<\/c:pt>/g))
|
|
129
|
+
result[Number(match[1])] = numeric ? Number(match[2]) || 0 : entities(match[2] ?? ''); return result; };
|
|
130
|
+
const parsed = seriesXml.map((series, index) => { const cat = series.match(/<c:cat[\s\S]*?<\/c:cat>/)?.[0] ?? ''; const val = series.match(/<c:val[\s\S]*?<\/c:val>/)?.[0] ?? ''; const name = entities(series.match(/<c:tx[\s\S]*?<c:v>([\s\S]*?)<\/c:v>/)?.[1] ?? `Series ${index + 1}`); const color = `#${series.match(/<a:srgbClr\b[^>]*val="([0-9a-f]{6})"/i)?.[1] ?? ['004586', 'ff420e', 'ffd320', '579d1c'][index % 4]}`; return { name, color, categories: points(cat, false).map(String), values: points(val, true).map(Number) }; });
|
|
131
|
+
const categories = parsed[0]?.categories ?? [];
|
|
132
|
+
if (!categories.length)
|
|
133
|
+
return null;
|
|
134
|
+
const title = entities([...xml.matchAll(/<c:title[\s\S]*?<a:t>([\s\S]*?)<\/a:t>[\s\S]*?<\/c:title>/g)].map((match) => match[1]).join(' '));
|
|
135
|
+
return { title, categories, series: parsed.map(({ name, color, values }) => ({ name, color, values })) };
|
|
136
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type HostContext, type NavigationService, type PrintService } from '../../host/index.js';
|
|
2
|
+
import { type MountOptions, type ViewerHandle, type ViewerInput } from '../types.js';
|
|
3
|
+
import { type WordController } from './controller.js';
|
|
4
|
+
import { type SheetModule, type ZipModule } from './docx-preprocess.js';
|
|
5
|
+
export * from './controller.js';
|
|
6
|
+
export { wordViewerCss } from './styles.js';
|
|
7
|
+
export declare const WORD_VIEWER_META: {
|
|
8
|
+
id: string;
|
|
9
|
+
displayNameKey: string;
|
|
10
|
+
extensions: string[];
|
|
11
|
+
priority: number;
|
|
12
|
+
requiredServices: readonly [];
|
|
13
|
+
optionalServices: readonly ["print", "navigation"];
|
|
14
|
+
inputOwnership: "consumes";
|
|
15
|
+
};
|
|
16
|
+
export declare const WORD_MAX_INPUT_BYTES: number;
|
|
17
|
+
export interface DocxPreviewModule {
|
|
18
|
+
renderAsync(data: ArrayBuffer | Uint8Array, body: HTMLElement, styleContainer?: HTMLElement, options?: Record<string, unknown>): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface WordViewerDeps {
|
|
21
|
+
loadDocxPreview(): Promise<DocxPreviewModule>;
|
|
22
|
+
loadZip(): Promise<ZipModule>;
|
|
23
|
+
loadSheet?(): Promise<SheetModule>;
|
|
24
|
+
}
|
|
25
|
+
export type WordViewerContext = HostContext & {
|
|
26
|
+
print?: PrintService;
|
|
27
|
+
navigation?: NavigationService;
|
|
28
|
+
};
|
|
29
|
+
export interface WordViewerHandle extends ViewerHandle {
|
|
30
|
+
readonly controller: WordController;
|
|
31
|
+
}
|
|
32
|
+
export declare function mountWordViewer(input: ViewerInput, container: HTMLElement, ctx: WordViewerContext, deps?: Partial<WordViewerDeps>, options?: MountOptions): Promise<WordViewerHandle>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { ALLOWED_LINK_SCHEMES } from '../../host/index.js';
|
|
2
|
+
import { DocBinaryParser } from '../../parsers/doc-binary/index.js';
|
|
3
|
+
import { MountAbortedError, VIEWER_ROOT_CLASS } from '../types.js';
|
|
4
|
+
import { createWordController } from './controller.js';
|
|
5
|
+
import { wordViewerCss } from './styles.js';
|
|
6
|
+
import { DocxDecompressionLimitError, preprocessDocx } from './docx-preprocess.js';
|
|
7
|
+
import { paginateLegacyDocument } from './paginate.js';
|
|
8
|
+
import { normalizeDocxPreviewDom } from './normalize-docx.js';
|
|
9
|
+
export * from './controller.js';
|
|
10
|
+
export { wordViewerCss } from './styles.js';
|
|
11
|
+
export const WORD_VIEWER_META = { id: 'word', displayNameKey: 'word.title', extensions: ['docx', 'doc'], priority: 15, requiredServices: [], optionalServices: ['print', 'navigation'], inputOwnership: 'consumes' };
|
|
12
|
+
export const WORD_MAX_INPUT_BYTES = 50 * 1024 * 1024;
|
|
13
|
+
const el = (tag, cls, text) => {
|
|
14
|
+
const node = document.createElement(tag);
|
|
15
|
+
if (cls)
|
|
16
|
+
node.className = cls;
|
|
17
|
+
if (text !== undefined)
|
|
18
|
+
node.textContent = text;
|
|
19
|
+
return node;
|
|
20
|
+
};
|
|
21
|
+
export async function mountWordViewer(input, container, ctx, deps = {}, options = {}) {
|
|
22
|
+
if (options.signal?.aborted)
|
|
23
|
+
throw new MountAbortedError();
|
|
24
|
+
if (input.data.byteLength > WORD_MAX_INPUT_BYTES)
|
|
25
|
+
throw new Error(ctx.i18n.t('diag.word.limit-exceeded'));
|
|
26
|
+
const root = options.styleIsolation !== 'scoped' && typeof container.attachShadow === 'function' ? (container.shadowRoot ?? container.attachShadow({ mode: 'open' })) : container;
|
|
27
|
+
root.replaceChildren();
|
|
28
|
+
if (root === container)
|
|
29
|
+
container.classList.add(VIEWER_ROOT_CLASS, 'omni-viewer--word');
|
|
30
|
+
else {
|
|
31
|
+
const style = el('style');
|
|
32
|
+
style.textContent = wordViewerCss;
|
|
33
|
+
root.append(style);
|
|
34
|
+
}
|
|
35
|
+
const frame = el('section', 'omni-word');
|
|
36
|
+
const header = el('header', 'omni-word__header');
|
|
37
|
+
header.append(el('span', 'omni-word__title', `📄 ${input.fileName}`), el('span', 'omni-word__meta', `${formatBytes(input.data.byteLength)} · ${input.fileName.toLowerCase().endsWith('.docx') ? 'DOCX' : 'legacy DOC'}`));
|
|
38
|
+
const toolbar = el('div', 'omni-word__toolbar');
|
|
39
|
+
const out = el('button', undefined, '−'), level = el('span', 'omni-word__zoom', '100%'), inc = el('button', undefined, '+'), reset = el('button', undefined, ctx.i18n.t('word.reset')), print = el('button', undefined, ctx.i18n.t('word.print'));
|
|
40
|
+
out.type = inc.type = reset.type = print.type = 'button';
|
|
41
|
+
out.setAttribute('aria-label', ctx.i18n.t('word.zoomOut'));
|
|
42
|
+
inc.setAttribute('aria-label', ctx.i18n.t('word.zoomIn'));
|
|
43
|
+
reset.setAttribute('aria-label', ctx.i18n.t('word.reset'));
|
|
44
|
+
print.setAttribute('aria-label', ctx.i18n.t('word.print'));
|
|
45
|
+
if (!ctx.print) {
|
|
46
|
+
print.disabled = true;
|
|
47
|
+
print.title = ctx.i18n.t('word.printUnavailable');
|
|
48
|
+
}
|
|
49
|
+
toolbar.append(out, level, inc, reset, print);
|
|
50
|
+
const viewport = el('main', 'omni-word__viewport');
|
|
51
|
+
const content = el('div', 'omni-word__content');
|
|
52
|
+
content.setAttribute('role', 'document');
|
|
53
|
+
viewport.append(content);
|
|
54
|
+
frame.append(header, toolbar, viewport);
|
|
55
|
+
root.append(frame);
|
|
56
|
+
const controller = createWordController();
|
|
57
|
+
const disposers = [];
|
|
58
|
+
const listen = (target, name, fn) => { target.addEventListener(name, fn); disposers.push(() => target.removeEventListener(name, fn)); };
|
|
59
|
+
const off = controller.subscribe((state) => { content.style.transform = `scale(${state.zoom})`; level.textContent = `${Math.round(state.zoom * 100)}%`; });
|
|
60
|
+
out.onclick = () => controller.dispatch({ type: 'zoom-out' });
|
|
61
|
+
inc.onclick = () => controller.dispatch({ type: 'zoom-in' });
|
|
62
|
+
reset.onclick = () => controller.dispatch({ type: 'reset-zoom' });
|
|
63
|
+
print.onclick = () => void ctx.print?.print();
|
|
64
|
+
listen(frame, 'keydown', ((event) => { if (!(event.ctrlKey || event.metaKey))
|
|
65
|
+
return; if (event.key === '+' || event.key === '=')
|
|
66
|
+
controller.dispatch({ type: 'zoom-in' });
|
|
67
|
+
else if (event.key === '-')
|
|
68
|
+
controller.dispatch({ type: 'zoom-out' });
|
|
69
|
+
else if (event.key === '0')
|
|
70
|
+
controller.dispatch({ type: 'reset-zoom' });
|
|
71
|
+
else if (event.key.toLowerCase() === 'p' && ctx.print)
|
|
72
|
+
void ctx.print.print();
|
|
73
|
+
else
|
|
74
|
+
return; event.preventDefault(); }));
|
|
75
|
+
listen(viewport, 'wheel', ((event) => { if (!(event.ctrlKey || event.metaKey))
|
|
76
|
+
return; event.preventDefault(); controller.dispatch({ type: event.deltaY < 0 ? 'zoom-in' : 'zoom-out' }); }));
|
|
77
|
+
try {
|
|
78
|
+
if (input.fileName.toLowerCase().endsWith('.docx')) {
|
|
79
|
+
content.classList.add('word-content', 'docx-mode');
|
|
80
|
+
content.classList.remove('legacy-mode');
|
|
81
|
+
if (!deps.loadDocxPreview)
|
|
82
|
+
throw new Error(ctx.i18n.t('diag.word.missing-dependency'));
|
|
83
|
+
if (!deps.loadZip)
|
|
84
|
+
throw new Error(ctx.i18n.t('diag.word.missing-dependency'));
|
|
85
|
+
const [docx, zip, sheet] = await Promise.all([deps.loadDocxPreview(), deps.loadZip(), deps.loadSheet?.()]);
|
|
86
|
+
if (options.signal?.aborted)
|
|
87
|
+
throw new MountAbortedError();
|
|
88
|
+
const prepared = await preprocessDocx(input.data, zip, sheet);
|
|
89
|
+
await docx.renderAsync(prepared.data, content, content, { inWrapper: true, breakPages: true, ignoreWidth: false, ignoreHeight: false, ignoreFonts: false, renderHeaders: true, renderFooters: true });
|
|
90
|
+
injectDocxPlaceholders(content, prepared.placeholders);
|
|
91
|
+
normalizeDocxPreviewDom(content);
|
|
92
|
+
secureDocxDom(content, ctx, disposers);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
content.classList.add('word-content', 'legacy-mode');
|
|
96
|
+
if (!deps.loadSheet)
|
|
97
|
+
ctx.logger.log('warn', 'word: legacy embedded workbook previews disabled because loadSheet is unavailable');
|
|
98
|
+
if (!deps.loadZip)
|
|
99
|
+
ctx.logger.log('warn', 'word: legacy embedded package/chart previews disabled because loadZip is unavailable');
|
|
100
|
+
const [legacyZip, legacySheet] = await Promise.all([deps.loadZip?.(), deps.loadSheet?.()]);
|
|
101
|
+
const legacyHtml = await DocBinaryParser.parseBytes(input.data, { jszip: legacyZip, xlsx: legacySheet });
|
|
102
|
+
const parsed = new DOMParser().parseFromString(legacyHtml, 'text/html');
|
|
103
|
+
parsed.querySelectorAll('script:not(.ov-doc-legacy-section-meta),iframe,object,embed').forEach((node) => node.remove());
|
|
104
|
+
const fragment = document.createDocumentFragment();
|
|
105
|
+
for (const child of [...parsed.body.childNodes])
|
|
106
|
+
fragment.append(document.importNode(child, true));
|
|
107
|
+
content.append(fragment);
|
|
108
|
+
secureDocxDom(content, ctx, disposers);
|
|
109
|
+
paginateLegacyDocument(content);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof MountAbortedError) {
|
|
114
|
+
off();
|
|
115
|
+
disposers.forEach((dispose) => dispose());
|
|
116
|
+
frame.remove();
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
content.replaceChildren(el('div', 'omni-word__error', error instanceof DocxDecompressionLimitError ? ctx.i18n.t('diag.word.decompression-limit') : error instanceof Error ? error.message : String(error)));
|
|
120
|
+
}
|
|
121
|
+
return { controller, dispose() { off(); disposers.forEach((dispose) => dispose()); frame.remove(); } };
|
|
122
|
+
}
|
|
123
|
+
function formatBytes(bytes) { if (bytes < 1024)
|
|
124
|
+
return `${bytes} B`; if (bytes < 1024 * 1024)
|
|
125
|
+
return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; }
|
|
126
|
+
function injectDocxPlaceholders(root, placeholders) {
|
|
127
|
+
for (const placeholder of placeholders) {
|
|
128
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
129
|
+
let node;
|
|
130
|
+
while ((node = walker.nextNode())) {
|
|
131
|
+
const value = node.textContent ?? '';
|
|
132
|
+
const index = value.indexOf(placeholder.token);
|
|
133
|
+
if (index < 0 || !node.parentNode)
|
|
134
|
+
continue;
|
|
135
|
+
const fragment = document.createDocumentFragment();
|
|
136
|
+
if (index)
|
|
137
|
+
fragment.append(value.slice(0, index));
|
|
138
|
+
fragment.append(placeholder.kind === 'chart' ? renderChart(placeholder.chart) : renderEmbeddedSheet(placeholder.title, placeholder.rows));
|
|
139
|
+
if (index + placeholder.token.length < value.length)
|
|
140
|
+
fragment.append(value.slice(index + placeholder.token.length));
|
|
141
|
+
node.parentNode.replaceChild(fragment, node);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function renderEmbeddedSheet(title, rows) {
|
|
147
|
+
const section = el('section', 'omni-word__embedded-sheet');
|
|
148
|
+
section.append(el('h3', undefined, title));
|
|
149
|
+
const table = el('table', 'omni-word__legacy-table');
|
|
150
|
+
rows.forEach((row, rowIndex) => { const tr = el('tr'); row.forEach((value) => tr.append(el(rowIndex ? 'td' : 'th', undefined, value))); table.append(tr); });
|
|
151
|
+
section.append(table);
|
|
152
|
+
return section;
|
|
153
|
+
}
|
|
154
|
+
function renderChart(chart) {
|
|
155
|
+
const card = el('figure', 'omni-word__chart');
|
|
156
|
+
card.setAttribute('aria-label', chart.title || 'Chart');
|
|
157
|
+
if (chart.title)
|
|
158
|
+
card.append(el('figcaption', undefined, chart.title));
|
|
159
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
160
|
+
svg.setAttribute('viewBox', '0 0 640 300');
|
|
161
|
+
svg.setAttribute('role', 'img');
|
|
162
|
+
const max = Math.max(1, ...chart.series.flatMap((series) => series.values));
|
|
163
|
+
const group = 520 / Math.max(1, chart.categories.length), bar = group / Math.max(1, chart.series.length + 1);
|
|
164
|
+
chart.series.forEach((series, seriesIndex) => series.values.forEach((value, valueIndex) => { const rect = document.createElementNS(svg.namespaceURI, 'rect'); const height = Math.max(0, value) / max * 220; rect.setAttribute('x', String(70 + valueIndex * group + seriesIndex * bar)); rect.setAttribute('y', String(250 - height)); rect.setAttribute('width', String(Math.max(2, bar - 2))); rect.setAttribute('height', String(height)); rect.setAttribute('fill', series.color); svg.append(rect); }));
|
|
165
|
+
card.append(svg);
|
|
166
|
+
const legend = el('div', 'omni-word__chart-legend');
|
|
167
|
+
chart.series.forEach((series) => { const item = el('span', undefined, series.name); item.style.borderLeft = `12px solid ${series.color}`; legend.append(item); });
|
|
168
|
+
card.append(legend);
|
|
169
|
+
return card;
|
|
170
|
+
}
|
|
171
|
+
function secureDocxDom(root, ctx, disposers) {
|
|
172
|
+
root.querySelectorAll('img,source,video,audio').forEach((node) => { for (const attr of ['src', 'srcset']) {
|
|
173
|
+
const value = node.getAttribute(attr);
|
|
174
|
+
if (value && /^(?:https?:)?\/\//i.test(value))
|
|
175
|
+
node.removeAttribute(attr);
|
|
176
|
+
} });
|
|
177
|
+
root.querySelectorAll('a').forEach((anchor) => { const href = anchor.getAttribute('href') ?? ''; anchor.removeAttribute('href'); anchor.removeAttribute('target'); let allowed = false; try {
|
|
178
|
+
allowed = ALLOWED_LINK_SCHEMES.includes(new URL(href).protocol);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
allowed = false;
|
|
182
|
+
} if (!allowed || !ctx.navigation) {
|
|
183
|
+
anchor.setAttribute('aria-disabled', 'true');
|
|
184
|
+
return;
|
|
185
|
+
} anchor.setAttribute('role', 'link'); anchor.tabIndex = 0; const open = (event) => { event.preventDefault(); void ctx.navigation?.openExternalUrl(href); }; anchor.addEventListener('click', open); disposers.push(() => anchor.removeEventListener('click', open)); });
|
|
186
|
+
root.querySelectorAll('iframe,object,embed,script:not(.ov-doc-legacy-section-meta)').forEach((node) => node.remove());
|
|
187
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Normalizes glyphs emitted by docx-preview for Wingdings/PUA list markers. */
|
|
2
|
+
export function normalizeDocxPreviewDom(wordContent) {
|
|
3
|
+
const brokenBulletPrefix = /^[\u25A1\u25A0\u25FB\u25FC\uF0A7\uF0B7]\s*/;
|
|
4
|
+
const brokenBulletSingle = /[\u25A1\u25A0\u25FB\u25FC\uF0A7\uF0B7]/;
|
|
5
|
+
const normalizeLeadingGlyph = (element) => {
|
|
6
|
+
for (const node of element.childNodes) {
|
|
7
|
+
if (node.nodeType !== Node.TEXT_NODE)
|
|
8
|
+
continue;
|
|
9
|
+
const value = node.textContent ?? '', trimmed = value.trimStart();
|
|
10
|
+
if (!trimmed)
|
|
11
|
+
continue;
|
|
12
|
+
if (brokenBulletPrefix.test(trimmed)) {
|
|
13
|
+
const leadingSpaces = value.slice(0, value.length - trimmed.length);
|
|
14
|
+
node.textContent = `${leadingSpaces}${trimmed.replace(brokenBulletPrefix, '* ')}`;
|
|
15
|
+
}
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
wordContent.querySelectorAll('p, li').forEach((element) => {
|
|
20
|
+
normalizeLeadingGlyph(element);
|
|
21
|
+
const firstSpan = element.querySelector('span');
|
|
22
|
+
if (firstSpan && brokenBulletPrefix.test(firstSpan.textContent ?? ''))
|
|
23
|
+
firstSpan.textContent = (firstSpan.textContent ?? '').replace(brokenBulletPrefix, '* ');
|
|
24
|
+
});
|
|
25
|
+
wordContent.querySelectorAll('p[class*="docx-num-"], li[class*="docx-num-"]').forEach((element) => {
|
|
26
|
+
const beforeContent = window.getComputedStyle(element, '::before').content ?? '';
|
|
27
|
+
if (brokenBulletSingle.test(beforeContent))
|
|
28
|
+
element.setAttribute('data-ov-bullet-fix', '1');
|
|
29
|
+
});
|
|
30
|
+
normalizeChartSpacerParagraphs(wordContent);
|
|
31
|
+
}
|
|
32
|
+
function normalizeChartSpacerParagraphs(root) {
|
|
33
|
+
const visualEmpty = (paragraph) => paragraph.tagName === 'P' && !paragraph.querySelector('.omni-word__chart,.ov-chart-card,table,img,svg,canvas,object,iframe') && (paragraph.textContent ?? '').replace(/\u200b/g, '').trim() === '';
|
|
34
|
+
root.querySelectorAll('p[data-ov-chart-spacer]').forEach((element) => element.removeAttribute('data-ov-chart-spacer'));
|
|
35
|
+
root.querySelectorAll('p').forEach((paragraph) => {
|
|
36
|
+
if (!paragraph.querySelector('.omni-word__chart,.ov-chart-card'))
|
|
37
|
+
return;
|
|
38
|
+
const clone = paragraph.cloneNode(true);
|
|
39
|
+
clone.querySelectorAll('.omni-word__chart,.ov-chart-card').forEach((node) => node.remove());
|
|
40
|
+
if ((clone.textContent ?? '').replace(/\u200b/g, '').trim())
|
|
41
|
+
return;
|
|
42
|
+
const empty = [];
|
|
43
|
+
let sibling = paragraph.nextElementSibling;
|
|
44
|
+
while (sibling && visualEmpty(sibling)) {
|
|
45
|
+
empty.push(sibling);
|
|
46
|
+
sibling = sibling.nextElementSibling;
|
|
47
|
+
}
|
|
48
|
+
empty.forEach((element, index) => element.setAttribute('data-ov-chart-spacer', index === 0 ? 'keep' : 'trim'));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function paginateLegacyDocument(wordContent: HTMLElement): void;
|