@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.
- package/COPYING +674 -0
- package/LICENSE +165 -0
- package/README.md +165 -0
- package/dist/AudioPreview.d.ts +9 -0
- package/dist/AudioPreview.js +29 -0
- package/dist/AudioPreview.js.map +1 -0
- package/dist/CodePreview.d.ts +10 -0
- package/dist/CodePreview.js +121 -0
- package/dist/CodePreview.js.map +1 -0
- package/dist/CsvPreview.d.ts +9 -0
- package/dist/CsvPreview.js +117 -0
- package/dist/CsvPreview.js.map +1 -0
- package/dist/DocxPreview.d.ts +11 -0
- package/dist/DocxPreview.js +89 -0
- package/dist/DocxPreview.js.map +1 -0
- package/dist/EpubPreview.d.ts +9 -0
- package/dist/EpubPreview.js +693 -0
- package/dist/EpubPreview.js.map +1 -0
- package/dist/HtmlPreview.d.ts +9 -0
- package/dist/HtmlPreview.js +60 -0
- package/dist/HtmlPreview.js.map +1 -0
- package/dist/ImagePreview.d.ts +9 -0
- package/dist/ImagePreview.js +44 -0
- package/dist/ImagePreview.js.map +1 -0
- package/dist/LargeFileGate.d.ts +12 -0
- package/dist/LargeFileGate.js +88 -0
- package/dist/LargeFileGate.js.map +1 -0
- package/dist/MarkdownPreview.d.ts +8 -0
- package/dist/MarkdownPreview.js +140 -0
- package/dist/MarkdownPreview.js.map +1 -0
- package/dist/PdfPreview.d.ts +11 -0
- package/dist/PdfPreview.js +206 -0
- package/dist/PdfPreview.js.map +1 -0
- package/dist/PlainTextLargePreview.d.ts +9 -0
- package/dist/PlainTextLargePreview.js +62 -0
- package/dist/PlainTextLargePreview.js.map +1 -0
- package/dist/PluginPreviewRenderer.d.ts +13 -0
- package/dist/PluginPreviewRenderer.js +89 -0
- package/dist/PluginPreviewRenderer.js.map +1 -0
- package/dist/PptxPreview.d.ts +16 -0
- package/dist/PptxPreview.js +376 -0
- package/dist/PptxPreview.js.map +1 -0
- package/dist/PreviewErrorBoundary.d.ts +29 -0
- package/dist/PreviewErrorBoundary.js +53 -0
- package/dist/PreviewErrorBoundary.js.map +1 -0
- package/dist/PreviewFallback.d.ts +18 -0
- package/dist/PreviewFallback.js +143 -0
- package/dist/PreviewFallback.js.map +1 -0
- package/dist/PreviewLoading.d.ts +8 -0
- package/dist/PreviewLoading.js +14 -0
- package/dist/PreviewLoading.js.map +1 -0
- package/dist/RtfPreview.d.ts +10 -0
- package/dist/RtfPreview.js +240 -0
- package/dist/RtfPreview.js.map +1 -0
- package/dist/ShikiSourceView.d.ts +11 -0
- package/dist/ShikiSourceView.js +112 -0
- package/dist/ShikiSourceView.js.map +1 -0
- package/dist/SvgPreview.d.ts +9 -0
- package/dist/SvgPreview.js +89 -0
- package/dist/SvgPreview.js.map +1 -0
- package/dist/TextPreview.d.ts +9 -0
- package/dist/TextPreview.js +9 -0
- package/dist/TextPreview.js.map +1 -0
- package/dist/VideoPreview.d.ts +9 -0
- package/dist/VideoPreview.js +18 -0
- package/dist/VideoPreview.js.map +1 -0
- package/dist/XlsxPreview.d.ts +12 -0
- package/dist/XlsxPreview.js +856 -0
- package/dist/XlsxPreview.js.map +1 -0
- package/dist/ZipPreview.d.ts +9 -0
- package/dist/ZipPreview.js +153 -0
- package/dist/ZipPreview.js.map +1 -0
- package/dist/core/binary.d.ts +10 -0
- package/dist/core/binary.js +27 -0
- package/dist/core/binary.js.map +1 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +19 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/download.d.ts +5 -0
- package/dist/core/download.js +20 -0
- package/dist/core/download.js.map +1 -0
- package/dist/core/i18n.d.ts +101 -0
- package/dist/core/i18n.js +200 -0
- package/dist/core/i18n.js.map +1 -0
- package/dist/core/plugin.d.ts +38 -0
- package/dist/core/plugin.js +46 -0
- package/dist/core/plugin.js.map +1 -0
- package/dist/core/registry.d.ts +13 -0
- package/dist/core/registry.js +33 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/source.d.ts +14 -0
- package/dist/core/source.js +131 -0
- package/dist/core/source.js.map +1 -0
- package/dist/core/types.d.ts +88 -0
- package/dist/core/types.js +27 -0
- package/dist/core/types.js.map +1 -0
- package/dist/hooks/useObjectUrlFromSource.d.ts +9 -0
- package/dist/hooks/useObjectUrlFromSource.js +50 -0
- package/dist/hooks/useObjectUrlFromSource.js.map +1 -0
- package/dist/hooks/useSourceBase64.d.ts +10 -0
- package/dist/hooks/useSourceBase64.js +32 -0
- package/dist/hooks/useSourceBase64.js.map +1 -0
- package/dist/hooks/useSourceText.d.ts +10 -0
- package/dist/hooks/useSourceText.js +32 -0
- package/dist/hooks/useSourceText.js.map +1 -0
- package/dist/icons.d.ts +44 -0
- package/dist/icons.js +248 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +26 -0
- package/dist/limits.js +45 -0
- package/dist/limits.js.map +1 -0
- package/dist/performance-limits.d.ts +27 -0
- package/dist/performance-limits.js +54 -0
- package/dist/performance-limits.js.map +1 -0
- package/dist/plugins/audio-plugin.d.ts +7 -0
- package/dist/plugins/audio-plugin.js +11 -0
- package/dist/plugins/audio-plugin.js.map +1 -0
- package/dist/plugins/builtin-plugins.d.ts +9 -0
- package/dist/plugins/builtin-plugins.js +43 -0
- package/dist/plugins/builtin-plugins.js.map +1 -0
- package/dist/plugins/csv-plugin.d.ts +7 -0
- package/dist/plugins/csv-plugin.js +11 -0
- package/dist/plugins/csv-plugin.js.map +1 -0
- package/dist/plugins/docx-plugin.d.ts +7 -0
- package/dist/plugins/docx-plugin.js +15 -0
- package/dist/plugins/docx-plugin.js.map +1 -0
- package/dist/plugins/epub-plugin.d.ts +7 -0
- package/dist/plugins/epub-plugin.js +15 -0
- package/dist/plugins/epub-plugin.js.map +1 -0
- package/dist/plugins/html-plugin.d.ts +7 -0
- package/dist/plugins/html-plugin.js +11 -0
- package/dist/plugins/html-plugin.js.map +1 -0
- package/dist/plugins/image-plugin.d.ts +7 -0
- package/dist/plugins/image-plugin.js +11 -0
- package/dist/plugins/image-plugin.js.map +1 -0
- package/dist/plugins/markdown-plugin.d.ts +7 -0
- package/dist/plugins/markdown-plugin.js +11 -0
- package/dist/plugins/markdown-plugin.js.map +1 -0
- package/dist/plugins/pdf-plugin.d.ts +7 -0
- package/dist/plugins/pdf-plugin.js +15 -0
- package/dist/plugins/pdf-plugin.js.map +1 -0
- package/dist/plugins/pptx-plugin.d.ts +7 -0
- package/dist/plugins/pptx-plugin.js +15 -0
- package/dist/plugins/pptx-plugin.js.map +1 -0
- package/dist/plugins/rtf-plugin.d.ts +7 -0
- package/dist/plugins/rtf-plugin.js +15 -0
- package/dist/plugins/rtf-plugin.js.map +1 -0
- package/dist/plugins/source-code-plugin.d.ts +7 -0
- package/dist/plugins/source-code-plugin.js +11 -0
- package/dist/plugins/source-code-plugin.js.map +1 -0
- package/dist/plugins/svg-plugin.d.ts +7 -0
- package/dist/plugins/svg-plugin.js +11 -0
- package/dist/plugins/svg-plugin.js.map +1 -0
- package/dist/plugins/text-plugin.d.ts +7 -0
- package/dist/plugins/text-plugin.js +11 -0
- package/dist/plugins/text-plugin.js.map +1 -0
- package/dist/plugins/video-plugin.d.ts +7 -0
- package/dist/plugins/video-plugin.js +11 -0
- package/dist/plugins/video-plugin.js.map +1 -0
- package/dist/plugins/xlsx-plugin.d.ts +7 -0
- package/dist/plugins/xlsx-plugin.js +15 -0
- package/dist/plugins/xlsx-plugin.js.map +1 -0
- package/dist/plugins/zip-plugin.d.ts +7 -0
- package/dist/plugins/zip-plugin.js +15 -0
- package/dist/plugins/zip-plugin.js.map +1 -0
- package/dist/preview-adapters/AudioPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/AudioPreviewAdapter.js +31 -0
- package/dist/preview-adapters/AudioPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/CsvPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/CsvPreviewAdapter.js +28 -0
- package/dist/preview-adapters/CsvPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/DocxPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/DocxPreviewAdapter.js +16 -0
- package/dist/preview-adapters/DocxPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/EpubPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/EpubPreviewAdapter.js +28 -0
- package/dist/preview-adapters/EpubPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/HtmlPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/HtmlPreviewAdapter.js +28 -0
- package/dist/preview-adapters/HtmlPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/ImagePreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/ImagePreviewAdapter.js +31 -0
- package/dist/preview-adapters/ImagePreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/MarkdownPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/MarkdownPreviewAdapter.js +28 -0
- package/dist/preview-adapters/MarkdownPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/PdfPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/PdfPreviewAdapter.js +16 -0
- package/dist/preview-adapters/PdfPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/PptxPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/PptxPreviewAdapter.js +16 -0
- package/dist/preview-adapters/PptxPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/RtfPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/RtfPreviewAdapter.js +54 -0
- package/dist/preview-adapters/RtfPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/SourceCodePreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/SourceCodePreviewAdapter.js +35 -0
- package/dist/preview-adapters/SourceCodePreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/SvgPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/SvgPreviewAdapter.js +28 -0
- package/dist/preview-adapters/SvgPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/TextPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/TextPreviewAdapter.js +28 -0
- package/dist/preview-adapters/TextPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/UnsupportedPluginPreview.d.ts +11 -0
- package/dist/preview-adapters/UnsupportedPluginPreview.js +34 -0
- package/dist/preview-adapters/UnsupportedPluginPreview.js.map +1 -0
- package/dist/preview-adapters/VideoPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/VideoPreviewAdapter.js +31 -0
- package/dist/preview-adapters/VideoPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/XlsxPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/XlsxPreviewAdapter.js +17 -0
- package/dist/preview-adapters/XlsxPreviewAdapter.js.map +1 -0
- package/dist/preview-adapters/ZipPreviewAdapter.d.ts +8 -0
- package/dist/preview-adapters/ZipPreviewAdapter.js +28 -0
- package/dist/preview-adapters/ZipPreviewAdapter.js.map +1 -0
- package/dist/remote-url.d.ts +20 -0
- package/dist/remote-url.js +478 -0
- package/dist/remote-url.js.map +1 -0
- package/dist/rtf/load-rtfjs.d.ts +42 -0
- package/dist/rtf/load-rtfjs.js +71 -0
- package/dist/rtf/load-rtfjs.js.map +1 -0
- package/dist/rtf/normalize-codepage.d.ts +33 -0
- package/dist/rtf/normalize-codepage.js +88 -0
- package/dist/rtf/normalize-codepage.js.map +1 -0
- package/dist/shiki.d.ts +35 -0
- package/dist/shiki.js +128 -0
- package/dist/shiki.js.map +1 -0
- package/dist/styles/AudioPreview.css +35 -0
- package/dist/styles/CsvPreview.css +106 -0
- package/dist/styles/DocxPreview.css +93 -0
- package/dist/styles/EpubPreview.css +509 -0
- package/dist/styles/HtmlPreview.css +15 -0
- package/dist/styles/ImagePreview.css +45 -0
- package/dist/styles/LargeFileGate.css +55 -0
- package/dist/styles/MarkdownPreview.css +291 -0
- package/dist/styles/PdfPreview.css +68 -0
- package/dist/styles/PlainTextLargePreview.css +85 -0
- package/dist/styles/PluginDebugBar.css +30 -0
- package/dist/styles/PptxPreview.css +207 -0
- package/dist/styles/PreviewFallback.css +88 -0
- package/dist/styles/PreviewLoading.css +13 -0
- package/dist/styles/RtfPreview.css +99 -0
- package/dist/styles/ShikiSourceView.css +159 -0
- package/dist/styles/SvgPreview.css +99 -0
- package/dist/styles/VideoPreview.css +19 -0
- package/dist/styles/ViewModeBar.css +38 -0
- package/dist/styles/XlsxPreview.css +361 -0
- package/dist/styles/ZipPreview.css +86 -0
- package/dist/styles/base.css +238 -0
- package/dist/styles/index.css +39 -0
- package/dist/support-status.d.ts +19 -0
- package/dist/support-status.js +174 -0
- package/dist/support-status.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +205 -0
- package/dist/utils.js.map +1 -0
- package/package.json +125 -0
- package/scripts/copy-pdf-worker.mjs +31 -0
- 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,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,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,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":"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","names":[]}
|
|
@@ -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":[]}
|