@lamberl-lee/file-preview 0.2.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.
Files changed (263) hide show
  1. package/COPYING +674 -0
  2. package/LICENSE +165 -0
  3. package/README.md +165 -0
  4. package/dist/AudioPreview.d.ts +9 -0
  5. package/dist/AudioPreview.js +29 -0
  6. package/dist/AudioPreview.js.map +1 -0
  7. package/dist/CodePreview.d.ts +10 -0
  8. package/dist/CodePreview.js +121 -0
  9. package/dist/CodePreview.js.map +1 -0
  10. package/dist/CsvPreview.d.ts +9 -0
  11. package/dist/CsvPreview.js +117 -0
  12. package/dist/CsvPreview.js.map +1 -0
  13. package/dist/DocxPreview.d.ts +11 -0
  14. package/dist/DocxPreview.js +89 -0
  15. package/dist/DocxPreview.js.map +1 -0
  16. package/dist/EpubPreview.d.ts +9 -0
  17. package/dist/EpubPreview.js +693 -0
  18. package/dist/EpubPreview.js.map +1 -0
  19. package/dist/HtmlPreview.d.ts +9 -0
  20. package/dist/HtmlPreview.js +60 -0
  21. package/dist/HtmlPreview.js.map +1 -0
  22. package/dist/ImagePreview.d.ts +9 -0
  23. package/dist/ImagePreview.js +44 -0
  24. package/dist/ImagePreview.js.map +1 -0
  25. package/dist/LargeFileGate.d.ts +12 -0
  26. package/dist/LargeFileGate.js +88 -0
  27. package/dist/LargeFileGate.js.map +1 -0
  28. package/dist/MarkdownPreview.d.ts +8 -0
  29. package/dist/MarkdownPreview.js +140 -0
  30. package/dist/MarkdownPreview.js.map +1 -0
  31. package/dist/PdfPreview.d.ts +11 -0
  32. package/dist/PdfPreview.js +206 -0
  33. package/dist/PdfPreview.js.map +1 -0
  34. package/dist/PlainTextLargePreview.d.ts +9 -0
  35. package/dist/PlainTextLargePreview.js +62 -0
  36. package/dist/PlainTextLargePreview.js.map +1 -0
  37. package/dist/PluginPreviewRenderer.d.ts +13 -0
  38. package/dist/PluginPreviewRenderer.js +89 -0
  39. package/dist/PluginPreviewRenderer.js.map +1 -0
  40. package/dist/PptxPreview.d.ts +16 -0
  41. package/dist/PptxPreview.js +376 -0
  42. package/dist/PptxPreview.js.map +1 -0
  43. package/dist/PreviewErrorBoundary.d.ts +29 -0
  44. package/dist/PreviewErrorBoundary.js +53 -0
  45. package/dist/PreviewErrorBoundary.js.map +1 -0
  46. package/dist/PreviewFallback.d.ts +18 -0
  47. package/dist/PreviewFallback.js +143 -0
  48. package/dist/PreviewFallback.js.map +1 -0
  49. package/dist/PreviewLoading.d.ts +8 -0
  50. package/dist/PreviewLoading.js +14 -0
  51. package/dist/PreviewLoading.js.map +1 -0
  52. package/dist/RtfPreview.d.ts +10 -0
  53. package/dist/RtfPreview.js +240 -0
  54. package/dist/RtfPreview.js.map +1 -0
  55. package/dist/ShikiSourceView.d.ts +11 -0
  56. package/dist/ShikiSourceView.js +112 -0
  57. package/dist/ShikiSourceView.js.map +1 -0
  58. package/dist/SvgPreview.d.ts +9 -0
  59. package/dist/SvgPreview.js +89 -0
  60. package/dist/SvgPreview.js.map +1 -0
  61. package/dist/TextPreview.d.ts +9 -0
  62. package/dist/TextPreview.js +9 -0
  63. package/dist/TextPreview.js.map +1 -0
  64. package/dist/VideoPreview.d.ts +9 -0
  65. package/dist/VideoPreview.js +18 -0
  66. package/dist/VideoPreview.js.map +1 -0
  67. package/dist/XlsxPreview.d.ts +12 -0
  68. package/dist/XlsxPreview.js +856 -0
  69. package/dist/XlsxPreview.js.map +1 -0
  70. package/dist/ZipPreview.d.ts +9 -0
  71. package/dist/ZipPreview.js +153 -0
  72. package/dist/ZipPreview.js.map +1 -0
  73. package/dist/core/binary.d.ts +10 -0
  74. package/dist/core/binary.js +27 -0
  75. package/dist/core/binary.js.map +1 -0
  76. package/dist/core/config.d.ts +17 -0
  77. package/dist/core/config.js +19 -0
  78. package/dist/core/config.js.map +1 -0
  79. package/dist/core/download.d.ts +5 -0
  80. package/dist/core/download.js +20 -0
  81. package/dist/core/download.js.map +1 -0
  82. package/dist/core/i18n.d.ts +101 -0
  83. package/dist/core/i18n.js +200 -0
  84. package/dist/core/i18n.js.map +1 -0
  85. package/dist/core/plugin.d.ts +38 -0
  86. package/dist/core/plugin.js +46 -0
  87. package/dist/core/plugin.js.map +1 -0
  88. package/dist/core/registry.d.ts +13 -0
  89. package/dist/core/registry.js +33 -0
  90. package/dist/core/registry.js.map +1 -0
  91. package/dist/core/source.d.ts +14 -0
  92. package/dist/core/source.js +131 -0
  93. package/dist/core/source.js.map +1 -0
  94. package/dist/core/types.d.ts +88 -0
  95. package/dist/core/types.js +27 -0
  96. package/dist/core/types.js.map +1 -0
  97. package/dist/hooks/useObjectUrlFromSource.d.ts +9 -0
  98. package/dist/hooks/useObjectUrlFromSource.js +50 -0
  99. package/dist/hooks/useObjectUrlFromSource.js.map +1 -0
  100. package/dist/hooks/useSourceBase64.d.ts +10 -0
  101. package/dist/hooks/useSourceBase64.js +32 -0
  102. package/dist/hooks/useSourceBase64.js.map +1 -0
  103. package/dist/hooks/useSourceText.d.ts +10 -0
  104. package/dist/hooks/useSourceText.js +32 -0
  105. package/dist/hooks/useSourceText.js.map +1 -0
  106. package/dist/icons.d.ts +44 -0
  107. package/dist/icons.js +248 -0
  108. package/dist/icons.js.map +1 -0
  109. package/dist/index.d.ts +36 -0
  110. package/dist/index.js +147 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/limits.d.ts +26 -0
  113. package/dist/limits.js +45 -0
  114. package/dist/limits.js.map +1 -0
  115. package/dist/performance-limits.d.ts +27 -0
  116. package/dist/performance-limits.js +54 -0
  117. package/dist/performance-limits.js.map +1 -0
  118. package/dist/plugins/audio-plugin.d.ts +7 -0
  119. package/dist/plugins/audio-plugin.js +11 -0
  120. package/dist/plugins/audio-plugin.js.map +1 -0
  121. package/dist/plugins/builtin-plugins.d.ts +9 -0
  122. package/dist/plugins/builtin-plugins.js +43 -0
  123. package/dist/plugins/builtin-plugins.js.map +1 -0
  124. package/dist/plugins/csv-plugin.d.ts +7 -0
  125. package/dist/plugins/csv-plugin.js +11 -0
  126. package/dist/plugins/csv-plugin.js.map +1 -0
  127. package/dist/plugins/docx-plugin.d.ts +7 -0
  128. package/dist/plugins/docx-plugin.js +15 -0
  129. package/dist/plugins/docx-plugin.js.map +1 -0
  130. package/dist/plugins/epub-plugin.d.ts +7 -0
  131. package/dist/plugins/epub-plugin.js +15 -0
  132. package/dist/plugins/epub-plugin.js.map +1 -0
  133. package/dist/plugins/html-plugin.d.ts +7 -0
  134. package/dist/plugins/html-plugin.js +11 -0
  135. package/dist/plugins/html-plugin.js.map +1 -0
  136. package/dist/plugins/image-plugin.d.ts +7 -0
  137. package/dist/plugins/image-plugin.js +11 -0
  138. package/dist/plugins/image-plugin.js.map +1 -0
  139. package/dist/plugins/markdown-plugin.d.ts +7 -0
  140. package/dist/plugins/markdown-plugin.js +11 -0
  141. package/dist/plugins/markdown-plugin.js.map +1 -0
  142. package/dist/plugins/pdf-plugin.d.ts +7 -0
  143. package/dist/plugins/pdf-plugin.js +15 -0
  144. package/dist/plugins/pdf-plugin.js.map +1 -0
  145. package/dist/plugins/pptx-plugin.d.ts +7 -0
  146. package/dist/plugins/pptx-plugin.js +15 -0
  147. package/dist/plugins/pptx-plugin.js.map +1 -0
  148. package/dist/plugins/rtf-plugin.d.ts +7 -0
  149. package/dist/plugins/rtf-plugin.js +15 -0
  150. package/dist/plugins/rtf-plugin.js.map +1 -0
  151. package/dist/plugins/source-code-plugin.d.ts +7 -0
  152. package/dist/plugins/source-code-plugin.js +11 -0
  153. package/dist/plugins/source-code-plugin.js.map +1 -0
  154. package/dist/plugins/svg-plugin.d.ts +7 -0
  155. package/dist/plugins/svg-plugin.js +11 -0
  156. package/dist/plugins/svg-plugin.js.map +1 -0
  157. package/dist/plugins/text-plugin.d.ts +7 -0
  158. package/dist/plugins/text-plugin.js +11 -0
  159. package/dist/plugins/text-plugin.js.map +1 -0
  160. package/dist/plugins/video-plugin.d.ts +7 -0
  161. package/dist/plugins/video-plugin.js +11 -0
  162. package/dist/plugins/video-plugin.js.map +1 -0
  163. package/dist/plugins/xlsx-plugin.d.ts +7 -0
  164. package/dist/plugins/xlsx-plugin.js +15 -0
  165. package/dist/plugins/xlsx-plugin.js.map +1 -0
  166. package/dist/plugins/zip-plugin.d.ts +7 -0
  167. package/dist/plugins/zip-plugin.js +15 -0
  168. package/dist/plugins/zip-plugin.js.map +1 -0
  169. package/dist/preview-adapters/AudioPreviewAdapter.d.ts +8 -0
  170. package/dist/preview-adapters/AudioPreviewAdapter.js +31 -0
  171. package/dist/preview-adapters/AudioPreviewAdapter.js.map +1 -0
  172. package/dist/preview-adapters/CsvPreviewAdapter.d.ts +8 -0
  173. package/dist/preview-adapters/CsvPreviewAdapter.js +28 -0
  174. package/dist/preview-adapters/CsvPreviewAdapter.js.map +1 -0
  175. package/dist/preview-adapters/DocxPreviewAdapter.d.ts +8 -0
  176. package/dist/preview-adapters/DocxPreviewAdapter.js +16 -0
  177. package/dist/preview-adapters/DocxPreviewAdapter.js.map +1 -0
  178. package/dist/preview-adapters/EpubPreviewAdapter.d.ts +8 -0
  179. package/dist/preview-adapters/EpubPreviewAdapter.js +28 -0
  180. package/dist/preview-adapters/EpubPreviewAdapter.js.map +1 -0
  181. package/dist/preview-adapters/HtmlPreviewAdapter.d.ts +8 -0
  182. package/dist/preview-adapters/HtmlPreviewAdapter.js +28 -0
  183. package/dist/preview-adapters/HtmlPreviewAdapter.js.map +1 -0
  184. package/dist/preview-adapters/ImagePreviewAdapter.d.ts +8 -0
  185. package/dist/preview-adapters/ImagePreviewAdapter.js +31 -0
  186. package/dist/preview-adapters/ImagePreviewAdapter.js.map +1 -0
  187. package/dist/preview-adapters/MarkdownPreviewAdapter.d.ts +8 -0
  188. package/dist/preview-adapters/MarkdownPreviewAdapter.js +28 -0
  189. package/dist/preview-adapters/MarkdownPreviewAdapter.js.map +1 -0
  190. package/dist/preview-adapters/PdfPreviewAdapter.d.ts +8 -0
  191. package/dist/preview-adapters/PdfPreviewAdapter.js +16 -0
  192. package/dist/preview-adapters/PdfPreviewAdapter.js.map +1 -0
  193. package/dist/preview-adapters/PptxPreviewAdapter.d.ts +8 -0
  194. package/dist/preview-adapters/PptxPreviewAdapter.js +16 -0
  195. package/dist/preview-adapters/PptxPreviewAdapter.js.map +1 -0
  196. package/dist/preview-adapters/RtfPreviewAdapter.d.ts +8 -0
  197. package/dist/preview-adapters/RtfPreviewAdapter.js +54 -0
  198. package/dist/preview-adapters/RtfPreviewAdapter.js.map +1 -0
  199. package/dist/preview-adapters/SourceCodePreviewAdapter.d.ts +8 -0
  200. package/dist/preview-adapters/SourceCodePreviewAdapter.js +35 -0
  201. package/dist/preview-adapters/SourceCodePreviewAdapter.js.map +1 -0
  202. package/dist/preview-adapters/SvgPreviewAdapter.d.ts +8 -0
  203. package/dist/preview-adapters/SvgPreviewAdapter.js +28 -0
  204. package/dist/preview-adapters/SvgPreviewAdapter.js.map +1 -0
  205. package/dist/preview-adapters/TextPreviewAdapter.d.ts +8 -0
  206. package/dist/preview-adapters/TextPreviewAdapter.js +28 -0
  207. package/dist/preview-adapters/TextPreviewAdapter.js.map +1 -0
  208. package/dist/preview-adapters/UnsupportedPluginPreview.d.ts +11 -0
  209. package/dist/preview-adapters/UnsupportedPluginPreview.js +34 -0
  210. package/dist/preview-adapters/UnsupportedPluginPreview.js.map +1 -0
  211. package/dist/preview-adapters/VideoPreviewAdapter.d.ts +8 -0
  212. package/dist/preview-adapters/VideoPreviewAdapter.js +31 -0
  213. package/dist/preview-adapters/VideoPreviewAdapter.js.map +1 -0
  214. package/dist/preview-adapters/XlsxPreviewAdapter.d.ts +8 -0
  215. package/dist/preview-adapters/XlsxPreviewAdapter.js +17 -0
  216. package/dist/preview-adapters/XlsxPreviewAdapter.js.map +1 -0
  217. package/dist/preview-adapters/ZipPreviewAdapter.d.ts +8 -0
  218. package/dist/preview-adapters/ZipPreviewAdapter.js +28 -0
  219. package/dist/preview-adapters/ZipPreviewAdapter.js.map +1 -0
  220. package/dist/remote-url.d.ts +20 -0
  221. package/dist/remote-url.js +478 -0
  222. package/dist/remote-url.js.map +1 -0
  223. package/dist/rtf/load-rtfjs.d.ts +42 -0
  224. package/dist/rtf/load-rtfjs.js +71 -0
  225. package/dist/rtf/load-rtfjs.js.map +1 -0
  226. package/dist/rtf/normalize-codepage.d.ts +33 -0
  227. package/dist/rtf/normalize-codepage.js +88 -0
  228. package/dist/rtf/normalize-codepage.js.map +1 -0
  229. package/dist/shiki.d.ts +35 -0
  230. package/dist/shiki.js +128 -0
  231. package/dist/shiki.js.map +1 -0
  232. package/dist/styles/AudioPreview.css +35 -0
  233. package/dist/styles/CsvPreview.css +106 -0
  234. package/dist/styles/DocxPreview.css +93 -0
  235. package/dist/styles/EpubPreview.css +509 -0
  236. package/dist/styles/HtmlPreview.css +15 -0
  237. package/dist/styles/ImagePreview.css +45 -0
  238. package/dist/styles/LargeFileGate.css +55 -0
  239. package/dist/styles/MarkdownPreview.css +291 -0
  240. package/dist/styles/PdfPreview.css +68 -0
  241. package/dist/styles/PlainTextLargePreview.css +85 -0
  242. package/dist/styles/PluginDebugBar.css +30 -0
  243. package/dist/styles/PptxPreview.css +207 -0
  244. package/dist/styles/PreviewFallback.css +88 -0
  245. package/dist/styles/PreviewLoading.css +13 -0
  246. package/dist/styles/RtfPreview.css +99 -0
  247. package/dist/styles/ShikiSourceView.css +159 -0
  248. package/dist/styles/SvgPreview.css +99 -0
  249. package/dist/styles/VideoPreview.css +19 -0
  250. package/dist/styles/ViewModeBar.css +38 -0
  251. package/dist/styles/XlsxPreview.css +361 -0
  252. package/dist/styles/ZipPreview.css +86 -0
  253. package/dist/styles/base.css +238 -0
  254. package/dist/styles/index.css +39 -0
  255. package/dist/support-status.d.ts +19 -0
  256. package/dist/support-status.js +174 -0
  257. package/dist/support-status.js.map +1 -0
  258. package/dist/utils.d.ts +17 -0
  259. package/dist/utils.js +205 -0
  260. package/dist/utils.js.map +1 -0
  261. package/package.json +125 -0
  262. package/scripts/copy-pdf-worker.mjs +31 -0
  263. package/scripts/copy-rtfjs-bundles.mjs +49 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PreviewFallback.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { AlertTriangleIcon, DownloadIcon, CopyIcon } from \"./icons\";\nimport type { FileInfo } from \"./utils\";\nimport { formatFileSize } from \"./utils\";\nimport { downloadSource } from \"./core/download\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/PreviewFallback.css\";\n\nexport type PreviewFallbackKind =\n | \"unsupported\"\n | \"plugin-load-failed\"\n | \"render-failed\"\n | \"source-read-failed\"\n | \"file-too-large\"\n | \"aborted\"\n | \"unknown\";\n\nexport interface PreviewFallbackProps {\n kind: PreviewFallbackKind;\n file: FileInfo;\n title?: string;\n description?: string;\n error?: unknown;\n pluginId?: string;\n pluginName?: string;\n onRetry?: () => void;\n canDownload?: boolean;\n}\n\nfunction getFallbackTitle(kind: PreviewFallbackKind, t: ReturnType<typeof useLocale>): string {\n switch (kind) {\n case \"unsupported\":\n return t.previewNotAvailable;\n case \"plugin-load-failed\":\n return t.failedToLoadPreview;\n case \"render-failed\":\n return t.previewFailed;\n case \"source-read-failed\":\n return t.failedToReadFile;\n case \"file-too-large\":\n return t.largeFile;\n case \"aborted\":\n return t.loadingCancelled;\n default:\n return t.previewFailed;\n }\n}\n\nfunction getFallbackDescription(\n kind: PreviewFallbackKind,\n t: ReturnType<typeof useLocale>,\n): string | undefined {\n switch (kind) {\n case \"unsupported\":\n return t.unsupportedFileType.replace(\"{fileType}\", \"\");\n case \"plugin-load-failed\":\n return \"The preview plugin could not be loaded. This may be a network issue or the plugin is not installed.\";\n case \"render-failed\":\n return \"The preview crashed while rendering. You can retry or download the original file.\";\n case \"source-read-failed\":\n return \"Could not read file content. The file may be corrupted or inaccessible.\";\n case \"file-too-large\":\n return t.largeFileHint;\n case \"aborted\":\n return \"File loading was cancelled.\";\n default:\n return undefined;\n }\n}\n\nfunction PreviewErrorDetails({\n error,\n pluginId,\n pluginName,\n t,\n}: {\n error?: unknown;\n pluginId?: string;\n pluginName?: string;\n t: ReturnType<typeof useLocale>;\n}) {\n const [expanded, setExpanded] = useState(false);\n\n if (!error) return null;\n\n const message =\n error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error);\n\n return (\n <div className=\"fv-fallback__details\">\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"fv-fallback__details-toggle\"\n >\n {expanded ? \"Hide\" : \"Show\"} {t.showErrorDetails.toLowerCase()}\n </button>\n\n {expanded && (\n <div className=\"fv-fallback__details-pre\">\n {pluginName && `Plugin: ${pluginName}\\n`}\n {pluginId && `ID: ${pluginId}\\n`}\n {message}\n <button\n onClick={() => {\n const text = [\n pluginName && `Plugin: ${pluginName}`,\n pluginId && `ID: ${pluginId}`,\n message,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n navigator.clipboard.writeText(text);\n }}\n className=\"fv-fallback__details-copy\"\n title={t.copyCode}\n >\n <CopyIcon size={12} />\n </button>\n </div>\n )}\n </div>\n );\n}\n\nexport function PreviewFallback({\n kind,\n file,\n title,\n description,\n error,\n pluginId,\n pluginName,\n onRetry,\n canDownload = true,\n}: PreviewFallbackProps) {\n const t = useLocale();\n\n return (\n <div className=\"fv-fallback\">\n <div className=\"fv-fallback__inner\">\n <div className=\"fv-fallback__icon-wrap\">\n <AlertTriangleIcon size={24} />\n </div>\n\n <div>\n <h3 className=\"fv-fallback__title\">\n {title ?? getFallbackTitle(kind, t)}\n </h3>\n <p className=\"fv-fallback__desc\">\n {description ?? getFallbackDescription(kind, t)}\n </p>\n <p className=\"fv-fallback__meta\">\n {file.name} · {formatFileSize(file.size)}\n </p>\n </div>\n\n <div className=\"fv-fallback__actions\">\n {onRetry && (\n <button className=\"fv-btn fv-btn--primary fv-btn--sm\" onClick={onRetry}>\n Retry\n </button>\n )}\n\n {canDownload && (\n <button\n className=\"fv-btn fv-btn--outline fv-btn--sm\"\n onClick={() => downloadSource(file.source, file.name, file.type)}\n >\n <DownloadIcon size={16} /> {t.downloadOriginal}\n </button>\n )}\n </div>\n\n <PreviewErrorDetails\n error={error}\n pluginId={pluginId}\n pluginName={pluginName}\n t={t}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ import * as react from 'react';
