mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 00:14:53 -06:00
feat: add DOCX/XLSX/PPTX file preview
- DOCX: mammoth converts to semantic HTML (prose preview) - XLSX: xlsx library extended to FileNav with sheet tabs at bottom - PPTX: custom canvas renderer produces PNG images per slide with panzoom zoom/pan and slide navigation Changes: - New: src/lib/utils/pptxToHtml.ts (canvas-based PPTX renderer) - FileNav.svelte: office format detection, blob download, conversion - FilePreview.svelte: office rendering branches, sheet tabs, slide viewer - FileItemModal.svelte: DOCX/PPTX preview tabs - package.json: added mammoth dependency
This commit is contained in:
Generated
+110
-99
@@ -66,6 +66,7 @@
|
||||
"idb": "^7.1.1",
|
||||
"js-sha256": "^0.10.1",
|
||||
"jspdf": "^4.0.0",
|
||||
"jszip": "^3.10.1",
|
||||
"katex": "^0.16.22",
|
||||
"kokoro-js": "^1.1.1",
|
||||
"leaflet": "^1.9.4",
|
||||
@@ -223,7 +224,7 @@
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz",
|
||||
"integrity": "sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@chevrotain/cst-dts-gen": {
|
||||
@@ -1850,6 +1851,7 @@
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
@@ -1860,6 +1862,7 @@
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@@ -1870,6 +1873,7 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@@ -1884,6 +1888,7 @@
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@@ -2304,6 +2309,7 @@
|
||||
"version": "1.0.0-next.28",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
|
||||
"integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
@@ -2702,6 +2708,7 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
|
||||
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.9.0"
|
||||
@@ -2747,6 +2754,7 @@
|
||||
"version": "2.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.22.4.tgz",
|
||||
"integrity": "sha512-BXK9hTbP8AeQIfoz6+P3uoyVYStVHc5CIKqoTSF7hXm3Q5P9BwFMdEus4jsQuhaYmXGHzukcGlxe2QrsE8BJfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||
@@ -2784,6 +2792,7 @@
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz",
|
||||
"integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0",
|
||||
@@ -2805,6 +2814,7 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
|
||||
"integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.7"
|
||||
@@ -2822,6 +2832,7 @@
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -2839,12 +2850,14 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -2862,6 +2875,7 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
@@ -3265,23 +3279,6 @@
|
||||
"lowlight": "^2 || ^3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-collaboration": {
|
||||
"version": "3.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-3.20.0.tgz",
|
||||
"integrity": "sha512-JItmI4U0i4kqorO114u24hM9k945IdaQ6Uc2DEtPBFFuS8cepJf2zw+ulAT1kAx6ZRiNvNpT9M7w+J0mWRn+Sg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^3.20.0",
|
||||
"@tiptap/pm": "^3.20.0",
|
||||
"@tiptap/y-tiptap": "^3.0.2",
|
||||
"yjs": "^13"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-document": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.0.7.tgz",
|
||||
@@ -3524,21 +3521,6 @@
|
||||
"@tiptap/suggestion": "^3.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-node-range": {
|
||||
"version": "3.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-node-range/-/extension-node-range-3.20.0.tgz",
|
||||
"integrity": "sha512-XeKKTV88VuJ4Mh0Rxvc/PPzG76cb44sE+rB4u0J/ms63R/WFTm6yJQlCgUVGnGeHleSlrWuZY8gGSuoljmQzqg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^3.20.0",
|
||||
"@tiptap/pm": "^3.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-ordered-list": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.0.7.tgz",
|
||||
@@ -3605,20 +3587,6 @@
|
||||
"@tiptap/core": "^3.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text-style": {
|
||||
"version": "3.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.20.0.tgz",
|
||||
"integrity": "sha512-zyWW1a6W+kaXAn3wv2svJ1XuVMapujftvH7Xn2Q3QmKKiDkO+NiFkrGe8BhMopu8Im51nO3NylIgVA0X1mS1rQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^3.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-typography": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-3.0.7.tgz",
|
||||
@@ -3752,31 +3720,11 @@
|
||||
"@tiptap/pm": "^3.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/y-tiptap": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.2.tgz",
|
||||
"integrity": "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.100"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prosemirror-model": "^1.7.1",
|
||||
"prosemirror-state": "^1.2.3",
|
||||
"prosemirror-view": "^1.9.10",
|
||||
"y-protocols": "^1.0.1",
|
||||
"yjs": "^13.5.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3": {
|
||||
"version": "7.4.3",
|
||||
@@ -4713,6 +4661,7 @@
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -4794,6 +4743,7 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -5074,7 +5024,7 @@
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
|
||||
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT/X11"
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
@@ -5620,6 +5570,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -5737,7 +5688,7 @@
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colors": {
|
||||
@@ -5806,6 +5757,7 @@
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -6675,7 +6627,8 @@
|
||||
"node_modules/devalue": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
|
||||
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="
|
||||
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/devlop": {
|
||||
"version": "1.1.0",
|
||||
@@ -7229,6 +7182,7 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/espree": {
|
||||
@@ -7264,6 +7218,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.1.tgz",
|
||||
"integrity": "sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
@@ -8011,7 +7966,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -8341,7 +8296,7 @@
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
|
||||
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
@@ -8800,6 +8755,7 @@
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -8928,7 +8884,7 @@
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz",
|
||||
"integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3"
|
||||
@@ -8960,6 +8916,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8980,6 +8937,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9000,6 +8958,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9020,6 +8979,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9040,6 +9000,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9060,6 +9021,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9080,6 +9042,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9100,6 +9063,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9120,6 +9084,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9140,6 +9105,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9157,7 +9123,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
@@ -9276,7 +9242,8 @@
|
||||
"node_modules/locate-character": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
@@ -9840,6 +9807,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -9848,6 +9816,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
|
||||
"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -10379,6 +10348,7 @@
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -10510,6 +10480,7 @@
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -11340,7 +11311,7 @@
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -11349,6 +11320,7 @@
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mri": "^1.1.0"
|
||||
},
|
||||
@@ -11370,7 +11342,7 @@
|
||||
"version": "1.81.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.81.0.tgz",
|
||||
"integrity": "sha512-uZQ2Faxb1oWBHpeSSzjxnhClbMb3QadN0ql0ZFNuqWOLUxwaVhrMlMhPq6TDPbbfDUjihuwrMCuy695Bgna5RA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
@@ -11418,6 +11390,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11434,6 +11407,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11450,6 +11424,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11466,6 +11441,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11482,6 +11458,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11498,6 +11475,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11514,6 +11492,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11530,6 +11509,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11546,6 +11526,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11562,6 +11543,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11578,6 +11560,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11594,6 +11577,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11610,6 +11594,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11626,6 +11611,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11642,6 +11628,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11658,6 +11645,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11674,6 +11662,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11690,6 +11679,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11706,6 +11696,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11722,6 +11713,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11735,7 +11727,7 @@
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
@@ -11761,7 +11753,8 @@
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
|
||||
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
|
||||
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
@@ -11890,6 +11883,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
|
||||
"integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@polka/url": "^1.0.0-next.24",
|
||||
@@ -11965,6 +11959,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12223,6 +12218,7 @@
|
||||
"version": "5.42.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.42.2.tgz",
|
||||
"integrity": "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
@@ -12302,21 +12298,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
@@ -12380,12 +12361,14 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/svelte/node_modules/is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.6"
|
||||
@@ -12411,7 +12394,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
|
||||
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sync-message-port": "^1.0.0"
|
||||
@@ -12424,7 +12407,7 @@
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
|
||||
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
@@ -12633,6 +12616,7 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
||||
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -12893,7 +12877,7 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vega": {
|
||||
@@ -13402,6 +13386,7 @@
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
@@ -13505,6 +13490,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
@@ -13520,6 +13506,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -13535,6 +13522,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -13550,6 +13538,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -13565,6 +13554,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -13580,6 +13570,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -13595,6 +13586,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@@ -13610,6 +13602,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@@ -13625,6 +13618,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13640,6 +13634,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13655,6 +13650,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13670,6 +13666,7 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13685,6 +13682,7 @@
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13700,6 +13698,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13715,6 +13714,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13730,6 +13730,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13745,6 +13746,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -13760,6 +13762,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
@@ -13775,6 +13778,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
@@ -13790,6 +13794,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
@@ -13805,6 +13810,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -13820,6 +13826,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -13835,6 +13842,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -13847,6 +13855,7 @@
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
@@ -13884,6 +13893,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
|
||||
"integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"tests/deps/*",
|
||||
@@ -14607,6 +14617,7 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"idb": "^7.1.1",
|
||||
"js-sha256": "^0.10.1",
|
||||
"jspdf": "^4.0.0",
|
||||
"jszip": "^3.10.1",
|
||||
"katex": "^0.16.22",
|
||||
"kokoro-js": "^1.1.1",
|
||||
"leaflet": "^1.9.4",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import FileNavToolbar from './FileNav/FileNavToolbar.svelte';
|
||||
import FilePreview from './FileNav/FilePreview.svelte';
|
||||
import FileEntryRow from './FileNav/FileEntryRow.svelte';
|
||||
import PortList from './FileNav/PortList.svelte';
|
||||
import XTerminal from './XTerminal.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
@@ -93,6 +94,14 @@
|
||||
let fileLoading = false;
|
||||
let filePreviewRef: FilePreview;
|
||||
|
||||
// ── Office preview state ────────────────────────────────────────────
|
||||
let fileOfficeHtml: string | null = null;
|
||||
let fileOfficeSlides: string[] | null = null;
|
||||
let currentSlide = 0;
|
||||
let excelSheetNames: string[] = [];
|
||||
let selectedExcelSheet = '';
|
||||
let excelWorkbook: import('xlsx').WorkBook | null = null;
|
||||
|
||||
// ── File preview toolbar state (bound from FilePreview) ─────────────
|
||||
let editing = false;
|
||||
let showRaw = false;
|
||||
@@ -101,12 +110,15 @@
|
||||
const MD_EXTS = new Set(['md', 'markdown', 'mdx']);
|
||||
const CSV_EXTS = new Set(['csv', 'tsv']);
|
||||
const HTML_EXTS = new Set(['html', 'htm']);
|
||||
const OFFICE_EXTS = new Set(['docx', 'xlsx', 'pptx']);
|
||||
const getFileExt = (path: string | null) => path?.split('.').pop()?.toLowerCase() ?? '';
|
||||
|
||||
$: isMarkdown = MD_EXTS.has(getFileExt(selectedFile));
|
||||
$: isCsv = CSV_EXTS.has(getFileExt(selectedFile));
|
||||
$: isHtml = HTML_EXTS.has(getFileExt(selectedFile));
|
||||
$: isTextFile = fileContent !== null && fileImageUrl === null && filePdfData === null;
|
||||
$: isOfficeFile = OFFICE_EXTS.has(getFileExt(selectedFile));
|
||||
$: isTextFile =
|
||||
fileContent !== null && fileImageUrl === null && filePdfData === null && !isOfficeFile;
|
||||
|
||||
// ── Upload / folder creation ─────────────────────────────────────────
|
||||
let isDragOver = false;
|
||||
@@ -169,6 +181,7 @@
|
||||
const IMAGE_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif']);
|
||||
const isImage = (path: string) => IMAGE_EXTS.has(path.split('.').pop()?.toLowerCase() ?? '');
|
||||
const isPdf = (path: string) => path.split('.').pop()?.toLowerCase() === 'pdf';
|
||||
const isOffice = (path: string) => OFFICE_EXTS.has(path.split('.').pop()?.toLowerCase() ?? '');
|
||||
|
||||
const buildBreadcrumbs = (path: string) =>
|
||||
path
|
||||
@@ -192,6 +205,12 @@
|
||||
fileImageUrl = null;
|
||||
}
|
||||
filePdfData = null;
|
||||
fileOfficeHtml = null;
|
||||
fileOfficeSlides = null;
|
||||
currentSlide = 0;
|
||||
excelSheetNames = [];
|
||||
selectedExcelSheet = '';
|
||||
excelWorkbook = null;
|
||||
};
|
||||
|
||||
// ── Directory operations ─────────────────────────────────────────────
|
||||
@@ -244,6 +263,41 @@
|
||||
} else if (isPdf(filePath)) {
|
||||
const result = await downloadFileBlob(terminal.url, terminal.key, filePath);
|
||||
if (result) filePdfData = await result.blob.arrayBuffer();
|
||||
} else if (isOffice(filePath)) {
|
||||
const result = await downloadFileBlob(terminal.url, terminal.key, filePath);
|
||||
if (result) {
|
||||
const ext = getFileExt(filePath);
|
||||
const arrayBuffer = await result.blob.arrayBuffer();
|
||||
try {
|
||||
if (ext === 'docx') {
|
||||
const mammoth = await import('mammoth');
|
||||
const res = await mammoth.convertToHtml({ arrayBuffer });
|
||||
const DOMPurify = (await import('dompurify')).default;
|
||||
fileOfficeHtml = DOMPurify.sanitize(res.value);
|
||||
} else if (ext === 'xlsx') {
|
||||
const XLSX = await import('xlsx');
|
||||
const wb = XLSX.read(new Uint8Array(arrayBuffer), { type: 'array' });
|
||||
excelWorkbook = wb;
|
||||
excelSheetNames = wb.SheetNames;
|
||||
if (excelSheetNames.length > 0) {
|
||||
selectedExcelSheet = excelSheetNames[0];
|
||||
const ws = wb.Sheets[selectedExcelSheet];
|
||||
const DOMPurify = (await import('dompurify')).default;
|
||||
fileOfficeHtml = DOMPurify.sanitize(
|
||||
XLSX.utils.sheet_to_html(ws, { id: 'excel-table', editable: false, header: '' })
|
||||
);
|
||||
}
|
||||
} else if (ext === 'pptx') {
|
||||
const { pptxToImages } = await import('$lib/utils/pptxToHtml');
|
||||
const result = await pptxToImages(arrayBuffer);
|
||||
fileOfficeSlides = result.images;
|
||||
currentSlide = 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to render Office file:', e);
|
||||
fileContent = `Error previewing file: ${e instanceof Error ? e.message : 'Unknown error'}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fileContent = await readFile(terminal.url, terminal.key, filePath);
|
||||
}
|
||||
@@ -536,7 +590,7 @@
|
||||
onUploadFiles={handleUploadFiles}
|
||||
onMove={handleMove}
|
||||
>
|
||||
{#if fileImageUrl !== null}
|
||||
{#if fileImageUrl !== null || (fileOfficeSlides !== null && fileOfficeSlides.length > 0)}
|
||||
<Tooltip content={$i18n.t('Reset view')}>
|
||||
<button
|
||||
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
|
||||
@@ -664,6 +718,7 @@
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<Tooltip content={$i18n.t('Download')}>
|
||||
<button
|
||||
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
|
||||
@@ -696,11 +751,26 @@
|
||||
bind:editing
|
||||
bind:showRaw
|
||||
bind:saving
|
||||
bind:currentSlide
|
||||
{selectedFile}
|
||||
{fileLoading}
|
||||
{fileImageUrl}
|
||||
{filePdfData}
|
||||
{fileContent}
|
||||
{fileOfficeHtml}
|
||||
{fileOfficeSlides}
|
||||
{excelSheetNames}
|
||||
{selectedExcelSheet}
|
||||
onSheetChange={async (sheet) => {
|
||||
if (!excelWorkbook) return;
|
||||
selectedExcelSheet = sheet;
|
||||
const XLSX = await import('xlsx');
|
||||
const ws = excelWorkbook.Sheets[sheet];
|
||||
const DOMPurify = (await import('dompurify')).default;
|
||||
fileOfficeHtml = DOMPurify.sanitize(
|
||||
XLSX.utils.sheet_to_html(ws, { id: 'excel-table', editable: false, header: '' })
|
||||
);
|
||||
}}
|
||||
{overlay}
|
||||
onSave={async (content) => {
|
||||
const terminal = selectedTerminal;
|
||||
@@ -797,6 +867,13 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Port detection -->
|
||||
{#if selectedTerminal && !selectedFile}
|
||||
<div class="shrink-0 border-t border-gray-100 dark:border-gray-800">
|
||||
<PortList baseUrl={selectedTerminal.url} apiKey={selectedTerminal.key} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Terminal bottom panel -->
|
||||
{#if terminalEnabled}
|
||||
<div class="shrink-0 border-t border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-850">
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
export let filePdfData: ArrayBuffer | null = null;
|
||||
export let fileContent: string | null = null;
|
||||
|
||||
// Office preview props
|
||||
export let fileOfficeHtml: string | null = null;
|
||||
export let fileOfficeSlides: string[] | null = null;
|
||||
export let currentSlide = 0;
|
||||
export let excelSheetNames: string[] = [];
|
||||
export let selectedExcelSheet = '';
|
||||
export let onSheetChange: ((sheet: string) => void) | null = null;
|
||||
|
||||
export let overlay = false;
|
||||
|
||||
export let onSave: ((content: string) => Promise<void>) | null = null;
|
||||
@@ -124,7 +132,8 @@
|
||||
pzInstance = panzoom(node, {
|
||||
bounds: true,
|
||||
boundsPadding: 0.1,
|
||||
zoomSpeed: 0.065
|
||||
zoomSpeed: 0.065,
|
||||
zoomDoubleClickSpeed: 1
|
||||
});
|
||||
};
|
||||
|
||||
@@ -152,7 +161,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex-1 {fileImageUrl !== null
|
||||
class="flex-1 {fileImageUrl !== null || (fileOfficeSlides !== null && fileOfficeSlides.length > 0)
|
||||
? 'overflow-hidden'
|
||||
: 'overflow-y-auto'} min-h-0 relative h-full"
|
||||
>
|
||||
@@ -170,6 +179,61 @@
|
||||
</div>
|
||||
{:else if filePdfData !== null}
|
||||
<PDFViewer bind:this={pdfViewerRef} data={filePdfData} className="w-full h-full" />
|
||||
{:else if fileOfficeHtml !== null}
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="office-preview overflow-auto flex-1 min-h-0 p-3">
|
||||
{@html fileOfficeHtml}
|
||||
</div>
|
||||
{#if excelSheetNames.length > 1}
|
||||
<div class="flex items-center gap-1 py-1.5 px-3 border-t border-gray-100 dark:border-gray-800 overflow-x-auto">
|
||||
{#each excelSheetNames as sheet}
|
||||
<button
|
||||
class="shrink-0 px-3 py-1 text-xs rounded-md transition-colors
|
||||
{selectedExcelSheet === sheet
|
||||
? 'bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-medium'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'}"
|
||||
on:click={() => onSheetChange?.(sheet)}
|
||||
>
|
||||
{sheet}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if fileOfficeSlides !== null && fileOfficeSlides.length > 0}
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="w-full flex-1 min-h-0 flex items-center justify-center overflow-hidden" use:initImagePanzoom>
|
||||
<img
|
||||
src={fileOfficeSlides[currentSlide]}
|
||||
alt="Slide {currentSlide + 1}"
|
||||
class="max-w-full max-h-full object-contain p-3"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
{#if fileOfficeSlides.length > 1}
|
||||
<div class="flex items-center justify-center gap-3 py-2 px-3 border-t border-gray-100 dark:border-gray-800 text-xs text-gray-500">
|
||||
<button
|
||||
class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-30"
|
||||
disabled={currentSlide === 0}
|
||||
on:click={() => { resetImageView(); currentSlide = Math.max(0, currentSlide - 1); }}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4">
|
||||
<path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<span>{currentSlide + 1} / {fileOfficeSlides.length}</span>
|
||||
<button
|
||||
class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-30"
|
||||
disabled={currentSlide === fileOfficeSlides.length - 1}
|
||||
on:click={() => { resetImageView(); currentSlide = Math.min(fileOfficeSlides.length - 1, currentSlide + 1); }}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4">
|
||||
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if fileContent !== null}
|
||||
{#if isHtml && !showRaw}
|
||||
{#if overlay}
|
||||
@@ -226,7 +290,7 @@
|
||||
class="text-xs font-mono text-gray-800 dark:text-gray-200 whitespace-pre-wrap break-all leading-relaxed p-3">{fileContent}</pre>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-sm text-gray-400 text-center pt-8">
|
||||
<div class="text-xs text-gray-400 text-center pt-8">
|
||||
{$i18n.t('Could not read file.')}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -284,4 +348,62 @@
|
||||
:global(.dark) .csv-row-num {
|
||||
color: #6b7280;
|
||||
}
|
||||
/* ── Office preview styles ──────────────────────────────────────── */
|
||||
:global(.office-preview) {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
color: #1f2937;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
:global(.dark .office-preview) {
|
||||
color: #e5e7eb;
|
||||
background: #1a1a2e;
|
||||
}
|
||||
:global(.office-preview table) {
|
||||
border-collapse: collapse;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
:global(.office-preview table td),
|
||||
:global(.office-preview table th) {
|
||||
border: 1px solid rgba(128, 128, 128, 0.25);
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:global(.dark .office-preview table td),
|
||||
:global(.dark .office-preview table th) {
|
||||
border-color: rgba(128, 128, 128, 0.35);
|
||||
}
|
||||
:global(.office-preview table th) {
|
||||
background: rgba(243, 244, 246, 0.95);
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
:global(.dark .office-preview table th) {
|
||||
background: rgba(31, 41, 55, 0.95);
|
||||
}
|
||||
:global(.office-preview table tr:nth-child(even)) {
|
||||
background: rgba(128, 128, 128, 0.04);
|
||||
}
|
||||
:global(.office-preview table tr:hover) {
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
}
|
||||
:global(.dark .office-preview table tr:hover) {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
:global(.office-preview img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
:global(.office-preview h1) { font-size: 1.5rem; font-weight: 700; margin: 0.75em 0 0.5em; }
|
||||
:global(.office-preview h2) { font-size: 1.25rem; font-weight: 600; margin: 0.75em 0 0.5em; }
|
||||
:global(.office-preview h3) { font-size: 1.1rem; font-weight: 600; margin: 0.5em 0 0.25em; }
|
||||
:global(.office-preview p) { margin: 0.25em 0; }
|
||||
:global(.office-preview ul),
|
||||
:global(.office-preview ol) { padding-left: 1.5em; margin: 0.5em 0; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
let isAudio = false;
|
||||
let isImage = false;
|
||||
let isExcel = false;
|
||||
let isDocx = false;
|
||||
let isPptx = false;
|
||||
|
||||
let selectedTab = '';
|
||||
let excelWorkbook: WorkBook | null = null;
|
||||
@@ -49,6 +51,15 @@
|
||||
let excelError = '';
|
||||
let rowCount = 0;
|
||||
|
||||
// DOCX state
|
||||
let docxHtml = '';
|
||||
let docxError = '';
|
||||
|
||||
// PPTX state
|
||||
let pptxSlides: string[] = [];
|
||||
let pptxCurrentSlide = 0;
|
||||
let pptxError = '';
|
||||
|
||||
let pzInstance: PanZoom | null = null;
|
||||
|
||||
const initImagePanzoom = (node: HTMLElement) => {
|
||||
@@ -128,6 +139,16 @@
|
||||
item.name.toLowerCase().endsWith('.xlsx') ||
|
||||
item.name.toLowerCase().endsWith('.csv')));
|
||||
|
||||
$: isDocx =
|
||||
item?.meta?.content_type ===
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||
(item?.name && item.name.toLowerCase().endsWith('.docx'));
|
||||
|
||||
$: isPptx =
|
||||
item?.meta?.content_type ===
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' ||
|
||||
(item?.name && item.name.toLowerCase().endsWith('.pptx'));
|
||||
|
||||
const loadExcelContent = async () => {
|
||||
try {
|
||||
excelError = '';
|
||||
@@ -170,6 +191,37 @@
|
||||
renderExcelSheet();
|
||||
}
|
||||
|
||||
const loadDocxContent = async () => {
|
||||
try {
|
||||
docxError = '';
|
||||
const [arrayBuffer, mammoth] = await Promise.all([
|
||||
getFileContentById(item.id),
|
||||
import('mammoth')
|
||||
]);
|
||||
const result = await mammoth.convertToHtml({ arrayBuffer });
|
||||
docxHtml = DOMPurify.sanitize(result.value);
|
||||
} catch (error) {
|
||||
console.error('Error loading DOCX file:', error);
|
||||
docxError = $i18n.t('Failed to load DOCX file. Please try downloading it instead.');
|
||||
}
|
||||
};
|
||||
|
||||
const loadPptxContent = async () => {
|
||||
try {
|
||||
pptxError = '';
|
||||
const [arrayBuffer, { pptxToImages }] = await Promise.all([
|
||||
getFileContentById(item.id),
|
||||
import('$lib/utils/pptxToHtml')
|
||||
]);
|
||||
const result = await pptxToImages(arrayBuffer);
|
||||
pptxSlides = result.images;
|
||||
pptxCurrentSlide = 0;
|
||||
} catch (error) {
|
||||
console.error('Error loading PPTX file:', error);
|
||||
pptxError = $i18n.t('Failed to load PPTX file. Please try downloading it instead.');
|
||||
}
|
||||
};
|
||||
|
||||
const loadContent = async () => {
|
||||
selectedTab = '';
|
||||
expandedContent = false;
|
||||
@@ -201,6 +253,12 @@
|
||||
if (isExcel) {
|
||||
await loadExcelContent();
|
||||
}
|
||||
if (isDocx) {
|
||||
await loadDocxContent();
|
||||
}
|
||||
if (isPptx) {
|
||||
await loadPptxContent();
|
||||
}
|
||||
|
||||
loading = false;
|
||||
}
|
||||
@@ -358,7 +416,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isAudio || isPDF || isExcel || isCode || isMarkdown}
|
||||
{#if isAudio || isPDF || isExcel || isCode || isMarkdown || isDocx || isPptx}
|
||||
<div
|
||||
class="flex mb-2.5 scrollbar-none overflow-x-auto w-full border-b border-gray-50 dark:border-gray-850/30 text-center text-sm font-medium bg-transparent dark:text-gray-200"
|
||||
>
|
||||
@@ -534,6 +592,56 @@
|
||||
>
|
||||
<Markdown content={item.file.data.content} id="markdown-viewer" />
|
||||
</div>
|
||||
{:else if isDocx}
|
||||
{#if docxError}
|
||||
<div class="text-red-500 text-sm p-4">{docxError}</div>
|
||||
{:else if docxHtml}
|
||||
<div class="office-preview max-h-[60vh] overflow-auto p-4 prose dark:prose-invert max-w-full text-sm">
|
||||
{@html docxHtml}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-sm p-4">No content available</div>
|
||||
{/if}
|
||||
{:else if isPptx}
|
||||
{#if pptxError}
|
||||
<div class="text-red-500 text-sm p-4">{pptxError}</div>
|
||||
{:else if pptxSlides.length > 0}
|
||||
<div class="max-h-[60vh] overflow-auto">
|
||||
<div class="flex justify-center p-4">
|
||||
<img
|
||||
src={pptxSlides[pptxCurrentSlide]}
|
||||
alt="Slide {pptxCurrentSlide + 1}"
|
||||
class="max-w-full max-h-[50vh] object-contain rounded-md shadow-lg"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
{#if pptxSlides.length > 1}
|
||||
<div class="flex items-center justify-center gap-3 pb-3 text-sm text-gray-500">
|
||||
<button
|
||||
class="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-30"
|
||||
disabled={pptxCurrentSlide === 0}
|
||||
on:click={() => pptxCurrentSlide = Math.max(0, pptxCurrentSlide - 1)}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
|
||||
<path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<span>{pptxCurrentSlide + 1} / {pptxSlides.length}</span>
|
||||
<button
|
||||
class="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-30"
|
||||
disabled={pptxCurrentSlide === pptxSlides.length - 1}
|
||||
on:click={() => pptxCurrentSlide = Math.min(pptxSlides.length - 1, pptxCurrentSlide + 1)}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
|
||||
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-sm p-4">No content available</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
|
||||
{(item?.file?.data?.content ?? '').trim() || 'No content'}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Lightweight PPTX → Image renderer.
|
||||
*
|
||||
* Extracts text and images from each slide and renders them
|
||||
* directly to canvas, returning PNG data URLs.
|
||||
*
|
||||
* Uses jszip (dynamically imported) and the browser Canvas 2D API.
|
||||
* No theme resolution, charts, SmartArt, or animations — preview only.
|
||||
*/
|
||||
|
||||
const EMU_PER_PX = 9525;
|
||||
const emuToPx = (emu: number) => Math.round(emu / EMU_PER_PX);
|
||||
|
||||
const parseEmu = (val: string | null | undefined): number =>
|
||||
val ? parseInt(val, 10) || 0 : 0;
|
||||
|
||||
/** Load a data URI into an Image element and wait for it. */
|
||||
const loadImage = (src: string): Promise<HTMLImageElement> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = () => reject(new Error('Failed to load image'));
|
||||
img.src = src;
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert PPTX ArrayBuffer → array of PNG data URL strings, one per slide.
|
||||
*/
|
||||
export async function pptxToImages(
|
||||
buffer: ArrayBuffer
|
||||
): Promise<{ images: string[]; width: number; height: number }> {
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = await JSZip.loadAsync(buffer);
|
||||
|
||||
// ── Read slide dimensions from presentation.xml ──────────────────
|
||||
let slideW = 960;
|
||||
let slideH = 540;
|
||||
const presXml = zip.file('ppt/presentation.xml');
|
||||
if (presXml) {
|
||||
const presText = await presXml.async('text');
|
||||
const presDoc = new DOMParser().parseFromString(presText, 'application/xml');
|
||||
const sldSz = presDoc.getElementsByTagName('p:sldSz')[0];
|
||||
if (sldSz) {
|
||||
slideW = emuToPx(parseEmu(sldSz.getAttribute('cx')));
|
||||
slideH = emuToPx(parseEmu(sldSz.getAttribute('cy')));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Collect media files (images) as base64 data URIs ─────────────
|
||||
const media: Record<string, string> = {};
|
||||
const mediaFiles = Object.keys(zip.files).filter((f) => f.startsWith('ppt/media/'));
|
||||
await Promise.all(
|
||||
mediaFiles.map(async (path) => {
|
||||
const file = zip.file(path);
|
||||
if (!file) return;
|
||||
const base64 = await file.async('base64');
|
||||
const ext = path.split('.').pop()?.toLowerCase() ?? '';
|
||||
const mime =
|
||||
ext === 'png'
|
||||
? 'image/png'
|
||||
: ext === 'gif'
|
||||
? 'image/gif'
|
||||
: ext === 'svg'
|
||||
? 'image/svg+xml'
|
||||
: ext === 'emf' || ext === 'wmf'
|
||||
? 'image/x-emf'
|
||||
: 'image/jpeg';
|
||||
media[path] = `data:${mime};base64,${base64}`;
|
||||
})
|
||||
);
|
||||
|
||||
// ── Discover slide files ─────────────────────────────────────────
|
||||
const slideFiles = Object.keys(zip.files)
|
||||
.filter((f) => /^ppt\/slides\/slide\d+\.xml$/.test(f))
|
||||
.sort((a, b) => {
|
||||
const na = parseInt(a.match(/slide(\d+)/)?.[1] ?? '0');
|
||||
const nb = parseInt(b.match(/slide(\d+)/)?.[1] ?? '0');
|
||||
return na - nb;
|
||||
});
|
||||
|
||||
const images: string[] = [];
|
||||
|
||||
for (const slidePath of slideFiles) {
|
||||
const slideText = await zip.file(slidePath)!.async('text');
|
||||
const slideDoc = new DOMParser().parseFromString(slideText, 'application/xml');
|
||||
|
||||
// Load relationship file for this slide to resolve image references
|
||||
const slideNum = slidePath.match(/slide(\d+)/)?.[1];
|
||||
const relsPath = `ppt/slides/_rels/slide${slideNum}.xml.rels`;
|
||||
const rels: Record<string, string> = {};
|
||||
const relsFile = zip.file(relsPath);
|
||||
if (relsFile) {
|
||||
const relsText = await relsFile.async('text');
|
||||
const relsDoc = new DOMParser().parseFromString(relsText, 'application/xml');
|
||||
const relEls = relsDoc.getElementsByTagName('Relationship');
|
||||
for (let i = 0; i < relEls.length; i++) {
|
||||
const rel = relEls[i];
|
||||
const id = rel.getAttribute('Id') ?? '';
|
||||
const target = rel.getAttribute('Target') ?? '';
|
||||
if (target.startsWith('../')) {
|
||||
rels[id] = 'ppt/' + target.replace('../', '');
|
||||
} else {
|
||||
rels[id] = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Create canvas and render slide ───────────────────────────
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = slideW;
|
||||
canvas.height = slideH;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
// White background
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, slideW, slideH);
|
||||
|
||||
const spTree = slideDoc.getElementsByTagName('p:spTree')[0];
|
||||
if (!spTree) {
|
||||
images.push(canvas.toDataURL('image/png'));
|
||||
continue;
|
||||
}
|
||||
|
||||
const shapes = [
|
||||
...Array.from(spTree.getElementsByTagName('p:sp')),
|
||||
...Array.from(spTree.getElementsByTagName('p:pic'))
|
||||
];
|
||||
|
||||
for (const shape of shapes) {
|
||||
const xfrm =
|
||||
shape.getElementsByTagName('a:xfrm')[0] ??
|
||||
shape.getElementsByTagName('p:xfrm')[0];
|
||||
if (!xfrm) continue;
|
||||
|
||||
const off = xfrm.getElementsByTagName('a:off')[0];
|
||||
const ext = xfrm.getElementsByTagName('a:ext')[0];
|
||||
if (!off || !ext) continue;
|
||||
|
||||
const x = emuToPx(parseEmu(off.getAttribute('x')));
|
||||
const y = emuToPx(parseEmu(off.getAttribute('y')));
|
||||
const w = emuToPx(parseEmu(ext.getAttribute('cx')));
|
||||
const h = emuToPx(parseEmu(ext.getAttribute('cy')));
|
||||
|
||||
if (w === 0 && h === 0) continue;
|
||||
|
||||
// ── Picture ──────────────────────────────────────────────
|
||||
const blipFill = shape.getElementsByTagName('p:blipFill')[0];
|
||||
if (blipFill) {
|
||||
const blip = blipFill.getElementsByTagName('a:blip')[0];
|
||||
if (blip) {
|
||||
const rEmbed = blip.getAttribute('r:embed') ?? '';
|
||||
const mediaPath = rels[rEmbed];
|
||||
const dataUri = mediaPath ? media[mediaPath] : '';
|
||||
if (dataUri && !dataUri.includes('image/x-emf')) {
|
||||
try {
|
||||
const img = await loadImage(dataUri);
|
||||
ctx.drawImage(img, x, y, w, h);
|
||||
} catch {
|
||||
// Skip images that fail to load
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── Text shape ───────────────────────────────────────────
|
||||
const txBody = shape.getElementsByTagName('p:txBody')[0];
|
||||
if (!txBody) continue;
|
||||
|
||||
ctx.save();
|
||||
ctx.rect(x, y, w, h);
|
||||
ctx.clip();
|
||||
|
||||
const paragraphs = txBody.getElementsByTagName('a:p');
|
||||
let cursorY = y;
|
||||
const defaultFontSize = 12;
|
||||
|
||||
for (let pi = 0; pi < paragraphs.length; pi++) {
|
||||
const para = paragraphs[pi];
|
||||
const runs = para.getElementsByTagName('a:r');
|
||||
|
||||
if (runs.length === 0) {
|
||||
cursorY += defaultFontSize * 1.5;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate max font size in this paragraph for line height
|
||||
let maxFontPt = defaultFontSize;
|
||||
for (let ri = 0; ri < runs.length; ri++) {
|
||||
const rPr = runs[ri].getElementsByTagName('a:rPr')[0];
|
||||
if (rPr) {
|
||||
const sz = rPr.getAttribute('sz');
|
||||
if (sz) {
|
||||
const pt = parseInt(sz, 10) / 100;
|
||||
if (pt > maxFontPt) maxFontPt = pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lineHeight = maxFontPt * 1.4;
|
||||
cursorY += maxFontPt; // baseline offset
|
||||
|
||||
let cursorX = x + 4; // small left padding
|
||||
|
||||
for (let ri = 0; ri < runs.length; ri++) {
|
||||
const run = runs[ri];
|
||||
const rPr = run.getElementsByTagName('a:rPr')[0];
|
||||
const text = run.getElementsByTagName('a:t')[0]?.textContent ?? '';
|
||||
if (!text) continue;
|
||||
|
||||
let fontPt = defaultFontSize;
|
||||
let bold = false;
|
||||
let italic = false;
|
||||
let color = '#000000';
|
||||
|
||||
if (rPr) {
|
||||
if (rPr.getAttribute('b') === '1') bold = true;
|
||||
if (rPr.getAttribute('i') === '1') italic = true;
|
||||
const sz = rPr.getAttribute('sz');
|
||||
if (sz) fontPt = parseInt(sz, 10) / 100;
|
||||
const solidFill = rPr.getElementsByTagName('a:solidFill')[0];
|
||||
if (solidFill) {
|
||||
const srgb = solidFill.getElementsByTagName('a:srgbClr')[0];
|
||||
if (srgb) {
|
||||
const val = srgb.getAttribute('val');
|
||||
if (val) color = `#${val}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.font = `${italic ? 'italic ' : ''}${bold ? 'bold ' : ''}${fontPt}pt Calibri, Arial, sans-serif`;
|
||||
ctx.fillStyle = color;
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
|
||||
// Simple word-wrap within the shape bounds
|
||||
const words = text.split(/(\s+)/);
|
||||
for (const word of words) {
|
||||
const metrics = ctx.measureText(word);
|
||||
if (cursorX + metrics.width > x + w && cursorX > x + 4) {
|
||||
cursorX = x + 4;
|
||||
cursorY += lineHeight;
|
||||
}
|
||||
if (cursorY > y + h) break;
|
||||
ctx.fillText(word, cursorX, cursorY);
|
||||
cursorX += metrics.width;
|
||||
}
|
||||
}
|
||||
|
||||
cursorY += lineHeight * 0.4; // paragraph spacing
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
images.push(canvas.toDataURL('image/png'));
|
||||
}
|
||||
|
||||
return { images, width: slideW, height: slideH };
|
||||
}
|
||||
Reference in New Issue
Block a user