2
+
3
+ interface PreviewLoadingProps {
4
+ label?: string;
5
+ }
6
+ declare function PreviewLoading({ label, }: PreviewLoadingProps): react.JSX.Element;
7
+
8
+ export { PreviewLoading };
@@ -0,0 +1,14 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import "./styles/PreviewLoading.css";
3
+ function PreviewLoading({
4
+ label = "Loading preview..."
5
+ }) {
6
+ return /* @__PURE__ */ jsxs("div", { className: "fv-loading", children: [
7
+ /* @__PURE__ */ jsx("div", { className: "fv-spinner fv-spinner--lg" }),
8
+ /* @__PURE__ */ jsx("p", { className: "fv-loading__label", children: label })
9
+ ] });
10
+ }
11
+ export {
12
+ PreviewLoading
13
+ };
14
+ //# sourceMappingURL=PreviewLoading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PreviewLoading.tsx"],"sourcesContent":["import \"./styles/PreviewLoading.css\";\n\ninterface PreviewLoadingProps {\n label?: string;\n}\n\nexport function PreviewLoading({\n label = \"Loading preview...\",\n}: PreviewLoadingProps) {\n return (\n <div className=\"fv-loading\">\n <div className=\"fv-spinner fv-spinner--lg\" />\n <p className=\"fv-loading__label\">{label}</p>\n </div>\n );\n}\n"],"mappings":"AAUI,SACE,KADF;AAVJ,OAAO;AAMA,SAAS,eAAe;AAAA,EAC7B,QAAQ;AACV,GAAwB;AACtB,SACE,qBAAC,SAAI,WAAU,cACb;AAAA,wBAAC,SAAI,WAAU,6BAA4B;AAAA,IAC3C,oBAAC,OAAE,WAAU,qBAAqB,iBAAM;AAAA,KAC1C;AAEJ;","names":[]}
@@ -0,0 +1,10 @@
1
+ import * as react from 'react';
2
+
3
+ interface RtfPreviewProps {
4
+ buffer: ArrayBuffer;
5
+ rawText: string;
6
+ fileName: string;
7
+ }
8
+ declare function RtfPreview({ buffer, rawText, fileName }: RtfPreviewProps): react.JSX.Element;
9
+
10
+ export { RtfPreview };
@@ -0,0 +1,240 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { EyeIcon, Code2Icon } from "./icons";
4
+ import DOMPurify from "dompurify";
5
+ import { ShikiSourceView } from "./ShikiSourceView";
6
+ import { loadRtfJsGlobals } from "./rtf/load-rtfjs";
7
+ import { normalizeRtfCodepage } from "./rtf/normalize-codepage";
8
+ import { useLocale } from "./core/i18n";
9
+ import "./styles/RtfPreview.css";
10
+ import "./styles/ViewModeBar.css";
11
+ function extractRtfText(rtf) {
12
+ let text = rtf;
13
+ let prev;
14
+ do {
15
+ prev = text;
16
+ text = text.replace(/\{\\\*[^{}]*\}/g, "");
17
+ } while (text !== prev);
18
+ text = text.replace(
19
+ /\{\\(?:fonttbl|colortbl|stylesheet|info|generator|listtable|listoverridetable|rsidtbl|datastore|themedata)[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/gi,
20
+ ""
21
+ );
22
+ text = text.replace(/\\par\b\s?/g, "\n");
23
+ text = text.replace(/\\line\b\s?/g, "\n");
24
+ text = text.replace(/\\tab\b\s?/g, " ");
25
+ text = text.replace(/\\u(-?\d+)\??/g, (_, n) => {
26
+ let code = parseInt(n, 10);
27
+ if (code < 0) code += 65536;
28
+ return String.fromCharCode(code);
29
+ });
30
+ text = text.replace(
31
+ /\\'([0-9a-fA-F]{2})/g,
32
+ (_, hex) => String.fromCharCode(parseInt(hex, 16))
33
+ );
34
+ text = text.replace(/\\([\\{}])/g, "$1");
35
+ text = text.replace(/\\[a-z]+-?\d* ?/gi, "");
36
+ text = text.replace(/\\[^a-zA-Z0-9]/g, "");
37
+ text = text.replace(/[{}]/g, "");
38
+ text = text.replace(/\r\n?/g, "\n");
39
+ return text.split("\n").map((l) => l.trim()).filter(Boolean);
40
+ }
41
+ async function buildRtfHtml(buffer) {
42
+ const head = new TextDecoder("ascii", { fatal: false }).decode(
43
+ buffer.slice(0, 16)
44
+ );
45
+ if (!head.trimStart().startsWith("{\\rtf")) {
46
+ return {
47
+ html: null,
48
+ error: "Not a valid RTF stream (missing {\\rtf header)"
49
+ };
50
+ }
51
+ try {
52
+ const { RTFJS } = await loadRtfJsGlobals();
53
+ const { bytes, injectedCodepage } = normalizeRtfCodepage(buffer);
54
+ if (injectedCodepage !== null) {
55
+ console.info(
56
+ `[FileVista][RTF] no \\ansicpg in source \u2014 inferred cp${injectedCodepage} from \\fcharset`
57
+ );
58
+ }
59
+ const doc = new RTFJS.Document(bytes.buffer.slice(
60
+ bytes.byteOffset,
61
+ bytes.byteOffset + bytes.byteLength
62
+ ), {});
63
+ const elements = await doc.render();
64
+ const container = document.createElement("div");
65
+ for (const el of elements) {
66
+ container.appendChild(el);
67
+ }
68
+ let rawHtml = container.innerHTML;
69
+ rawHtml = rawHtml.replace(/undefined/g, "");
70
+ if (!rawHtml.trim()) {
71
+ return { html: null, error: "Parsed RTF produced no renderable content" };
72
+ }
73
+ const sanitized = DOMPurify.sanitize(rawHtml, {
74
+ USE_PROFILES: { html: true, svg: true, svgFilters: true },
75
+ FORBID_TAGS: ["script", "iframe", "object", "embed", "form", "input", "button"],
76
+ ALLOWED_ATTR: [
77
+ "href",
78
+ "src",
79
+ "alt",
80
+ "title",
81
+ "width",
82
+ "height",
83
+ "colspan",
84
+ "rowspan",
85
+ "align",
86
+ "valign",
87
+ "border",
88
+ "cellpadding",
89
+ "cellspacing",
90
+ "class",
91
+ "style"
92
+ ]
93
+ });
94
+ return { html: sanitized, error: null };
95
+ } catch (err) {
96
+ const message = err instanceof Error ? err.message : String(err);
97
+ console.warn("[FileVista][RTF] rich render failed, falling back to text:", message);
98
+ return { html: null, error: message };
99
+ }
100
+ }
101
+ function buildIframeDoc(html) {
102
+ return `<!DOCTYPE html>
103
+ <html>
104
+ <head>
105
+ <meta charset="utf-8">
106
+ <style>
107
+ * { margin: 0; padding: 0; box-sizing: border-box; }
108
+ html, body {
109
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
110
+ "Hiragino Sans GB", "Microsoft YaHei", Roboto, "Helvetica Neue",
111
+ Arial, sans-serif;
112
+ font-size: 15px; line-height: 1.7; color: #1f2937; background: #fff;
113
+ }
114
+ body { padding: 32px 40px; max-width: 860px; margin: 0 auto; }
115
+ h1, h2, h3 { line-height: 1.3; font-weight: 600; color: #111827; }
116
+ h1 { font-size: 1.75rem; margin: 1.4rem 0 0.9rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; }
117
+ h2 { font-size: 1.4rem; margin: 1.3rem 0 0.7rem; padding-bottom: 0.35rem; border-bottom: 1px solid #e5e7eb; }
118
+ h3 { font-size: 1.15rem; margin: 1.1rem 0 0.55rem; }
119
+ h1:first-child, h2:first-child, h3:first-child { margin-top: 0; }
120
+ p { margin: 0 0 0.8rem; } p:last-child { margin-bottom: 0; }
121
+ strong, b { font-weight: 600; color: #111827; }
122
+ em, i { font-style: italic; } u { text-decoration: underline; text-underline-offset: 2px; }
123
+ s, strike, del { text-decoration: line-through; }
124
+ table { border-collapse: collapse; width: 100%; margin: 0.8rem 0; }
125
+ th, td { border: 1px solid #e5e7eb; padding: 8px 12px; text-align: left; vertical-align: top; }
126
+ th { background: #f9fafb; font-weight: 600; }
127
+ ul, ol { padding-left: 1.6rem; margin: 0 0 0.8rem; } li { margin-bottom: 0.25rem; }
128
+ a { color: #2563eb; text-decoration: underline; }
129
+ blockquote { border-left: 3px solid #d1d5db; padding: 0.25rem 0 0.25rem 1rem; margin: 0.8rem 0; color: #4b5563; }
130
+ pre { background: #f3f4f6; padding: 12px 16px; border-radius: 6px; overflow-x: auto; font-size: 13px; line-height: 1.5; margin: 0.8rem 0; }
131
+ code { background: #f3f4f6; padding: 2px 5px; border-radius: 3px; font-size: 13px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
132
+ pre code { background: none; padding: 0; }
133
+ img, svg { max-width: 100%; height: auto; }
134
+ hr { border: none; border-top: 1px solid #e5e7eb; margin: 1.2rem 0; }
135
+ body > div { margin: 0; }
136
+ @media (prefers-color-scheme: dark) {
137
+ html, body { color: #d1d5db; background: #111827; }
138
+ h1, h2, h3 { color: #f3f4f6; } h1, h2 { border-color: #374151; }
139
+ strong, b { color: #f9fafb; } th { background: #1f2937; } th, td { border-color: #374151; }
140
+ pre, code { background: #1f2937; } blockquote { border-color: #4b5563; color: #9ca3af; }
141
+ a { color: #60a5fa; } hr { border-color: #374151; }
142
+ }
143
+ </style>
144
+ </head>
145
+ <body>${html}</body></html>`;
146
+ }
147
+ function RtfPreview({ buffer, rawText, fileName }) {
148
+ const [viewMode, setViewMode] = useState("preview");
149
+ const [iframeHtml, setIframeHtml] = useState(null);
150
+ const [renderError, setRenderError] = useState(null);
151
+ const [renderState, setRenderState] = useState("loading");
152
+ const t = useLocale();
153
+ useEffect(() => {
154
+ let cancelled = false;
155
+ setRenderState("loading");
156
+ buildRtfHtml(buffer).then((result) => {
157
+ if (cancelled) return;
158
+ if (result.html !== null) {
159
+ setIframeHtml(buildIframeDoc(result.html));
160
+ setRenderError(null);
161
+ } else {
162
+ setIframeHtml(null);
163
+ setRenderError(result.error);
164
+ }
165
+ setRenderState("done");
166
+ });
167
+ return () => {
168
+ cancelled = true;
169
+ };
170
+ }, [buffer]);
171
+ return /* @__PURE__ */ jsxs("div", { className: "fv-rtf", children: [
172
+ /* @__PURE__ */ jsxs("div", { className: "fv-rtf__topbar", children: [
173
+ /* @__PURE__ */ jsxs("div", { className: "fv-rtf__topbar-left", children: [
174
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "var(--fv-font-size-sm)" }, children: "\u{1F4C3}" }),
175
+ /* @__PURE__ */ jsx("span", { className: "fv-rtf__filename", children: fileName }),
176
+ /* @__PURE__ */ jsx("span", { className: "fv-rtf__filetype", children: "RTF" })
177
+ ] }),
178
+ /* @__PURE__ */ jsxs("div", { className: "fv-view-mode-group", children: [
179
+ /* @__PURE__ */ jsxs(
180
+ "button",
181
+ {
182
+ onClick: () => setViewMode("preview"),
183
+ className: `fv-view-mode-btn ${viewMode === "preview" ? "fv-view-mode-btn--active" : ""}`,
184
+ children: [
185
+ /* @__PURE__ */ jsx(EyeIcon, { size: 13 }),
186
+ t.preview
187
+ ]
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsxs(
191
+ "button",
192
+ {
193
+ onClick: () => setViewMode("source"),
194
+ className: `fv-view-mode-btn ${viewMode === "source" ? "fv-view-mode-btn--active" : ""}`,
195
+ children: [
196
+ /* @__PURE__ */ jsx(Code2Icon, { size: 13 }),
197
+ t.source
198
+ ]
199
+ }
200
+ )
201
+ ] })
202
+ ] }),
203
+ /* @__PURE__ */ jsx("div", { className: "fv-rtf__content", children: viewMode === "preview" ? renderState === "loading" ? /* @__PURE__ */ jsx("div", { className: "fv-rtf__loading", children: t.loadingRtf }) : iframeHtml ? /* @__PURE__ */ jsx(
204
+ "iframe",
205
+ {
206
+ srcDoc: iframeHtml,
207
+ sandbox: "",
208
+ className: "fv-rtf__iframe",
209
+ title: `Preview of ${fileName}`
210
+ }
211
+ ) : /* @__PURE__ */ jsx(RtfTextFallback, { rawText, renderError, t }) : /* @__PURE__ */ jsx(ShikiSourceView, { content: rawText, fileName, language: "text" }) })
212
+ ] });
213
+ }
214
+ function RtfTextFallback({
215
+ rawText,
216
+ renderError,
217
+ t
218
+ }) {
219
+ const paragraphs = extractRtfText(rawText);
220
+ return /* @__PURE__ */ jsx("div", { className: "fv-rtf-text-fallback", children: /* @__PURE__ */ jsxs("div", { className: "fv-rtf-text-fallback__inner", children: [
221
+ /* @__PURE__ */ jsxs("div", { className: "fv-rtf-text-fallback__warn", children: [
222
+ /* @__PURE__ */ jsxs("div", { className: "fv-rtf-text-fallback__warn-header", children: [
223
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "var(--fv-font-size-sm)" }, children: "\u26A0\uFE0F" }),
224
+ /* @__PURE__ */ jsx("span", { className: "fv-rtf-text-fallback__warn-text", children: t.rtfFallback })
225
+ ] }),
226
+ renderError && /* @__PURE__ */ jsxs("details", { style: { marginTop: "0.5rem" }, children: [
227
+ /* @__PURE__ */ jsx("summary", { style: { cursor: "pointer", fontSize: "11px", color: "var(--fv-warning)", opacity: 0.7 }, children: t.showErrorDetails }),
228
+ /* @__PURE__ */ jsx("pre", { style: { marginTop: "0.375rem", fontSize: "10px", color: "var(--fv-warning)", opacity: 0.7, whiteSpace: "pre-wrap", wordBreak: "break-all" }, children: renderError })
229
+ ] })
230
+ ] }),
231
+ /* @__PURE__ */ jsxs("div", { className: "fv-rtf-text-fallback__paper", children: [
232
+ paragraphs.map((para, i) => /* @__PURE__ */ jsx("p", { className: "fv-rtf-text-fallback__p", children: para }, i)),
233
+ paragraphs.length === 0 && /* @__PURE__ */ jsx("p", { style: { color: "var(--fv-muted-foreground)", fontSize: "var(--fv-font-size-sm)" }, children: t.rtfNoText })
234
+ ] })
235
+ ] }) });
236
+ }
237
+ export {
238
+ RtfPreview
239
+ };
240
+ //# sourceMappingURL=RtfPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/RtfPreview.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { EyeIcon, Code2Icon } from \"./icons\";\nimport DOMPurify from \"dompurify\";\nimport { ShikiSourceView } from \"./ShikiSourceView\";\nimport { loadRtfJsGlobals } from \"./rtf/load-rtfjs\";\nimport { normalizeRtfCodepage } from \"./rtf/normalize-codepage\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/RtfPreview.css\";\nimport \"./styles/ViewModeBar.css\";\n\ninterface RtfPreviewProps {\n buffer: ArrayBuffer;\n rawText: string;\n fileName: string;\n}\n\ntype ViewMode = \"preview\" | \"source\";\n\nfunction extractRtfText(rtf: string): string[] {\n let text = rtf;\n let prev: string;\n do {\n prev = text;\n text = text.replace(/\\{\\\\\\*[^{}]*\\}/g, \"\");\n } while (text !== prev);\n\n text = text.replace(\n /\\{\\\\(?:fonttbl|colortbl|stylesheet|info|generator|listtable|listoverridetable|rsidtbl|datastore|themedata)[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}/gi,\n \"\",\n );\n\n text = text.replace(/\\\\par\\b\\s?/g, \"\\n\");\n text = text.replace(/\\\\line\\b\\s?/g, \"\\n\");\n text = text.replace(/\\\\tab\\b\\s?/g, \"\\t\");\n\n text = text.replace(/\\\\u(-?\\d+)\\??/g, (_, n) => {\n let code = parseInt(n, 10);\n if (code < 0) code += 65536;\n return String.fromCharCode(code);\n });\n\n text = text.replace(/\\\\'([0-9a-fA-F]{2})/g, (_, hex) =>\n String.fromCharCode(parseInt(hex, 16)),\n );\n\n text = text.replace(/\\\\([\\\\{}])/g, \"$1\");\n text = text.replace(/\\\\[a-z]+-?\\d* ?/gi, \"\");\n text = text.replace(/\\\\[^a-zA-Z0-9]/g, \"\");\n text = text.replace(/[{}]/g, \"\");\n text = text.replace(/\\r\\n?/g, \"\\n\");\n\n return text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n}\n\nasync function buildRtfHtml(\n buffer: ArrayBuffer,\n): Promise<{ html: string | null; error: string | null }> {\n const head = new TextDecoder(\"ascii\", { fatal: false }).decode(\n buffer.slice(0, 16),\n );\n if (!head.trimStart().startsWith(\"{\\\\rtf\")) {\n return {\n html: null,\n error: \"Not a valid RTF stream (missing {\\\\rtf header)\",\n };\n }\n\n try {\n const { RTFJS } = await loadRtfJsGlobals();\n\n const { bytes, injectedCodepage } = normalizeRtfCodepage(buffer);\n if (injectedCodepage !== null) {\n console.info(\n `[FileVista][RTF] no \\\\ansicpg in source — inferred cp${injectedCodepage} from \\\\fcharset`,\n );\n }\n\n const doc = new RTFJS.Document(bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength,\n ) as ArrayBuffer, {});\n const elements = await doc.render();\n\n const container = document.createElement(\"div\");\n for (const el of elements) {\n container.appendChild(el);\n }\n let rawHtml = container.innerHTML;\n rawHtml = rawHtml.replace(/undefined/g, \"\");\n\n if (!rawHtml.trim()) {\n return { html: null, error: \"Parsed RTF produced no renderable content\" };\n }\n\n const sanitized = DOMPurify.sanitize(rawHtml, {\n USE_PROFILES: { html: true, svg: true, svgFilters: true },\n FORBID_TAGS: [\"script\", \"iframe\", \"object\", \"embed\", \"form\", \"input\", \"button\"],\n ALLOWED_ATTR: [\n \"href\", \"src\", \"alt\", \"title\", \"width\", \"height\",\n \"colspan\", \"rowspan\", \"align\", \"valign\", \"border\",\n \"cellpadding\", \"cellspacing\", \"class\", \"style\",\n ],\n });\n\n return { html: sanitized, error: null };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(\"[FileVista][RTF] rich render failed, falling back to text:\", message);\n return { html: null, error: message };\n }\n}\n\nfunction buildIframeDoc(html: string): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\",\n \"Hiragino Sans GB\", \"Microsoft YaHei\", Roboto, \"Helvetica Neue\",\n Arial, sans-serif;\n font-size: 15px; line-height: 1.7; color: #1f2937; background: #fff;\n }\n body { padding: 32px 40px; max-width: 860px; margin: 0 auto; }\n h1, h2, h3 { line-height: 1.3; font-weight: 600; color: #111827; }\n h1 { font-size: 1.75rem; margin: 1.4rem 0 0.9rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; }\n h2 { font-size: 1.4rem; margin: 1.3rem 0 0.7rem; padding-bottom: 0.35rem; border-bottom: 1px solid #e5e7eb; }\n h3 { font-size: 1.15rem; margin: 1.1rem 0 0.55rem; }\n h1:first-child, h2:first-child, h3:first-child { margin-top: 0; }\n p { margin: 0 0 0.8rem; } p:last-child { margin-bottom: 0; }\n strong, b { font-weight: 600; color: #111827; }\n em, i { font-style: italic; } u { text-decoration: underline; text-underline-offset: 2px; }\n s, strike, del { text-decoration: line-through; }\n table { border-collapse: collapse; width: 100%; margin: 0.8rem 0; }\n th, td { border: 1px solid #e5e7eb; padding: 8px 12px; text-align: left; vertical-align: top; }\n th { background: #f9fafb; font-weight: 600; }\n ul, ol { padding-left: 1.6rem; margin: 0 0 0.8rem; } li { margin-bottom: 0.25rem; }\n a { color: #2563eb; text-decoration: underline; }\n blockquote { border-left: 3px solid #d1d5db; padding: 0.25rem 0 0.25rem 1rem; margin: 0.8rem 0; color: #4b5563; }\n pre { background: #f3f4f6; padding: 12px 16px; border-radius: 6px; overflow-x: auto; font-size: 13px; line-height: 1.5; margin: 0.8rem 0; }\n code { background: #f3f4f6; padding: 2px 5px; border-radius: 3px; font-size: 13px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n pre code { background: none; padding: 0; }\n img, svg { max-width: 100%; height: auto; }\n hr { border: none; border-top: 1px solid #e5e7eb; margin: 1.2rem 0; }\n body > div { margin: 0; }\n @media (prefers-color-scheme: dark) {\n html, body { color: #d1d5db; background: #111827; }\n h1, h2, h3 { color: #f3f4f6; } h1, h2 { border-color: #374151; }\n strong, b { color: #f9fafb; } th { background: #1f2937; } th, td { border-color: #374151; }\n pre, code { background: #1f2937; } blockquote { border-color: #4b5563; color: #9ca3af; }\n a { color: #60a5fa; } hr { border-color: #374151; }\n }\n</style>\n</head>\n<body>${html}</body></html>`;\n}\n\nexport function RtfPreview({ buffer, rawText, fileName }: RtfPreviewProps) {\n const [viewMode, setViewMode] = useState<ViewMode>(\"preview\");\n const [iframeHtml, setIframeHtml] = useState<string | null>(null);\n const [renderError, setRenderError] = useState<string | null>(null);\n const [renderState, setRenderState] = useState<\"loading\" | \"done\">(\"loading\");\n const t = useLocale();\n\n useEffect(() => {\n let cancelled = false;\n setRenderState(\"loading\");\n\n buildRtfHtml(buffer).then((result) => {\n if (cancelled) return;\n if (result.html !== null) {\n setIframeHtml(buildIframeDoc(result.html));\n setRenderError(null);\n } else {\n setIframeHtml(null);\n setRenderError(result.error);\n }\n setRenderState(\"done\");\n });\n\n return () => { cancelled = true; };\n }, [buffer]);\n\n return (\n <div className=\"fv-rtf\">\n <div className=\"fv-rtf__topbar\">\n <div className=\"fv-rtf__topbar-left\">\n <span style={{ fontSize: 'var(--fv-font-size-sm)' }}>📃</span>\n <span className=\"fv-rtf__filename\">{fileName}</span>\n <span className=\"fv-rtf__filetype\">RTF</span>\n </div>\n <div className=\"fv-view-mode-group\">\n <button\n onClick={() => setViewMode(\"preview\")}\n className={`fv-view-mode-btn ${viewMode === \"preview\" ? \"fv-view-mode-btn--active\" : \"\"}`}\n >\n <EyeIcon size={13} />\n {t.preview}\n </button>\n <button\n onClick={() => setViewMode(\"source\")}\n className={`fv-view-mode-btn ${viewMode === \"source\" ? \"fv-view-mode-btn--active\" : \"\"}`}\n >\n <Code2Icon size={13} />\n {t.source}\n </button>\n </div>\n </div>\n\n <div className=\"fv-rtf__content\">\n {viewMode === \"preview\" ? (\n renderState === \"loading\" ? (\n <div className=\"fv-rtf__loading\">{t.loadingRtf}</div>\n ) : iframeHtml ? (\n <iframe\n srcDoc={iframeHtml}\n sandbox=\"\"\n className=\"fv-rtf__iframe\"\n title={`Preview of ${fileName}`}\n />\n ) : (\n <RtfTextFallback rawText={rawText} renderError={renderError} t={t} />\n )\n ) : (\n <ShikiSourceView content={rawText} fileName={fileName} language=\"text\" />\n )}\n </div>\n </div>\n );\n}\n\nfunction RtfTextFallback({\n rawText,\n renderError,\n t,\n}: {\n rawText: string;\n renderError: string | null;\n t: ReturnType<typeof useLocale>;\n}) {\n const paragraphs = extractRtfText(rawText);\n\n return (\n <div className=\"fv-rtf-text-fallback\">\n <div className=\"fv-rtf-text-fallback__inner\">\n <div className=\"fv-rtf-text-fallback__warn\">\n <div className=\"fv-rtf-text-fallback__warn-header\">\n <span style={{ fontSize: 'var(--fv-font-size-sm)' }}>⚠️</span>\n <span className=\"fv-rtf-text-fallback__warn-text\">\n {t.rtfFallback}\n </span>\n </div>\n {renderError && (\n <details style={{ marginTop: '0.5rem' }}>\n <summary style={{ cursor: 'pointer', fontSize: '11px', color: 'var(--fv-warning)', opacity: 0.7 }}>\n {t.showErrorDetails}\n </summary>\n <pre style={{ marginTop: '0.375rem', fontSize: '10px', color: 'var(--fv-warning)', opacity: 0.7, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>\n {renderError}\n </pre>\n </details>\n )}\n </div>\n <div className=\"fv-rtf-text-fallback__paper\">\n {paragraphs.map((para, i) => (\n <p key={i} className=\"fv-rtf-text-fallback__p\">{para}</p>\n ))}\n {paragraphs.length === 0 && (\n <p style={{ color: 'var(--fv-muted-foreground)', fontSize: 'var(--fv-font-size-sm)' }}>\n {t.rtfNoText}\n </p>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ import * as react from 'react';
2
+
3
+ interface ShikiSourceViewProps {
4
+ content: string;
5
+ fileName: string;
6
+ language?: string;
7
+ showToolbar?: boolean;
8
+ }
9
+ declare function ShikiSourceView({ content, fileName, language: languageOverride, showToolbar, }: ShikiSourceViewProps): react.JSX.Element;
10
+
11
+ export { ShikiSourceView };
@@ -0,0 +1,112 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState, useCallback, useMemo } from "react";
3
+ import { CopyIcon, CheckIcon, WrapTextIcon } from "./icons";
4
+ import { highlightCode, getShikiLanguage } from "./shiki";
5
+ import { shouldHighlight } from "./limits";
6
+ import { PlainTextLargePreview } from "./PlainTextLargePreview";
7
+ import { useLocale } from "./core/i18n";
8
+ import "./styles/ShikiSourceView.css";
9
+ function ShikiSourceView({
10
+ content,
11
+ fileName,
12
+ language: languageOverride,
13
+ showToolbar = true
14
+ }) {
15
+ const [html, setHtml] = useState("");
16
+ const [loading, setLoading] = useState(true);
17
+ const [copied, setCopied] = useState(false);
18
+ const [wordWrap, setWordWrap] = useState(true);
19
+ const t = useLocale();
20
+ const language = useMemo(
21
+ () => languageOverride || getShikiLanguage(fileName),
22
+ [languageOverride, fileName]
23
+ );
24
+ const lineCount = useMemo(
25
+ () => content.split("\n").length,
26
+ [content]
27
+ );
28
+ const canHighlight = useMemo(() => shouldHighlight(content), [content]);
29
+ const [prevDeps, setPrevDeps] = useState({ content, language });
30
+ if (prevDeps.content !== content || prevDeps.language !== language) {
31
+ setPrevDeps({ content, language });
32
+ setHtml("");
33
+ setLoading(canHighlight);
34
+ }
35
+ useEffect(() => {
36
+ if (!canHighlight) return;
37
+ let cancelled = false;
38
+ highlightCode(content, language).then(
39
+ (result) => {
40
+ if (cancelled) return;
41
+ setHtml(result);
42
+ setLoading(false);
43
+ },
44
+ (err) => {
45
+ if (cancelled) return;
46
+ console.warn("[ShikiSourceView] highlight error:", err);
47
+ setHtml("");
48
+ setLoading(false);
49
+ }
50
+ );
51
+ return () => {
52
+ cancelled = true;
53
+ };
54
+ }, [content, language, canHighlight]);
55
+ const handleCopy = useCallback(async () => {
56
+ await navigator.clipboard.writeText(content);
57
+ setCopied(true);
58
+ setTimeout(() => setCopied(false), 2e3);
59
+ }, [content]);
60
+ if (!canHighlight) {
61
+ return /* @__PURE__ */ jsx(PlainTextLargePreview, { content, language });
62
+ }
63
+ return /* @__PURE__ */ jsxs("div", { className: "fv-source", children: [
64
+ showToolbar && /* @__PURE__ */ jsxs("div", { className: "fv-source__toolbar", children: [
65
+ /* @__PURE__ */ jsxs("div", { className: "fv-source__toolbar-left", children: [
66
+ /* @__PURE__ */ jsx("span", { className: "fv-source__lang-badge", children: language }),
67
+ /* @__PURE__ */ jsxs("span", { className: "fv-source__line-count", children: [
68
+ lineCount,
69
+ " line",
70
+ lineCount !== 1 ? "s" : ""
71
+ ] })
72
+ ] }),
73
+ /* @__PURE__ */ jsxs("div", { className: "fv-source__toolbar-right", children: [
74
+ /* @__PURE__ */ jsx(
75
+ "button",
76
+ {
77
+ onClick: () => setWordWrap((w) => !w),
78
+ className: `fv-btn fv-btn--icon ${wordWrap ? "fv-source__btn-active" : ""}`,
79
+ title: wordWrap ? "Disable word wrap" : "Enable word wrap",
80
+ children: /* @__PURE__ */ jsx(WrapTextIcon, { size: 14 })
81
+ }
82
+ ),
83
+ /* @__PURE__ */ jsx(
84
+ "button",
85
+ {
86
+ onClick: handleCopy,
87
+ className: "fv-btn fv-btn--icon",
88
+ title: t.copyCode,
89
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { size: 14 }) : /* @__PURE__ */ jsx(CopyIcon, { size: 14 })
90
+ }
91
+ )
92
+ ] })
93
+ ] }),
94
+ /* @__PURE__ */ jsx("div", { className: "fv-source__content", children: loading ? /* @__PURE__ */ jsx("div", { className: "fv-source__loading", children: /* @__PURE__ */ jsxs("div", { className: "fv-source__loading-inner", children: [
95
+ /* @__PURE__ */ jsx("div", { className: "fv-spinner" }),
96
+ /* @__PURE__ */ jsx("p", { className: "fv-source__loading-label", children: "Loading syntax highlighter..." })
97
+ ] }) }) : html ? /* @__PURE__ */ jsx(
98
+ "div",
99
+ {
100
+ className: `fv-shiki-wrapper ${wordWrap ? "fv-shiki-wrap" : "fv-shiki-nowrap"}`,
101
+ dangerouslySetInnerHTML: { __html: html }
102
+ }
103
+ ) : /* @__PURE__ */ jsx("div", { className: "fv-shiki-plaintext", children: /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: content.split("\n").map((line, i) => /* @__PURE__ */ jsxs("div", { className: "line", children: [
104
+ /* @__PURE__ */ jsx("span", { className: "linenumber", children: i + 1 }),
105
+ /* @__PURE__ */ jsx("span", { className: "linecontent", children: line || "\xA0" })
106
+ ] }, i)) }) }) }) })
107
+ ] });
108
+ }
109
+ export {
110
+ ShikiSourceView
111
+ };
112
+ //# sourceMappingURL=ShikiSourceView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ShikiSourceView.tsx"],"sourcesContent":["import { useEffect, useState, useCallback, useMemo } from \"react\";\nimport { CopyIcon, CheckIcon, WrapTextIcon } from \"./icons\";\nimport { highlightCode, getShikiLanguage } from \"./shiki\";\nimport { shouldHighlight } from \"./limits\";\nimport { PlainTextLargePreview } from \"./PlainTextLargePreview\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/ShikiSourceView.css\";\n\ninterface ShikiSourceViewProps {\n content: string;\n fileName: string;\n language?: string;\n showToolbar?: boolean;\n}\n\nexport function ShikiSourceView({\n content,\n fileName,\n language: languageOverride,\n showToolbar = true,\n}: ShikiSourceViewProps) {\n const [html, setHtml] = useState<string>(\"\");\n const [loading, setLoading] = useState(true);\n const [copied, setCopied] = useState(false);\n const [wordWrap, setWordWrap] = useState(true);\n const t = useLocale();\n\n const language = useMemo(\n () => languageOverride || getShikiLanguage(fileName),\n [languageOverride, fileName]\n );\n\n const lineCount = useMemo(\n () => content.split(\"\\n\").length,\n [content]\n );\n\n const canHighlight = useMemo(() => shouldHighlight(content), [content]);\n\n const [prevDeps, setPrevDeps] = useState({ content, language });\n if (prevDeps.content !== content || prevDeps.language !== language) {\n setPrevDeps({ content, language });\n setHtml(\"\");\n setLoading(canHighlight);\n }\n\n useEffect(() => {\n if (!canHighlight) return;\n\n let cancelled = false;\n highlightCode(content, language).then(\n (result) => {\n if (cancelled) return;\n setHtml(result);\n setLoading(false);\n },\n (err) => {\n if (cancelled) return;\n console.warn(\"[ShikiSourceView] highlight error:\", err);\n setHtml(\"\");\n setLoading(false);\n }\n );\n\n return () => {\n cancelled = true;\n };\n }, [content, language, canHighlight]);\n\n const handleCopy = useCallback(async () => {\n await navigator.clipboard.writeText(content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [content]);\n\n if (!canHighlight) {\n return <PlainTextLargePreview content={content} language={language} />;\n }\n\n return (\n <div className=\"fv-source\">\n {showToolbar && (\n <div className=\"fv-source__toolbar\">\n <div className=\"fv-source__toolbar-left\">\n <span className=\"fv-source__lang-badge\">{language}</span>\n <span className=\"fv-source__line-count\">\n {lineCount} line{lineCount !== 1 ? \"s\" : \"\"}\n </span>\n </div>\n <div className=\"fv-source__toolbar-right\">\n <button\n onClick={() => setWordWrap((w) => !w)}\n className={`fv-btn fv-btn--icon ${wordWrap ? \"fv-source__btn-active\" : \"\"}`}\n title={wordWrap ? \"Disable word wrap\" : \"Enable word wrap\"}\n >\n <WrapTextIcon size={14} />\n </button>\n <button\n onClick={handleCopy}\n className=\"fv-btn fv-btn--icon\"\n title={t.copyCode}\n >\n {copied ? <CheckIcon size={14} /> : <CopyIcon size={14} />}\n </button>\n </div>\n </div>\n )}\n\n <div className=\"fv-source__content\">\n {loading ? (\n <div className=\"fv-source__loading\">\n <div className=\"fv-source__loading-inner\">\n <div className=\"fv-spinner\" />\n <p className=\"fv-source__loading-label\">Loading syntax highlighter...</p>\n </div>\n </div>\n ) : html ? (\n <div\n className={`fv-shiki-wrapper ${wordWrap ? \"fv-shiki-wrap\" : \"fv-shiki-nowrap\"}`}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n ) : (\n <div className=\"fv-shiki-plaintext\">\n <pre>\n <code>\n {content.split(\"\\n\").map((line, i) => (\n <div key={i} className=\"line\">\n <span className=\"linenumber\">{i + 1}</span>\n <span className=\"linecontent\">{line || \" \"}</span>\n </div>\n ))}\n </code>\n </pre>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ import * as react from 'react';
2
+
3
+ interface SvgPreviewProps {
4
+ content: string;
5
+ fileName: string;
6
+ }
7
+ declare function SvgPreview({ content, fileName }: SvgPreviewProps): react.JSX.Element;
8
+
9
+ export { SvgPreview };
@@ -0,0 +1,89 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ import { EyeIcon, Code2Icon, Columns2Icon, ZoomInIcon, ZoomOutIcon, RotateCwIcon } from "./icons";
4
+ import { ShikiSourceView } from "./ShikiSourceView";
5
+ import { useLocale } from "./core/i18n";
6
+ import "./styles/SvgPreview.css";
7
+ function SvgPreview({ content, fileName }) {
8
+ const [viewMode, setViewMode] = useState("rendered");
9
+ const [zoom, setZoom] = useState(100);
10
+ const [rotation, setRotation] = useState(0);
11
+ const t = useLocale();
12
+ const svgUrl = useMemo(() => {
13
+ const blob = new Blob([content], { type: "image/svg+xml" });
14
+ return URL.createObjectURL(blob);
15
+ }, [content]);
16
+ useEffect(() => {
17
+ return () => URL.revokeObjectURL(svgUrl);
18
+ }, [svgUrl]);
19
+ const renderedView = /* @__PURE__ */ jsxs("div", { className: "fv-svg__panel", children: [
20
+ /* @__PURE__ */ jsxs("div", { className: "fv-svg__toolbar", children: [
21
+ /* @__PURE__ */ jsx("button", { onClick: () => setZoom((z) => Math.max(z - 25, 25)), className: "fv-btn fv-btn--icon", title: "Zoom Out", children: /* @__PURE__ */ jsx(ZoomOutIcon, { size: 14 }) }),
22
+ /* @__PURE__ */ jsxs("span", { className: "fv-svg__zoom-label", children: [
23
+ zoom,
24
+ "%"
25
+ ] }),
26
+ /* @__PURE__ */ jsx("button", { onClick: () => setZoom((z) => Math.min(z + 25, 400)), className: "fv-btn fv-btn--icon", title: "Zoom In", children: /* @__PURE__ */ jsx(ZoomInIcon, { size: 14 }) }),
27
+ /* @__PURE__ */ jsx("div", { className: "fv-toolbar__separator" }),
28
+ /* @__PURE__ */ jsx("button", { onClick: () => setRotation((r) => (r + 90) % 360), className: "fv-btn fv-btn--icon", title: "Rotate", children: /* @__PURE__ */ jsx(RotateCwIcon, { size: 14 }) }),
29
+ /* @__PURE__ */ jsx("button", { onClick: () => {
30
+ setZoom(100);
31
+ setRotation(0);
32
+ }, className: "fv-svg__reset-btn", children: "Reset" })
33
+ ] }),
34
+ /* @__PURE__ */ jsx("div", { className: "fv-svg__canvas", children: /* @__PURE__ */ jsx(
35
+ "img",
36
+ {
37
+ src: svgUrl,
38
+ alt: fileName,
39
+ className: "fv-svg__img",
40
+ style: { transform: `scale(${zoom / 100}) rotate(${rotation}deg)`, transformOrigin: "center center" }
41
+ }
42
+ ) })
43
+ ] });
44
+ return /* @__PURE__ */ jsxs("div", { className: "fv-svg", children: [
45
+ /* @__PURE__ */ jsx("div", { className: "fv-svg__mode-bar", children: /* @__PURE__ */ jsxs("div", { className: "fv-svg__mode-group", children: [
46
+ /* @__PURE__ */ jsxs(
47
+ "button",
48
+ {
49
+ onClick: () => setViewMode("rendered"),
50
+ className: `fv-svg__mode-btn ${viewMode === "rendered" ? "fv-svg__mode-btn--active" : ""}`,
51
+ children: [
52
+ /* @__PURE__ */ jsx(EyeIcon, { size: 13 }),
53
+ t.preview
54
+ ]
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsxs(
58
+ "button",
59
+ {
60
+ onClick: () => setViewMode("source"),
61
+ className: `fv-svg__mode-btn ${viewMode === "source" ? "fv-svg__mode-btn--active" : ""}`,
62
+ children: [
63
+ /* @__PURE__ */ jsx(Code2Icon, { size: 13 }),
64
+ t.source
65
+ ]
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsxs(
69
+ "button",
70
+ {
71
+ onClick: () => setViewMode("split"),
72
+ className: `fv-svg__mode-btn ${viewMode === "split" ? "fv-svg__mode-btn--active" : ""}`,
73
+ children: [
74
+ /* @__PURE__ */ jsx(Columns2Icon, { size: 13 }),
75
+ t.split
76
+ ]
77
+ }
78
+ )
79
+ ] }) }),
80
+ /* @__PURE__ */ jsxs("div", { className: "fv-svg__content", children: [
81
+ (viewMode === "rendered" || viewMode === "split") && /* @__PURE__ */ jsx("div", { className: `fv-svg__panel ${viewMode === "split" ? "fv-svg__panel--split" : ""}`, children: renderedView }),
82
+ (viewMode === "source" || viewMode === "split") && /* @__PURE__ */ jsx("div", { className: `fv-svg__panel ${viewMode === "split" ? "fv-svg__panel--split-source" : ""}`, children: /* @__PURE__ */ jsx(ShikiSourceView, { content, fileName, language: "xml" }) })
83
+ ] })
84
+ ] });
85
+ }
86
+ export {
87
+ SvgPreview
88
+ };
89
+ //# sourceMappingURL=SvgPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/SvgPreview.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from \"react\";\nimport { EyeIcon, Code2Icon, Columns2Icon, ZoomInIcon, ZoomOutIcon, RotateCwIcon } from \"./icons\";\nimport { ShikiSourceView } from \"./ShikiSourceView\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/SvgPreview.css\";\n\ninterface SvgPreviewProps {\n content: string;\n fileName: string;\n}\n\ntype ViewMode = \"rendered\" | \"source\" | \"split\";\n\nexport function SvgPreview({ content, fileName }: SvgPreviewProps) {\n const [viewMode, setViewMode] = useState<ViewMode>(\"rendered\");\n const [zoom, setZoom] = useState(100);\n const [rotation, setRotation] = useState(0);\n const t = useLocale();\n\n const svgUrl = useMemo(() => {\n const blob = new Blob([content], { type: \"image/svg+xml\" });\n return URL.createObjectURL(blob);\n }, [content]);\n\n useEffect(() => {\n return () => URL.revokeObjectURL(svgUrl);\n }, [svgUrl]);\n\n const renderedView = (\n <div className=\"fv-svg__panel\">\n <div className=\"fv-svg__toolbar\">\n <button onClick={() => setZoom(z => Math.max(z - 25, 25))} className=\"fv-btn fv-btn--icon\" title=\"Zoom Out\">\n <ZoomOutIcon size={14} />\n </button>\n <span className=\"fv-svg__zoom-label\">{zoom}%</span>\n <button onClick={() => setZoom(z => Math.min(z + 25, 400))} className=\"fv-btn fv-btn--icon\" title=\"Zoom In\">\n <ZoomInIcon size={14} />\n </button>\n <div className=\"fv-toolbar__separator\" />\n <button onClick={() => setRotation(r => (r + 90) % 360)} className=\"fv-btn fv-btn--icon\" title=\"Rotate\">\n <RotateCwIcon size={14} />\n </button>\n <button onClick={() => { setZoom(100); setRotation(0); }} className=\"fv-svg__reset-btn\">Reset</button>\n </div>\n <div className=\"fv-svg__canvas\">\n <img\n src={svgUrl}\n alt={fileName}\n className=\"fv-svg__img\"\n style={{ transform: `scale(${zoom / 100}) rotate(${rotation}deg)`, transformOrigin: \"center center\" }}\n />\n </div>\n </div>\n );\n\n return (\n <div className=\"fv-svg\">\n <div className=\"fv-svg__mode-bar\">\n <div className=\"fv-svg__mode-group\">\n <button\n onClick={() => setViewMode(\"rendered\")}\n className={`fv-svg__mode-btn ${viewMode === \"rendered\" ? \"fv-svg__mode-btn--active\" : \"\"}`}\n >\n <EyeIcon size={13} />\n {t.preview}\n </button>\n <button\n onClick={() => setViewMode(\"source\")}\n className={`fv-svg__mode-btn ${viewMode === \"source\" ? \"fv-svg__mode-btn--active\" : \"\"}`}\n >\n <Code2Icon size={13} />\n {t.source}\n </button>\n <button\n onClick={() => setViewMode(\"split\")}\n className={`fv-svg__mode-btn ${viewMode === \"split\" ? \"fv-svg__mode-btn--active\" : \"\"}`}\n >\n <Columns2Icon size={13} />\n {t.split}\n </button>\n </div>\n </div>\n\n <div className=\"fv-svg__content\">\n {(viewMode === \"rendered\" || viewMode === \"split\") && (\n <div className={`fv-svg__panel ${viewMode === \"split\" ? \"fv-svg__panel--split\" : \"\"}`}>\n {renderedView}\n </div>\n )}\n {(viewMode === \"source\" || viewMode === \"split\") && (\n <div className={`fv-svg__panel ${viewMode === \"split\" ? \"fv-svg__panel--split-source\" : \"\"}`}>\n <ShikiSourceView content={content} fileName={fileName} language=\"xml\" />\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"AAgCU,cAEF,YAFE;AAhCV,SAAS,UAAU,WAAW,eAAe;AAC7C,SAAS,SAAS,WAAW,cAAc,YAAY,aAAa,oBAAoB;AACxF,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B,OAAO;AASA,SAAS,WAAW,EAAE,SAAS,SAAS,GAAoB;AACjE,QAAM,CAAC,UAAU,WAAW,IAAI,SAAmB,UAAU;AAC7D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,GAAG;AACpC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,CAAC;AAC1C,QAAM,IAAI,UAAU;AAEpB,QAAM,SAAS,QAAQ,MAAM;AAC3B,UAAM,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1D,WAAO,IAAI,gBAAgB,IAAI;AAAA,EACjC,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,WAAO,MAAM,IAAI,gBAAgB,MAAM;AAAA,EACzC,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,eACJ,qBAAC,SAAI,WAAU,iBACb;AAAA,yBAAC,SAAI,WAAU,mBACb;AAAA,0BAAC,YAAO,SAAS,MAAM,QAAQ,OAAK,KAAK,IAAI,IAAI,IAAI,EAAE,CAAC,GAAG,WAAU,uBAAsB,OAAM,YAC/F,8BAAC,eAAY,MAAM,IAAI,GACzB;AAAA,MACA,qBAAC,UAAK,WAAU,sBAAsB;AAAA;AAAA,QAAK;AAAA,SAAC;AAAA,MAC5C,oBAAC,YAAO,SAAS,MAAM,QAAQ,OAAK,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,GAAG,WAAU,uBAAsB,OAAM,WAChG,8BAAC,cAAW,MAAM,IAAI,GACxB;AAAA,MACA,oBAAC,SAAI,WAAU,yBAAwB;AAAA,MACvC,oBAAC,YAAO,SAAS,MAAM,YAAY,QAAM,IAAI,MAAM,GAAG,GAAG,WAAU,uBAAsB,OAAM,UAC7F,8BAAC,gBAAa,MAAM,IAAI,GAC1B;AAAA,MACA,oBAAC,YAAO,SAAS,MAAM;AAAE,gBAAQ,GAAG;AAAG,oBAAY,CAAC;AAAA,MAAG,GAAG,WAAU,qBAAoB,mBAAK;AAAA,OAC/F;AAAA,IACA,oBAAC,SAAI,WAAU,kBACb;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,KAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAO,EAAE,WAAW,SAAS,OAAO,GAAG,YAAY,QAAQ,QAAQ,iBAAiB,gBAAgB;AAAA;AAAA,IACtG,GACF;AAAA,KACF;AAGF,SACE,qBAAC,SAAI,WAAU,UACb;AAAA,wBAAC,SAAI,WAAU,oBACb,+BAAC,SAAI,WAAU,sBACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,YAAY,UAAU;AAAA,UACrC,WAAW,oBAAoB,aAAa,aAAa,6BAA6B,EAAE;AAAA,UAExF;AAAA,gCAAC,WAAQ,MAAM,IAAI;AAAA,YAClB,EAAE;AAAA;AAAA;AAAA,MACL;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,YAAY,QAAQ;AAAA,UACnC,WAAW,oBAAoB,aAAa,WAAW,6BAA6B,EAAE;AAAA,UAEtF;AAAA,gCAAC,aAAU,MAAM,IAAI;AAAA,YACpB,EAAE;AAAA;AAAA;AAAA,MACL;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,YAAY,OAAO;AAAA,UAClC,WAAW,oBAAoB,aAAa,UAAU,6BAA6B,EAAE;AAAA,UAErF;AAAA,gCAAC,gBAAa,MAAM,IAAI;AAAA,YACvB,EAAE;AAAA;AAAA;AAAA,MACL;AAAA,OACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,mBACX;AAAA,oBAAa,cAAc,aAAa,YACxC,oBAAC,SAAI,WAAW,iBAAiB,aAAa,UAAU,yBAAyB,EAAE,IAChF,wBACH;AAAA,OAEA,aAAa,YAAY,aAAa,YACtC,oBAAC,SAAI,WAAW,iBAAiB,aAAa,UAAU,gCAAgC,EAAE,IACxF,8BAAC,mBAAgB,SAAkB,UAAoB,UAAS,OAAM,GACxE;AAAA,OAEJ;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,9 @@
1
+ import * as react from 'react';
2
+
3
+ interface TextPreviewProps {
4
+ content: string;
5
+ fileName: string;
6
+ }
7
+ declare function TextPreview({ content, fileName }: TextPreviewProps): react.JSX.Element;
8
+
9
+ export { TextPreview };
@@ -0,0 +1,9 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { ShikiSourceView } from "./ShikiSourceView";
3
+ function TextPreview({ content, fileName }) {
4
+ return /* @__PURE__ */ jsx(ShikiSourceView, { content, fileName, language: "text" });
5
+ }
6
+ export {
7
+ TextPreview
8
+ };
9
+ //# sourceMappingURL=TextPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/TextPreview.tsx"],"sourcesContent":["import { ShikiSourceView } from \"./ShikiSourceView\";\n\ninterface TextPreviewProps {\n content: string;\n fileName: string;\n}\n\nexport function TextPreview({ content, fileName }: TextPreviewProps) {\n return <ShikiSourceView content={content} fileName={fileName} language=\"text\" />;\n}\n"],"mappings":"AAQS;AART,SAAS,uBAAuB;AAOzB,SAAS,YAAY,EAAE,SAAS,SAAS,GAAqB;AACnE,SAAO,oBAAC,mBAAgB,SAAkB,UAAoB,UAAS,QAAO;AAChF;","names":[]}
@@ -0,0 +1,9 @@
1
+ import * as react from 'react';
2
+
3
+ interface VideoPreviewProps {
4
+ url: string;
5
+ fileName: string;
6
+ }
7
+ declare function VideoPreview({ url, fileName }: VideoPreviewProps): react.JSX.Element;
8
+
9
+ export { VideoPreview };