@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/EpubPreview.tsx"],"sourcesContent":["\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport JSZip from \"jszip\";\nimport {\n BookOpenIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n ListIcon,\n SearchIcon,\n XIcon,\n ChevronDownIcon,\n} from \"./icons\";\nimport { base64ToUint8Array } from \"./utils\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/EpubPreview.css\";\n\ninterface EpubPreviewProps {\n content: string; // base64 encoded\n fileName: string;\n}\n\ninterface EpubChapter {\n index: number;\n title: string;\n htmlContent: string;\n filePath: string;\n}\n\ninterface TocItem {\n title: string;\n src: string;\n children?: TocItem[];\n}\n\nasync function parseEpub(base64Content: string): Promise<{\n title: string;\n author: string;\n chapters: EpubChapter[];\n toc: TocItem[];\n stylesheets: string[];\n imageMap: Record<string, string>; // relative path -> blob URL\n}> {\n const bytes = base64ToUint8Array(base64Content);\n\n const zip = await JSZip.loadAsync(bytes);\n const chapters: EpubChapter[] = [];\n const toc: TocItem[] = [];\n let bookTitle = \"\";\n let bookAuthor = \"\";\n const stylesheets: string[] = [];\n const imageMap: Record<string, string> = {};\n\n // Helper: extract XML tag attributes (order-independent)\n function getAttr(tag: string, attrName: string): string | null {\n const re = new RegExp(`${attrName}=\"([^\"]*)\"`, \"i\");\n const m = tag.match(re);\n return m ? m[1] : null;\n }\n\n // Helper: resolve relative path against a base directory\n function resolvePath(baseDir: string, relativePath: string): string {\n if (relativePath.startsWith(\"/\")) return relativePath.substring(1);\n // Decode URL-encoded paths (e.g. %20 -> space)\n const decoded = decodeURIComponent(relativePath);\n // Handle anchor-only references\n const pathPart = decoded.split(\"#\")[0];\n if (!pathPart) return baseDir;\n const baseParts = baseDir.split(\"/\").filter(Boolean);\n const relParts = pathPart.split(\"/\");\n for (const part of relParts) {\n if (part === \"..\") {\n baseParts.pop();\n } else if (part !== \".\" && part !== \"\") {\n baseParts.push(part);\n }\n }\n return baseParts.join(\"/\");\n }\n\n // Helper: try to find a file in the zip with flexible matching\n function findZipFile(path: string): JSZip.JSZipObject | null {\n // Try exact match first\n let f = zip.file(path);\n if (f) return f;\n // Try with URL decoding\n try {\n f = zip.file(decodeURIComponent(path));\n if (f) return f;\n } catch {}\n // Try case-insensitive search\n const lowerPath = path.toLowerCase();\n let found: JSZip.JSZipObject | null = null;\n zip.forEach((p, file) => {\n if (!found && p.toLowerCase() === lowerPath) {\n found = file;\n }\n });\n return found;\n }\n\n // ── Step 1: Build image map (blob URLs for all images) ──\n const imageExtensions = [\".jpg\", \".jpeg\", \".png\", \".gif\", \".svg\", \".webp\", \".bmp\"];\n const imagePromises: Promise<void>[] = [];\n zip.forEach((path, file) => {\n if (file.dir) return;\n const lowerPath = path.toLowerCase();\n if (imageExtensions.some((ext) => lowerPath.endsWith(ext))) {\n imagePromises.push(\n (async () => {\n try {\n const blob = await file.async(\"blob\");\n const url = URL.createObjectURL(blob);\n // Map both full path and filename-only\n imageMap[path] = url;\n const filename = path.split(\"/\").pop()!;\n imageMap[filename] = url;\n // Also store lowercase versions for case-insensitive matching\n imageMap[lowerPath] = url;\n imageMap[filename.toLowerCase()] = url;\n } catch {\n // skip unreadable images\n }\n })()\n );\n }\n });\n await Promise.all(imagePromises);\n\n // ── Step 2: Parse container.xml → OPF ──\n const containerFile = zip.file(\"META-INF/container.xml\");\n if (!containerFile) {\n throw new Error(\"Invalid EPUB: missing container.xml\");\n }\n\n const containerXml = await containerFile.async(\"string\");\n const rootfileMatch = containerXml.match(/full-path=\"([^\"]+)\"/);\n if (!rootfileMatch) {\n throw new Error(\"Invalid EPUB: no rootfile in container.xml\");\n }\n\n const opfPath = rootfileMatch[1];\n const opfFile = findZipFile(opfPath);\n if (!opfFile) {\n throw new Error(\"Invalid EPUB: OPF file not found\");\n }\n\n const opfXml = await opfFile.async(\"string\");\n const opfDir = opfPath.includes(\"/\")\n ? opfPath.substring(0, opfPath.lastIndexOf(\"/\") + 1)\n : \"\";\n\n // Extract title & author\n const titleMatch = opfXml.match(/<dc:title[^>]*>([\\s\\S]*?)<\\/dc:title>/);\n if (titleMatch) bookTitle = titleMatch[1].trim();\n const authorMatch = opfXml.match(/<dc:creator[^>]*>([\\s\\S]*?)<\\/dc:creator>/);\n if (authorMatch) bookAuthor = authorMatch[1].trim();\n\n // ── Step 3: Parse manifest (order-independent attribute extraction) ──\n const manifestMatch = opfXml.match(/<manifest[^>]*>([\\s\\S]*?)<\\/manifest>/);\n const manifestMap: Record<string, { href: string; mediaType: string }> = {};\n const manifestHrefToId: Record<string, string> = {};\n if (manifestMatch) {\n const itemRegex = /<item\\s+([^>]+?)\\/?>/g;\n let itemMatch;\n while ((itemMatch = itemRegex.exec(manifestMatch[1])) !== null) {\n const attrs = itemMatch[1];\n const id = getAttr(attrs, \"id\");\n const href = getAttr(attrs, \"href\");\n const mediaType = getAttr(attrs, \"media-type\") || \"\";\n if (id && href) {\n manifestMap[id] = { href, mediaType };\n manifestHrefToId[href] = id;\n }\n }\n }\n\n // ── Step 4: Parse spine (reading order) ──\n const spineMatch = opfXml.match(/<spine[^>]*>([\\s\\S]*?)<\\/spine>/);\n const spineItems: string[] = [];\n if (spineMatch) {\n const itemrefRegex = /<itemref\\s+([^>]+?)\\/?>/g;\n let refMatch;\n while ((refMatch = itemrefRegex.exec(spineMatch[1])) !== null) {\n const idref = getAttr(refMatch[1], \"idref\");\n if (idref) spineItems.push(idref);\n }\n }\n\n // ── Step 5: Parse TOC from toc.ncx ──\n const spineTocAttr = spineMatch\n ? (spineMatch[0].match(/toc=\"([^\"]+)\"/)?.[1] ?? null)\n : null;\n if (spineTocAttr) {\n const tocInfo = manifestMap[spineTocAttr];\n if (tocInfo) {\n const tocPath = resolvePath(opfDir, tocInfo.href);\n const tocFile = findZipFile(tocPath);\n if (tocFile) {\n const tocXml = await tocFile.async(\"string\");\n // Parse navPoints recursively\n function parseNavPoints(xml: string, parentPath: string): TocItem[] {\n const items: TocItem[] = [];\n const navRegex = /<navPoint[^>]*>([\\s\\S]*?)<\\/navPoint>/g;\n let navMatch;\n while ((navMatch = navRegex.exec(xml)) !== null) {\n const block = navMatch[1];\n const labelMatch = block.match(\n /<navLabel[^>]*>[\\s\\S]*?<text>([^<]*)<\\/text>/\n );\n const contentMatch = block.match(/<content\\s+src=\"([^\"]+)\"/);\n if (labelMatch && contentMatch) {\n const src = contentMatch[1].split(\"#\")[0]; // remove anchor\n const item: TocItem = {\n title: labelMatch[1].trim(),\n src: resolvePath(parentPath, src),\n };\n // Parse children\n const children = parseNavPoints(block, parentPath);\n if (children.length > 0) item.children = children;\n items.push(item);\n }\n }\n return items;\n }\n const parsed = parseNavPoints(tocXml, opfDir);\n toc.push(...parsed);\n }\n }\n }\n\n // ── Step 6: Collect CSS stylesheets ──\n for (const [, info] of Object.entries(manifestMap)) {\n if (info.mediaType === \"text/css\" || info.href.endsWith(\".css\")) {\n const cssPath = resolvePath(opfDir, info.href);\n const cssFile = findZipFile(cssPath);\n if (cssFile) {\n try {\n const cssText = await cssFile.async(\"string\");\n stylesheets.push(cssText);\n } catch {\n // skip unreadable CSS\n }\n }\n }\n }\n\n // ── Step 7: Build a TOC src → title map for better chapter titles ──\n const tocTitleMap: Record<string, string> = {};\n function flattenToc(items: TocItem[]) {\n for (const item of items) {\n if (item.title) {\n tocTitleMap[item.src] = item.title;\n // Also map just the filename\n const filename = item.src.split(\"/\").pop()!;\n tocTitleMap[filename] = item.title;\n }\n if (item.children) flattenToc(item.children);\n }\n }\n flattenToc(toc);\n\n // ── Step 8: Read chapters in spine order ──\n for (let i = 0; i < spineItems.length; i++) {\n const idref = spineItems[i];\n const info = manifestMap[idref];\n if (!info) continue;\n\n const filePath = resolvePath(opfDir, info.href);\n const chapterFile = findZipFile(filePath);\n if (!chapterFile) continue;\n\n const chapterXml = await chapterFile.async(\"string\");\n\n // Get chapter title: TOC > h1/h2/h3 > <title> > fallback\n let chapterTitle = `Chapter ${i + 1}`;\n // Try TOC first (most accurate)\n const tocTitle =\n tocTitleMap[filePath] ||\n tocTitleMap[filePath.toLowerCase()] ||\n tocTitleMap[info.href] ||\n tocTitleMap[info.href.toLowerCase()];\n if (tocTitle) {\n chapterTitle = tocTitle;\n } else {\n // Try h1/h2/h3\n const hMatch = chapterXml.match(/<h[1-3][^>]*>([\\s\\S]*?)<\\/h[1-3]>/);\n if (hMatch && hMatch[1].trim()) {\n // Strip HTML tags from heading\n chapterTitle = hMatch[1].replace(/<[^>]+>/g, \"\").trim();\n } else {\n const titleTagMatch = chapterXml.match(/<title>([^<]*)<\\/title>/);\n if (titleTagMatch && titleTagMatch[1].trim()) {\n chapterTitle = titleTagMatch[1].trim();\n }\n }\n }\n\n // Extract body content\n const bodyMatch = chapterXml.match(/<body[^>]*>([\\s\\S]*?)<\\/body>/);\n let bodyHtml = bodyMatch ? bodyMatch[1] : chapterXml;\n\n // Clean up: remove scripts only, keep styles for proper rendering\n bodyHtml = bodyHtml\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n // Remove page-break divs that calibre adds\n .replace(/<div\\s+class=\"mbp_pagebreak\"[^>]*><\\/div>/gi, \"\");\n\n // Resolve relative image paths in HTML to blob URLs\n const chapterDir = filePath.includes(\"/\")\n ? filePath.substring(0, filePath.lastIndexOf(\"/\") + 1)\n : \"\";\n bodyHtml = bodyHtml.replace(\n /(<img\\s+[^>]*?)src=\"([^\"]+)\"/gi,\n (_match, prefix, src) => {\n // Skip external URLs\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\") || src.startsWith(\"data:\")) {\n return `${prefix}src=\"${src}\"`;\n }\n const resolvedSrc = resolvePath(chapterDir, src);\n const blobUrl =\n imageMap[resolvedSrc] ||\n imageMap[resolvedSrc.toLowerCase()] ||\n imageMap[src] ||\n imageMap[src.toLowerCase()] ||\n imageMap[src.split(\"/\").pop()!] ||\n imageMap[src.split(\"/\").pop()!.toLowerCase()];\n if (blobUrl) {\n return `${prefix}src=\"${blobUrl}\"`;\n }\n return `${prefix}src=\"${src}\"`;\n }\n );\n\n // Resolve relative anchor links to chapter navigation\n bodyHtml = bodyHtml.replace(\n /(<a\\s+[^>]*?)href=\"([^\"]+)\"/gi,\n (_match, prefix, href) => {\n if (\n href.startsWith(\"http://\") ||\n href.startsWith(\"https://\") ||\n href.startsWith(\"#\") ||\n href.startsWith(\"mailto:\")\n ) {\n return `${prefix}href=\"${href}\"`;\n }\n // Mark internal links with data attribute for handling\n const resolvedHref = resolvePath(chapterDir, href.split(\"#\")[0]);\n const anchor = href.includes(\"#\") ? \"#\" + href.split(\"#\")[1] : \"\";\n return `${prefix}href=\"${resolvedHref}${anchor}\" data-epub-link=\"true\"`;\n }\n );\n\n chapters.push({\n index: i,\n title: chapterTitle,\n htmlContent: bodyHtml,\n filePath,\n });\n }\n\n // ── Fallback: if no chapters via OPF, scan all HTML files ──\n if (chapters.length === 0) {\n const htmlFiles: string[] = [];\n zip.forEach((path) => {\n if (path.endsWith(\".html\") || path.endsWith(\".xhtml\") || path.endsWith(\".htm\")) {\n htmlFiles.push(path);\n }\n });\n htmlFiles.sort();\n\n for (let i = 0; i < htmlFiles.length; i++) {\n const file = zip.file(htmlFiles[i]);\n if (!file) continue;\n\n const html = await file.async(\"string\");\n const bodyMatch = html.match(/<body[^>]*>([\\s\\S]*?)<\\/body>/);\n let bodyHtml = (bodyMatch ? bodyMatch[1] : html)\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<div\\s+class=\"mbp_pagebreak\"[^>]*><\\/div>/gi, \"\");\n\n // Resolve images in fallback mode too\n const chapterDir = htmlFiles[i].includes(\"/\")\n ? htmlFiles[i].substring(0, htmlFiles[i].lastIndexOf(\"/\") + 1)\n : \"\";\n bodyHtml = bodyHtml.replace(\n /(<img\\s+[^>]*?)src=\"([^\"]+)\"/gi,\n (_match, prefix, src) => {\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\") || src.startsWith(\"data:\")) {\n return `${prefix}src=\"${src}\"`;\n }\n const resolvedSrc = resolvePath(chapterDir, src);\n const blobUrl =\n imageMap[resolvedSrc] ||\n imageMap[resolvedSrc.toLowerCase()] ||\n imageMap[src] ||\n imageMap[src.toLowerCase()];\n if (blobUrl) {\n return `${prefix}src=\"${blobUrl}\"`;\n }\n return `${prefix}src=\"${src}\"`;\n }\n );\n\n // Try TOC for title\n const tocTitle =\n tocTitleMap[htmlFiles[i]] || tocTitleMap[htmlFiles[i].toLowerCase()];\n const hMatch = html.match(/<h[1-3][^>]*>([\\s\\S]*?)<\\/h[1-3]>/);\n let chTitle = `Chapter ${i + 1}`;\n if (tocTitle) {\n chTitle = tocTitle;\n } else if (hMatch && hMatch[1].trim()) {\n chTitle = hMatch[1].replace(/<[^>]+>/g, \"\").trim();\n }\n\n chapters.push({\n index: i,\n title: chTitle,\n htmlContent: bodyHtml,\n filePath: htmlFiles[i],\n });\n }\n }\n\n return { title: bookTitle, author: bookAuthor, chapters, toc, stylesheets, imageMap };\n}\n\nexport function EpubPreview({ content, fileName }: EpubPreviewProps) {\n const [bookData, setBookData] = useState<Awaited<ReturnType<typeof parseEpub>> | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [currentChapter, setCurrentChapter] = useState(0);\n const [showToc, setShowToc] = useState(false);\n const [showChapterDropdown, setShowChapterDropdown] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [searchResults, setSearchResults] = useState<{ chapterIndex: number; snippet: string }[]>([]);\n const contentRef = useRef<HTMLDivElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const t = useLocale();\n\n // Reset state when content changes — derived state during render\n const [prevContent, setPrevContent] = useState(content);\n if (prevContent !== content) {\n setPrevContent(content);\n setLoading(true);\n setError(null);\n setBookData(null);\n setCurrentChapter(0);\n setSearchResults([]);\n }\n\n useEffect(() => {\n let cancelled = false;\n parseEpub(content).then(\n (result) => {\n if (cancelled) return;\n setBookData(result);\n setLoading(false);\n },\n (err) => {\n if (cancelled) return;\n console.error(\"Error parsing EPUB:\", err);\n setError(err instanceof Error ? err.message : \"Failed to parse e-book\");\n setLoading(false);\n }\n );\n return () => {\n cancelled = true;\n };\n }, [content]);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(e: MouseEvent) {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setShowChapterDropdown(false);\n }\n }\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n }, []);\n\n // Cleanup blob URLs on unmount\n useEffect(() => {\n return () => {\n if (bookData?.imageMap) {\n Object.values(bookData.imageMap).forEach((url) => {\n if (url.startsWith(\"blob:\")) URL.revokeObjectURL(url);\n });\n }\n };\n }, [bookData?.imageMap]);\n\n // Scroll to top when chapter changes\n useEffect(() => {\n if (contentRef.current) {\n contentRef.current.scrollTop = 0;\n }\n }, [currentChapter]);\n\n // Search functionality\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query);\n if (!bookData || !query.trim()) {\n setSearchResults([]);\n return;\n }\n const lowerQuery = query.toLowerCase();\n const results: { chapterIndex: number; snippet: string }[] = [];\n for (let i = 0; i < bookData.chapters.length; i++) {\n // Strip HTML for text search\n const text = bookData.chapters[i].htmlContent.replace(/<[^>]+>/g, \"\");\n const idx = text.toLowerCase().indexOf(lowerQuery);\n if (idx !== -1) {\n const start = Math.max(0, idx - 30);\n const end = Math.min(text.length, idx + query.length + 30);\n const snippet =\n (start > 0 ? \"...\" : \"\") +\n text.substring(start, end) +\n (end < text.length ? \"...\" : \"\");\n results.push({ chapterIndex: i, snippet });\n if (results.length >= 20) break; // limit results\n }\n }\n setSearchResults(results);\n },\n [bookData]\n );\n\n // Handle internal EPUB link clicks\n const handleContentClick = useCallback(\n (e: React.MouseEvent) => {\n const target = e.target as HTMLElement;\n const anchor = target.closest(\"a\") as HTMLAnchorElement | null;\n if (!anchor || !bookData) return;\n\n const isEpubLink = anchor.getAttribute(\"data-epub-link\") === \"true\";\n if (isEpubLink) {\n e.preventDefault();\n const href = anchor.getAttribute(\"href\") || \"\";\n const targetPath = href.split(\"#\")[0];\n // Find the chapter that matches this path\n const chapterIdx = bookData.chapters.findIndex(\n (ch) =>\n ch.filePath === targetPath ||\n ch.filePath.toLowerCase() === targetPath.toLowerCase()\n );\n if (chapterIdx !== -1) {\n setCurrentChapter(chapterIdx);\n setTimeout(() => {\n if (contentRef.current) contentRef.current.scrollTop = 0;\n }, 100);\n }\n }\n },\n [bookData]\n );\n\n if (loading) {\n return (\n <div className=\"fv-epub__state fv-epub__state--loading\">\n <div className=\"fv-spinner fv-spinner--lg\" />\n <p className=\"fv-epub__state-msg\">{t.loadingEbook}</p>\n </div>\n );\n }\n\n if (error || !bookData) {\n return (\n <div className=\"fv-epub__state fv-epub__state--error\">\n <BookOpenIcon size={48} className=\"fv-epub__state-icon\" />\n <p className=\"fv-epub__state-title\">{t.ebookLoadFailed}</p>\n <p className=\"fv-epub__state-msg\">{error || t.unknownError}</p>\n </div>\n );\n }\n\n if (bookData.chapters.length === 0) {\n return (\n <div className=\"fv-epub__state fv-epub__state--empty\">\n <BookOpenIcon size={48} />\n <p className=\"fv-epub__state-title\">{t.noChaptersFound}</p>\n </div>\n );\n }\n\n const chapter = bookData.chapters[currentChapter];\n\n // Build combined CSS for inline injection\n // Override any absolute positioning or overflow issues in book CSS\n const combinedCss = bookData.stylesheets.join(\"\\n\") + `\n /* Override book styles that may cause horizontal overflow */\n .fv-epub__article * {\n max-width: 100% !important;\n overflow-wrap: break-word !important;\n word-wrap: break-word !important;\n }\n .fv-epub__article pre, .fv-epub__article code {\n overflow-x: auto !important;\n max-width: 100% !important;\n }\n .fv-epub__article img {\n max-width: 100% !important;\n height: auto !important;\n }\n .fv-epub__article table {\n display: block;\n overflow-x: auto;\n max-width: 100%;\n }\n `;\n\n return (\n <div className=\"fv-epub\">\n {/* Book info bar */}\n <div className=\"fv-epub__info-bar\">\n <div className=\"fv-epub__info-row\">\n <div className=\"fv-epub__info-left\">\n <BookOpenIcon size={14} className=\"fv-epub__info-icon\" />\n <span className=\"fv-epub__info-title\">\n {bookData.title || fileName}\n </span>\n {bookData.author && (\n <span className=\"fv-epub__info-author\">\n — {bookData.author}\n </span>\n )}\n </div>\n <div className=\"fv-epub__info-right\">\n {/* Search */}\n <div className=\"fv-epub__search-wrap\">\n <SearchIcon size={14} className=\"fv-epub__search-icon\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n placeholder={t.searchPlaceholder}\n className=\"fv-epub__search-input\"\n />\n {searchQuery && (\n <button\n onClick={() => {\n setSearchQuery(\"\");\n setSearchResults([]);\n }}\n className=\"fv-epub__search-clear\"\n >\n <XIcon size={12} />\n </button>\n )}\n </div>\n {/* TOC toggle */}\n <button\n onClick={() => setShowToc(!showToc)}\n className={`fv-epub__toc-btn ${showToc ? \"fv-epub__toc-btn--active\" : \"\"}`}\n title={t.tableOfContents}\n >\n <ListIcon size={16} />\n </button>\n </div>\n </div>\n </div>\n\n {/* Search results */}\n {searchQuery && searchResults.length > 0 && (\n <div className=\"fv-epub__search-results\">\n <p className=\"fv-epub__search-results-label\">\n {t.foundInChapters.replace(\"{count}\", searchResults.length.toLocaleString())}\n </p>\n <div className=\"fv-epub__search-results-list\">\n {searchResults.map((r, i) => (\n <button\n key={i}\n onClick={() => {\n setCurrentChapter(r.chapterIndex);\n setSearchQuery(\"\");\n setSearchResults([]);\n }}\n className=\"fv-epub__search-result-item\"\n >\n <span className=\"fv-epub__search-result-title\">\n {bookData.chapters[r.chapterIndex].title}\n </span>\n <span className=\"fv-epub__search-result-snippet\">{r.snippet}</span>\n </button>\n ))}\n </div>\n </div>\n )}\n\n {searchQuery && searchResults.length === 0 && (\n <div className=\"fv-epub__search-empty\">\n <p className=\"fv-epub__search-empty-text\">{t.noResultsFound}</p>\n </div>\n )}\n\n <div className=\"fv-epub__body\">\n {/* TOC sidebar */}\n {showToc && (\n <div className=\"fv-epub__toc-sidebar\">\n <div className=\"fv-epub__toc-inner\">\n <h3 className=\"fv-epub__toc-heading\">\n {t.tableOfContents}\n </h3>\n <TocTree\n items={bookData.toc}\n chapters={bookData.chapters}\n currentChapter={currentChapter}\n onSelect={(idx) => {\n setCurrentChapter(idx);\n setShowChapterDropdown(false);\n }}\n />\n {/* Fallback: if no TOC items, show chapter list */}\n {bookData.toc.length === 0 && (\n <div className=\"fv-epub__toc-list\">\n {bookData.chapters.map((ch, i) => (\n <button\n key={i}\n onClick={() => setCurrentChapter(i)}\n className={`fv-epub__toc-item ${i === currentChapter ? \"fv-epub__toc-item--active\" : \"\"}`}\n >\n {ch.title}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Main content area */}\n <div className=\"fv-epub__main\">\n {/* Compact chapter selector bar */}\n <div className=\"fv-epub__chapter-bar\">\n <button\n onClick={() => setCurrentChapter(Math.max(0, currentChapter - 1))}\n disabled={currentChapter === 0}\n className=\"fv-epub__chapter-nav-btn\"\n >\n <ChevronLeftIcon size={16} />\n </button>\n\n {/* Chapter dropdown */}\n <div className=\"fv-epub__dropdown-wrap\" ref={dropdownRef}>\n <button\n onClick={() => setShowChapterDropdown(!showChapterDropdown)}\n className=\"fv-epub__dropdown-trigger\"\n >\n <span className=\"fv-epub__dropdown-index\">\n {currentChapter + 1}/{bookData.chapters.length}\n </span>\n <span className=\"fv-epub__dropdown-title\">\n {chapter.title}\n </span>\n <ChevronDownIcon\n size={14}\n className={`fv-epub__dropdown-chevron ${showChapterDropdown ? \"fv-epub__dropdown-chevron--open\" : \"\"}`}\n />\n </button>\n\n {/* Dropdown list */}\n {showChapterDropdown && (\n <div className=\"fv-epub__dropdown-list\">\n {bookData.chapters.map((ch, i) => (\n <button\n key={i}\n onClick={() => {\n setCurrentChapter(i);\n setShowChapterDropdown(false);\n }}\n className={`fv-epub__dropdown-item ${i === currentChapter ? \"fv-epub__dropdown-item--active\" : \"\"}`}\n >\n <span className=\"fv-epub__dropdown-item-index\">{i + 1}.</span>\n {ch.title}\n </button>\n ))}\n </div>\n )}\n </div>\n\n <button\n onClick={() =>\n setCurrentChapter(Math.min(bookData.chapters.length - 1, currentChapter + 1))\n }\n disabled={currentChapter === bookData.chapters.length - 1}\n className=\"fv-epub__chapter-nav-btn\"\n >\n <ChevronRightIcon size={16} />\n </button>\n </div>\n\n {/* Chapter content */}\n <div className=\"fv-epub__content\" ref={contentRef} onClick={handleContentClick}>\n <div className=\"fv-epub__content-inner\">\n <h2 className=\"fv-epub__chapter-title\">{chapter.title}</h2>\n {/* Inject book CSS */}\n {combinedCss && (\n <style dangerouslySetInnerHTML={{ __html: combinedCss }} />\n )}\n <div\n className=\"fv-epub__article\"\n dangerouslySetInnerHTML={{ __html: chapter.htmlContent }}\n />\n </div>\n </div>\n\n {/* Navigation footer */}\n <div className=\"fv-epub__footer\">\n <button\n onClick={() => setCurrentChapter(Math.max(0, currentChapter - 1))}\n disabled={currentChapter === 0}\n className=\"fv-epub__footer-btn\"\n >\n <ChevronLeftIcon size={16} />\n <span className=\"fv-epub__footer-btn-text\">{t.previous}</span>\n </button>\n <span className=\"fv-epub__footer-label\">\n {currentChapter + 1} / {bookData.chapters.length}\n </span>\n <button\n onClick={() =>\n setCurrentChapter(Math.min(bookData.chapters.length - 1, currentChapter + 1))\n }\n disabled={currentChapter === bookData.chapters.length - 1}\n className=\"fv-epub__footer-btn\"\n >\n <span className=\"fv-epub__footer-btn-text\">{t.next}</span>\n <ChevronRightIcon size={16} />\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n\n// ── TOC Tree Component ──\nfunction TocTree({\n items,\n chapters,\n currentChapter,\n onSelect,\n depth = 0,\n}: {\n items: TocItem[];\n chapters: EpubChapter[];\n currentChapter: number;\n onSelect: (index: number) => void;\n depth?: number;\n}) {\n return (\n <div className={depth > 0 ? \"fv-epub__toc-indent\" : \"fv-epub__toc-list\"}>\n {items.map((item, i) => {\n // Find matching chapter index\n const chapterIdx = chapters.findIndex(\n (ch) =>\n ch.filePath === item.src ||\n ch.filePath.toLowerCase() === item.src.toLowerCase() ||\n item.src.endsWith(ch.filePath.split(\"/\").pop()!) ||\n ch.filePath.endsWith(item.src.split(\"/\").pop()!)\n );\n const isCurrent = chapterIdx === currentChapter;\n\n return (\n <div key={`${depth}-${i}`}>\n <button\n onClick={() => {\n if (chapterIdx !== -1) onSelect(chapterIdx);\n }}\n disabled={chapterIdx === -1}\n className={`fv-epub__toc-item ${isCurrent ? \"fv-epub__toc-item--active\" : \"\"}`}\n style={{ paddingLeft: `${8 + depth * 4}px` }}\n >\n {item.title}\n </button>\n {item.children && item.children.length > 0 && (\n <TocTree\n items={item.children}\n chapters={chapters}\n currentChapter={currentChapter}\n onSelect={onSelect}\n depth={depth + 1}\n />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":["parseNavPoints"]}
@@ -0,0 +1,9 @@
1
+ import * as react from 'react';
2
+
3
+ interface HtmlPreviewProps {
4
+ content: string;
5
+ fileName: string;
6
+ }
7
+ declare function HtmlPreview({ content, fileName }: HtmlPreviewProps): react.JSX.Element;
8
+
9
+ export { HtmlPreview };
@@ -0,0 +1,60 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ import { EyeIcon, Code2Icon } from "./icons";
4
+ import { ShikiSourceView } from "./ShikiSourceView";
5
+ import { useLocale } from "./core/i18n";
6
+ import "./styles/HtmlPreview.css";
7
+ import "./styles/ViewModeBar.css";
8
+ function HtmlPreview({ content, fileName }) {
9
+ const [viewMode, setViewMode] = useState("preview");
10
+ const t = useLocale();
11
+ const [securityMode] = useState("safe");
12
+ const sandbox = securityMode === "trusted" ? "allow-scripts allow-same-origin" : "";
13
+ const blobUrl = useMemo(() => {
14
+ const blob = new Blob([content], { type: "text/html" });
15
+ return URL.createObjectURL(blob);
16
+ }, [content]);
17
+ useEffect(() => {
18
+ return () => URL.revokeObjectURL(blobUrl);
19
+ }, [blobUrl]);
20
+ return /* @__PURE__ */ jsxs("div", { className: "fv-html", children: [
21
+ /* @__PURE__ */ jsx("div", { className: "fv-view-mode-bar", children: /* @__PURE__ */ jsxs("div", { className: "fv-view-mode-group", children: [
22
+ /* @__PURE__ */ jsxs(
23
+ "button",
24
+ {
25
+ onClick: () => setViewMode("preview"),
26
+ className: `fv-view-mode-btn ${viewMode === "preview" ? "fv-view-mode-btn--active" : ""}`,
27
+ children: [
28
+ /* @__PURE__ */ jsx(EyeIcon, { size: 13 }),
29
+ t.preview
30
+ ]
31
+ }
32
+ ),
33
+ /* @__PURE__ */ jsxs(
34
+ "button",
35
+ {
36
+ onClick: () => setViewMode("source"),
37
+ className: `fv-view-mode-btn ${viewMode === "source" ? "fv-view-mode-btn--active" : ""}`,
38
+ children: [
39
+ /* @__PURE__ */ jsx(Code2Icon, { size: 13 }),
40
+ t.source
41
+ ]
42
+ }
43
+ )
44
+ ] }) }),
45
+ /* @__PURE__ */ jsx("div", { className: "fv-html__content", children: viewMode === "preview" ? /* @__PURE__ */ jsx(
46
+ "iframe",
47
+ {
48
+ src: blobUrl,
49
+ sandbox,
50
+ referrerPolicy: "no-referrer",
51
+ className: "fv-html__iframe",
52
+ title: fileName
53
+ }
54
+ ) : /* @__PURE__ */ jsx(ShikiSourceView, { content, fileName, language: "html" }) })
55
+ ] });
56
+ }
57
+ export {
58
+ HtmlPreview
59
+ };
60
+ //# sourceMappingURL=HtmlPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/HtmlPreview.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from \"react\";\nimport { EyeIcon, Code2Icon } from \"./icons\";\nimport { ShikiSourceView } from \"./ShikiSourceView\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/HtmlPreview.css\";\nimport \"./styles/ViewModeBar.css\";\n\ninterface HtmlPreviewProps {\n content: string;\n fileName: string;\n}\n\ntype ViewMode = \"preview\" | \"source\";\ntype HtmlSecurityMode = \"safe\" | \"trusted\";\n\nexport function HtmlPreview({ content, fileName }: HtmlPreviewProps) {\n const [viewMode, setViewMode] = useState<ViewMode>(\"preview\");\n const t = useLocale();\n\n const [securityMode] = useState<HtmlSecurityMode>(\"safe\");\n const sandbox =\n securityMode === \"trusted\" ? \"allow-scripts allow-same-origin\" : \"\";\n\n const blobUrl = useMemo(() => {\n const blob = new Blob([content], { type: \"text/html\" });\n return URL.createObjectURL(blob);\n }, [content]);\n\n useEffect(() => {\n return () => URL.revokeObjectURL(blobUrl);\n }, [blobUrl]);\n\n return (\n <div className=\"fv-html\">\n <div className=\"fv-view-mode-bar\">\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-html__content\">\n {viewMode === \"preview\" ? (\n <iframe\n src={blobUrl}\n sandbox={sandbox}\n referrerPolicy=\"no-referrer\"\n className=\"fv-html__iframe\"\n title={fileName}\n />\n ) : (\n <ShikiSourceView content={content} fileName={fileName} language=\"html\" />\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"AAoCU,SAIE,KAJF;AApCV,SAAS,UAAU,WAAW,eAAe;AAC7C,SAAS,SAAS,iBAAiB;AACnC,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B,OAAO;AACP,OAAO;AAUA,SAAS,YAAY,EAAE,SAAS,SAAS,GAAqB;AACnE,QAAM,CAAC,UAAU,WAAW,IAAI,SAAmB,SAAS;AAC5D,QAAM,IAAI,UAAU;AAEpB,QAAM,CAAC,YAAY,IAAI,SAA2B,MAAM;AACxD,QAAM,UACJ,iBAAiB,YAAY,oCAAoC;AAEnE,QAAM,UAAU,QAAQ,MAAM;AAC5B,UAAM,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,YAAY,CAAC;AACtD,WAAO,IAAI,gBAAgB,IAAI;AAAA,EACjC,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,WAAO,MAAM,IAAI,gBAAgB,OAAO;AAAA,EAC1C,GAAG,CAAC,OAAO,CAAC;AAEZ,SACE,qBAAC,SAAI,WAAU,WACb;AAAA,wBAAC,SAAI,WAAU,oBACb,+BAAC,SAAI,WAAU,sBACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,YAAY,SAAS;AAAA,UACpC,WAAW,oBAAoB,aAAa,YAAY,6BAA6B,EAAE;AAAA,UAEvF;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,OACF,GACF;AAAA,IAEA,oBAAC,SAAI,WAAU,oBACZ,uBAAa,YACZ;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA,gBAAe;AAAA,QACf,WAAU;AAAA,QACV,OAAO;AAAA;AAAA,IACT,IAEA,oBAAC,mBAAgB,SAAkB,UAAoB,UAAS,QAAO,GAE3E;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,9 @@
1
+ import * as react from 'react';
2
+
3
+ interface ImagePreviewProps {
4
+ url: string;
5
+ fileName: string;
6
+ }
7
+ declare function ImagePreview({ url, fileName }: ImagePreviewProps): react.JSX.Element;
8
+
9
+ export { ImagePreview };
@@ -0,0 +1,44 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { ZoomInIcon, ZoomOutIcon, RotateCwIcon } from "./icons";
3
+ import { useState } from "react";
4
+ import "./styles/ImagePreview.css";
5
+ function ImagePreview({ url, fileName }) {
6
+ const [zoom, setZoom] = useState(100);
7
+ const [rotation, setRotation] = useState(0);
8
+ const handleZoomIn = () => setZoom((prev) => Math.min(prev + 25, 400));
9
+ const handleZoomOut = () => setZoom((prev) => Math.max(prev - 25, 25));
10
+ const handleRotate = () => setRotation((prev) => (prev + 90) % 360);
11
+ const handleReset = () => {
12
+ setZoom(100);
13
+ setRotation(0);
14
+ };
15
+ return /* @__PURE__ */ jsxs("div", { className: "fv-image", children: [
16
+ /* @__PURE__ */ jsxs("div", { className: "fv-image__toolbar", children: [
17
+ /* @__PURE__ */ jsx("button", { onClick: handleZoomOut, className: "fv-btn fv-btn--icon", title: "Zoom Out", children: /* @__PURE__ */ jsx(ZoomOutIcon, { size: 16 }) }),
18
+ /* @__PURE__ */ jsxs("span", { className: "fv-image__zoom-label", children: [
19
+ zoom,
20
+ "%"
21
+ ] }),
22
+ /* @__PURE__ */ jsx("button", { onClick: handleZoomIn, className: "fv-btn fv-btn--icon", title: "Zoom In", children: /* @__PURE__ */ jsx(ZoomInIcon, { size: 16 }) }),
23
+ /* @__PURE__ */ jsx("div", { className: "fv-toolbar__separator" }),
24
+ /* @__PURE__ */ jsx("button", { onClick: handleRotate, className: "fv-btn fv-btn--icon", title: "Rotate", children: /* @__PURE__ */ jsx(RotateCwIcon, { size: 16 }) }),
25
+ /* @__PURE__ */ jsx("button", { onClick: handleReset, className: "fv-image__reset-btn", children: "Reset" })
26
+ ] }),
27
+ /* @__PURE__ */ jsx("div", { className: "fv-image__canvas", children: /* @__PURE__ */ jsx(
28
+ "img",
29
+ {
30
+ src: url,
31
+ alt: fileName,
32
+ className: "fv-image__img",
33
+ style: {
34
+ transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,
35
+ transformOrigin: "center center"
36
+ }
37
+ }
38
+ ) })
39
+ ] });
40
+ }
41
+ export {
42
+ ImagePreview
43
+ };
44
+ //# sourceMappingURL=ImagePreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ImagePreview.tsx"],"sourcesContent":["import { ZoomInIcon, ZoomOutIcon, RotateCwIcon } from \"./icons\";\nimport { useState } from \"react\";\nimport \"./styles/ImagePreview.css\";\n\ninterface ImagePreviewProps {\n url: string;\n fileName: string;\n}\n\nexport function ImagePreview({ url, fileName }: ImagePreviewProps) {\n const [zoom, setZoom] = useState(100);\n const [rotation, setRotation] = useState(0);\n\n const handleZoomIn = () => setZoom((prev) => Math.min(prev + 25, 400));\n const handleZoomOut = () => setZoom((prev) => Math.max(prev - 25, 25));\n const handleRotate = () => setRotation((prev) => (prev + 90) % 360);\n const handleReset = () => {\n setZoom(100);\n setRotation(0);\n };\n\n return (\n <div className=\"fv-image\">\n <div className=\"fv-image__toolbar\">\n <button onClick={handleZoomOut} className=\"fv-btn fv-btn--icon\" title=\"Zoom Out\">\n <ZoomOutIcon size={16} />\n </button>\n <span className=\"fv-image__zoom-label\">{zoom}%</span>\n <button onClick={handleZoomIn} className=\"fv-btn fv-btn--icon\" title=\"Zoom In\">\n <ZoomInIcon size={16} />\n </button>\n <div className=\"fv-toolbar__separator\" />\n <button onClick={handleRotate} className=\"fv-btn fv-btn--icon\" title=\"Rotate\">\n <RotateCwIcon size={16} />\n </button>\n <button onClick={handleReset} className=\"fv-image__reset-btn\">\n Reset\n </button>\n </div>\n\n <div className=\"fv-image__canvas\">\n <img\n src={url}\n alt={fileName}\n className=\"fv-image__img\"\n style={{\n transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,\n transformOrigin: \"center center\",\n }}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"AAyBU,cAEF,YAFE;AAzBV,SAAS,YAAY,aAAa,oBAAoB;AACtD,SAAS,gBAAgB;AACzB,OAAO;AAOA,SAAS,aAAa,EAAE,KAAK,SAAS,GAAsB;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,GAAG;AACpC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,CAAC;AAE1C,QAAM,eAAe,MAAM,QAAQ,CAAC,SAAS,KAAK,IAAI,OAAO,IAAI,GAAG,CAAC;AACrE,QAAM,gBAAgB,MAAM,QAAQ,CAAC,SAAS,KAAK,IAAI,OAAO,IAAI,EAAE,CAAC;AACrE,QAAM,eAAe,MAAM,YAAY,CAAC,UAAU,OAAO,MAAM,GAAG;AAClE,QAAM,cAAc,MAAM;AACxB,YAAQ,GAAG;AACX,gBAAY,CAAC;AAAA,EACf;AAEA,SACE,qBAAC,SAAI,WAAU,YACb;AAAA,yBAAC,SAAI,WAAU,qBACb;AAAA,0BAAC,YAAO,SAAS,eAAe,WAAU,uBAAsB,OAAM,YACpE,8BAAC,eAAY,MAAM,IAAI,GACzB;AAAA,MACA,qBAAC,UAAK,WAAU,wBAAwB;AAAA;AAAA,QAAK;AAAA,SAAC;AAAA,MAC9C,oBAAC,YAAO,SAAS,cAAc,WAAU,uBAAsB,OAAM,WACnE,8BAAC,cAAW,MAAM,IAAI,GACxB;AAAA,MACA,oBAAC,SAAI,WAAU,yBAAwB;AAAA,MACvC,oBAAC,YAAO,SAAS,cAAc,WAAU,uBAAsB,OAAM,UACnE,8BAAC,gBAAa,MAAM,IAAI,GAC1B;AAAA,MACA,oBAAC,YAAO,SAAS,aAAa,WAAU,uBAAsB,mBAE9D;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,oBACb;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,KAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAO;AAAA,UACL,WAAW,SAAS,OAAO,GAAG,YAAY,QAAQ;AAAA,UAClD,iBAAiB;AAAA,QACnB;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,12 @@
1
+ import * as react from 'react';
2
+ import { FileInfo } from './core/types.js';
3
+
4
+ interface LargeFileGateProps {
5
+ file: FileInfo;
6
+ confirmed: boolean;
7
+ onConfirm: () => void;
8
+ children: React.ReactNode;
9
+ }
10
+ declare function LargeFileGate({ file, confirmed, onConfirm, children, }: LargeFileGateProps): react.JSX.Element;
11
+
12
+ export { LargeFileGate };
@@ -0,0 +1,88 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { AlertTriangleIcon, DownloadIcon } from "./icons";
3
+ import { formatFileSize } from "./utils";
4
+ import { getPreviewSizePolicy } from "./performance-limits";
5
+ import { downloadSource } from "./core/download";
6
+ import "./styles/LargeFileGate.css";
7
+ function LargeFileGate({
8
+ file,
9
+ confirmed,
10
+ onConfirm,
11
+ children
12
+ }) {
13
+ const policy = getPreviewSizePolicy({
14
+ size: file.size,
15
+ fileType: file.fileType
16
+ });
17
+ if (!policy.shouldWarn) {
18
+ return /* @__PURE__ */ jsx(Fragment, { children });
19
+ }
20
+ if (policy.level === "warning") {
21
+ return /* @__PURE__ */ jsx("div", { className: "fv-gate-warning", children: /* @__PURE__ */ jsxs("div", { className: "fv-gate-warning__inner", children: [
22
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { size: 14 }),
23
+ /* @__PURE__ */ jsxs("span", { children: [
24
+ "Large file: ",
25
+ formatFileSize(file.size),
26
+ ". Preview may be slower."
27
+ ] })
28
+ ] }) });
29
+ }
30
+ if (policy.shouldConfirm && !confirmed) {
31
+ return /* @__PURE__ */ jsxs("div", { className: "fv-gate-confirm", children: [
32
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { size: 48, className: "fv-gate-confirm__icon" }),
33
+ /* @__PURE__ */ jsxs("div", { className: "fv-gate-confirm__body", children: [
34
+ /* @__PURE__ */ jsx("h3", { className: "fv-gate-confirm__title", children: "Large file preview" }),
35
+ /* @__PURE__ */ jsx("p", { className: "fv-gate-confirm__desc", children: policy.message }),
36
+ /* @__PURE__ */ jsxs("p", { className: "fv-gate-confirm__meta", children: [
37
+ file.name,
38
+ " \xB7 ",
39
+ formatFileSize(file.size)
40
+ ] })
41
+ ] }),
42
+ /* @__PURE__ */ jsxs("div", { className: "fv-gate-confirm__actions", children: [
43
+ /* @__PURE__ */ jsx("button", { className: "fv-btn fv-btn--primary", onClick: onConfirm, children: "Preview anyway" }),
44
+ /* @__PURE__ */ jsxs(
45
+ "button",
46
+ {
47
+ className: "fv-btn fv-btn--outline",
48
+ onClick: () => downloadSource(file.source, file.name, file.type),
49
+ children: [
50
+ /* @__PURE__ */ jsx(DownloadIcon, { size: 16 }),
51
+ " Download"
52
+ ]
53
+ }
54
+ )
55
+ ] })
56
+ ] });
57
+ }
58
+ if (policy.shouldBlock) {
59
+ return /* @__PURE__ */ jsxs("div", { className: "fv-gate-confirm", children: [
60
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { size: 48, className: "fv-gate-confirm__icon fv-gate-confirm__icon--block" }),
61
+ /* @__PURE__ */ jsxs("div", { className: "fv-gate-confirm__body", children: [
62
+ /* @__PURE__ */ jsx("h3", { className: "fv-gate-confirm__title", children: "File too large to preview" }),
63
+ /* @__PURE__ */ jsx("p", { className: "fv-gate-confirm__desc", children: policy.message }),
64
+ /* @__PURE__ */ jsxs("p", { className: "fv-gate-confirm__meta", children: [
65
+ file.name,
66
+ " \xB7 ",
67
+ formatFileSize(file.size)
68
+ ] })
69
+ ] }),
70
+ /* @__PURE__ */ jsxs(
71
+ "button",
72
+ {
73
+ className: "fv-btn fv-btn--outline",
74
+ onClick: () => downloadSource(file.source, file.name, file.type),
75
+ children: [
76
+ /* @__PURE__ */ jsx(DownloadIcon, { size: 16 }),
77
+ " Download original file"
78
+ ]
79
+ }
80
+ )
81
+ ] });
82
+ }
83
+ return /* @__PURE__ */ jsx(Fragment, { children });
84
+ }
85
+ export {
86
+ LargeFileGate
87
+ };
88
+ //# sourceMappingURL=LargeFileGate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/LargeFileGate.tsx"],"sourcesContent":["import { AlertTriangleIcon, DownloadIcon } from \"./icons\";\nimport type { FileInfo } from \"./utils\";\nimport { formatFileSize } from \"./utils\";\nimport { getPreviewSizePolicy } from \"./performance-limits\";\nimport { downloadSource } from \"./core/download\";\nimport \"./styles/LargeFileGate.css\";\n\ninterface LargeFileGateProps {\n file: FileInfo;\n confirmed: boolean;\n onConfirm: () => void;\n children: React.ReactNode;\n}\n\nexport function LargeFileGate({\n file,\n confirmed,\n onConfirm,\n children,\n}: LargeFileGateProps) {\n const policy = getPreviewSizePolicy({\n size: file.size,\n fileType: file.fileType,\n });\n\n if (!policy.shouldWarn) {\n return <>{children}</>;\n }\n\n if (policy.level === \"warning\") {\n return (\n <div className=\"fv-gate-warning\">\n <div className=\"fv-gate-warning__inner\">\n <AlertTriangleIcon size={14} />\n <span>\n Large file: {formatFileSize(file.size)}. Preview may be slower.\n </span>\n </div>\n </div>\n );\n }\n\n if (policy.shouldConfirm && !confirmed) {\n return (\n <div className=\"fv-gate-confirm\">\n <AlertTriangleIcon size={48} className=\"fv-gate-confirm__icon\" />\n <div className=\"fv-gate-confirm__body\">\n <h3 className=\"fv-gate-confirm__title\">Large file preview</h3>\n <p className=\"fv-gate-confirm__desc\">\n {policy.message}\n </p>\n <p className=\"fv-gate-confirm__meta\">\n {file.name} · {formatFileSize(file.size)}\n </p>\n </div>\n <div className=\"fv-gate-confirm__actions\">\n <button className=\"fv-btn fv-btn--primary\" onClick={onConfirm}>Preview anyway</button>\n <button\n className=\"fv-btn fv-btn--outline\"\n onClick={() => downloadSource(file.source, file.name, file.type)}\n >\n <DownloadIcon size={16} /> Download\n </button>\n </div>\n </div>\n );\n }\n\n if (policy.shouldBlock) {\n return (\n <div className=\"fv-gate-confirm\">\n <AlertTriangleIcon size={48} className=\"fv-gate-confirm__icon fv-gate-confirm__icon--block\" />\n <div className=\"fv-gate-confirm__body\">\n <h3 className=\"fv-gate-confirm__title\">File too large to preview</h3>\n <p className=\"fv-gate-confirm__desc\">\n {policy.message}\n </p>\n <p className=\"fv-gate-confirm__meta\">\n {file.name} · {formatFileSize(file.size)}\n </p>\n </div>\n <button\n className=\"fv-btn fv-btn--outline\"\n onClick={() => downloadSource(file.source, file.name, file.type)}\n >\n <DownloadIcon size={16} /> Download original file\n </button>\n </div>\n );\n }\n\n return <>{children}</>;\n}\n"],"mappings":"AA0BW,wBAQD,YARC;AA1BX,SAAS,mBAAmB,oBAAoB;AAEhD,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B;AACrC,SAAS,sBAAsB;AAC/B,OAAO;AASA,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,SAAS,qBAAqB;AAAA,IAClC,MAAM,KAAK;AAAA,IACX,UAAU,KAAK;AAAA,EACjB,CAAC;AAED,MAAI,CAAC,OAAO,YAAY;AACtB,WAAO,gCAAG,UAAS;AAAA,EACrB;AAEA,MAAI,OAAO,UAAU,WAAW;AAC9B,WACE,oBAAC,SAAI,WAAU,mBACb,+BAAC,SAAI,WAAU,0BACb;AAAA,0BAAC,qBAAkB,MAAM,IAAI;AAAA,MAC7B,qBAAC,UAAK;AAAA;AAAA,QACS,eAAe,KAAK,IAAI;AAAA,QAAE;AAAA,SACzC;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,MAAI,OAAO,iBAAiB,CAAC,WAAW;AACtC,WACE,qBAAC,SAAI,WAAU,mBACb;AAAA,0BAAC,qBAAkB,MAAM,IAAI,WAAU,yBAAwB;AAAA,MAC/D,qBAAC,SAAI,WAAU,yBACb;AAAA,4BAAC,QAAG,WAAU,0BAAyB,gCAAkB;AAAA,QACzD,oBAAC,OAAE,WAAU,yBACV,iBAAO,SACV;AAAA,QACA,qBAAC,OAAE,WAAU,yBACV;AAAA,eAAK;AAAA,UAAK;AAAA,UAAI,eAAe,KAAK,IAAI;AAAA,WACzC;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,4BACb;AAAA,4BAAC,YAAO,WAAU,0BAAyB,SAAS,WAAW,4BAAc;AAAA,QAC7E;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,SAAS,MAAM,eAAe,KAAK,QAAQ,KAAK,MAAM,KAAK,IAAI;AAAA,YAE/D;AAAA,kCAAC,gBAAa,MAAM,IAAI;AAAA,cAAE;AAAA;AAAA;AAAA,QAC5B;AAAA,SACF;AAAA,OACF;AAAA,EAEJ;AAEA,MAAI,OAAO,aAAa;AACtB,WACE,qBAAC,SAAI,WAAU,mBACb;AAAA,0BAAC,qBAAkB,MAAM,IAAI,WAAU,sDAAqD;AAAA,MAC5F,qBAAC,SAAI,WAAU,yBACb;AAAA,4BAAC,QAAG,WAAU,0BAAyB,uCAAyB;AAAA,QAChE,oBAAC,OAAE,WAAU,yBACV,iBAAO,SACV;AAAA,QACA,qBAAC,OAAE,WAAU,yBACV;AAAA,eAAK;AAAA,UAAK;AAAA,UAAI,eAAe,KAAK,IAAI;AAAA,WACzC;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS,MAAM,eAAe,KAAK,QAAQ,KAAK,MAAM,KAAK,IAAI;AAAA,UAE/D;AAAA,gCAAC,gBAAa,MAAM,IAAI;AAAA,YAAE;AAAA;AAAA;AAAA,MAC5B;AAAA,OACF;AAAA,EAEJ;AAEA,SAAO,gCAAG,UAAS;AACrB;","names":[]}
@@ -0,0 +1,8 @@
1
+ import react__default from 'react';
2
+
3
+ interface MarkdownPreviewProps {
4
+ content: string;
5
+ }
6
+ declare function MarkdownPreview({ content }: MarkdownPreviewProps): react__default.JSX.Element;
7
+
8
+ export { MarkdownPreview };
@@ -0,0 +1,140 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useState, useRef, useMemo } from "react";
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+ import { highlightCode } from "./shiki";
6
+ import { EyeIcon, Code2Icon } from "./icons";
7
+ import { ShikiSourceView } from "./ShikiSourceView";
8
+ import { FILE_PREVIEW_LIMITS } from "./limits";
9
+ import { useLocale } from "./core/i18n";
10
+ import "./styles/MarkdownPreview.css";
11
+ import "./styles/ViewModeBar.css";
12
+ function getTextContent(node) {
13
+ if (typeof node === "string") return node;
14
+ if (typeof node === "number") return String(node);
15
+ if (!node) return "";
16
+ if (Array.isArray(node)) return node.map(getTextContent).join("");
17
+ if (React.isValidElement(node)) {
18
+ const props = node.props;
19
+ if (props.children) return getTextContent(props.children);
20
+ }
21
+ return "";
22
+ }
23
+ function ShikiPreBlock({ children, ...rest }) {
24
+ const childArray = React.Children.toArray(children);
25
+ const codeElement = childArray.find(
26
+ (child) => React.isValidElement(child) && (child.type === "code" || child.props?.className?.includes("language-"))
27
+ );
28
+ const codeClassName = codeElement && React.isValidElement(codeElement) ? codeElement.props?.className || "" : "";
29
+ const langMatch = /language-(\w+)/.exec(codeClassName);
30
+ const language = langMatch ? langMatch[1] : "";
31
+ const codeText = codeElement && React.isValidElement(codeElement) ? getTextContent(codeElement.props.children).replace(/\n$/, "") : getTextContent(children).replace(/\n$/, "");
32
+ if (!language) {
33
+ return /* @__PURE__ */ jsx("pre", { ...rest, children });
34
+ }
35
+ return /* @__PURE__ */ jsx(ShikiPreContent, { code: codeText, language }, `${language}:${codeText}`);
36
+ }
37
+ function ShikiPreContent({ code, language }) {
38
+ const [html, setHtml] = useState("");
39
+ const [loading, setLoading] = useState(true);
40
+ const [error, setError] = useState(false);
41
+ const mountedRef = useRef(true);
42
+ const t = useLocale();
43
+ useEffect(() => {
44
+ mountedRef.current = true;
45
+ let cancelled = false;
46
+ if (code.length > FILE_PREVIEW_LIMITS.SHIKI_MAX_CODE_BLOCK_SIZE) {
47
+ queueMicrotask(() => {
48
+ if (mountedRef.current) {
49
+ setLoading(false);
50
+ setHtml("");
51
+ }
52
+ });
53
+ return;
54
+ }
55
+ highlightCode(code, language).then((result) => {
56
+ if (!cancelled && mountedRef.current) {
57
+ setHtml(result);
58
+ setLoading(false);
59
+ }
60
+ }).catch((err) => {
61
+ console.warn("[MarkdownPreview] Shiki highlight error:", err);
62
+ if (!cancelled && mountedRef.current) {
63
+ setError(true);
64
+ setLoading(false);
65
+ }
66
+ });
67
+ return () => {
68
+ cancelled = true;
69
+ mountedRef.current = false;
70
+ };
71
+ }, [code, language]);
72
+ const [copied, setCopied] = useState(false);
73
+ const handleCopy = () => {
74
+ navigator.clipboard.writeText(code);
75
+ setCopied(true);
76
+ setTimeout(() => setCopied(false), 2e3);
77
+ };
78
+ const isOversized = code.length > FILE_PREVIEW_LIMITS.SHIKI_MAX_CODE_BLOCK_SIZE;
79
+ if (loading || error || !html && !isOversized) {
80
+ return /* @__PURE__ */ jsxs("pre", { className: "md-pre-loading", children: [
81
+ /* @__PURE__ */ jsx("div", { className: "md-pre-header", children: /* @__PURE__ */ jsx("span", { className: "md-lang-badge", children: language }) }),
82
+ /* @__PURE__ */ jsx("code", { className: `language-${language}`, children: code })
83
+ ] });
84
+ }
85
+ if (isOversized) {
86
+ return /* @__PURE__ */ jsxs("pre", { className: "md-pre-loading", children: [
87
+ /* @__PURE__ */ jsxs("div", { className: "md-pre-header", children: [
88
+ /* @__PURE__ */ jsx("span", { className: "md-lang-badge", children: language }),
89
+ /* @__PURE__ */ jsx("span", { className: "md-lang-badge", style: { fontSize: "0.6em", opacity: 0.7 }, children: t.oversizedCodeBlock })
90
+ ] }),
91
+ /* @__PURE__ */ jsx("code", { className: `language-${language}`, children: code })
92
+ ] });
93
+ }
94
+ return /* @__PURE__ */ jsxs("div", { className: "md-code-block", children: [
95
+ /* @__PURE__ */ jsxs("div", { className: "md-pre-header", children: [
96
+ /* @__PURE__ */ jsx("span", { className: "md-lang-badge", children: language }),
97
+ /* @__PURE__ */ jsx("button", { onClick: handleCopy, className: "md-copy-btn", title: "Copy code", children: copied ? /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) }) : /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
98
+ /* @__PURE__ */ jsx("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
99
+ /* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
100
+ ] }) })
101
+ ] }),
102
+ /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } })
103
+ ] });
104
+ }
105
+ function MarkdownPreview({ content }) {
106
+ const [viewMode, setViewMode] = useState("preview");
107
+ const components = useMemo(() => ({ pre: ShikiPreBlock }), []);
108
+ const t = useLocale();
109
+ return /* @__PURE__ */ jsxs("div", { className: "fv-markdown", children: [
110
+ /* @__PURE__ */ jsx("div", { className: "fv-view-mode-bar", children: /* @__PURE__ */ jsxs("div", { className: "fv-view-mode-group", children: [
111
+ /* @__PURE__ */ jsxs(
112
+ "button",
113
+ {
114
+ onClick: () => setViewMode("preview"),
115
+ className: `fv-view-mode-btn ${viewMode === "preview" ? "fv-view-mode-btn--active" : ""}`,
116
+ children: [
117
+ /* @__PURE__ */ jsx(EyeIcon, { size: 13 }),
118
+ t.preview
119
+ ]
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsxs(
123
+ "button",
124
+ {
125
+ onClick: () => setViewMode("source"),
126
+ className: `fv-view-mode-btn ${viewMode === "source" ? "fv-view-mode-btn--active" : ""}`,
127
+ children: [
128
+ /* @__PURE__ */ jsx(Code2Icon, { size: 13 }),
129
+ t.source
130
+ ]
131
+ }
132
+ )
133
+ ] }) }),
134
+ /* @__PURE__ */ jsx("div", { className: "fv-markdown__content", children: viewMode === "preview" ? /* @__PURE__ */ jsx("div", { className: "fv-markdown__preview", children: /* @__PURE__ */ jsx("article", { className: "fv-prose", children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components, children: content }) }) }) : /* @__PURE__ */ jsx(ShikiSourceView, { content, fileName: "markdown.md", language: "markdown" }) })
135
+ ] });
136
+ }
137
+ export {
138
+ MarkdownPreview
139
+ };
140
+ //# sourceMappingURL=MarkdownPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/MarkdownPreview.tsx"],"sourcesContent":["import React, { useEffect, useState, useRef, useMemo, type ReactNode } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { highlightCode } from \"./shiki\";\nimport { EyeIcon, Code2Icon } from \"./icons\";\nimport { ShikiSourceView } from \"./ShikiSourceView\";\nimport { FILE_PREVIEW_LIMITS } from \"./limits\";\nimport { useLocale } from \"./core/i18n\";\nimport \"./styles/MarkdownPreview.css\";\nimport \"./styles/ViewModeBar.css\";\n\ninterface MarkdownPreviewProps {\n content: string;\n}\n\ntype ViewMode = \"preview\" | \"source\";\n\nfunction getTextContent(node: ReactNode): string {\n if (typeof node === \"string\") return node;\n if (typeof node === \"number\") return String(node);\n if (!node) return \"\";\n if (Array.isArray(node)) return node.map(getTextContent).join(\"\");\n if (React.isValidElement(node)) {\n const props = node.props as { children?: ReactNode };\n if (props.children) return getTextContent(props.children);\n }\n return \"\";\n}\n\nfunction ShikiPreBlock({ children, ...rest }: React.HTMLAttributes<HTMLPreElement> & { children?: ReactNode }) {\n const childArray = React.Children.toArray(children);\n const codeElement = childArray.find(\n (child) => React.isValidElement(child) && (child.type === \"code\" || (child.props as { className?: string })?.className?.includes(\"language-\"))\n );\n\n const codeClassName = codeElement && React.isValidElement(codeElement)\n ? (codeElement.props as { className?: string })?.className || \"\"\n : \"\";\n const langMatch = /language-(\\w+)/.exec(codeClassName);\n const language = langMatch ? langMatch[1] : \"\";\n\n const codeText = codeElement && React.isValidElement(codeElement)\n ? getTextContent((codeElement.props as { children?: ReactNode }).children).replace(/\\n$/, \"\")\n : getTextContent(children).replace(/\\n$/, \"\");\n\n if (!language) {\n return <pre {...rest}>{children}</pre>;\n }\n\n return <ShikiPreContent key={`${language}:${codeText}`} code={codeText} language={language} />;\n}\n\nfunction ShikiPreContent({ code, language }: { code: string; language: string }) {\n const [html, setHtml] = useState<string>(\"\");\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(false);\n const mountedRef = useRef(true);\n const t = useLocale();\n\n useEffect(() => {\n mountedRef.current = true;\n let cancelled = false;\n\n if (code.length > FILE_PREVIEW_LIMITS.SHIKI_MAX_CODE_BLOCK_SIZE) {\n queueMicrotask(() => {\n if (mountedRef.current) {\n setLoading(false);\n setHtml(\"\");\n }\n });\n return;\n }\n\n highlightCode(code, language)\n .then((result) => {\n if (!cancelled && mountedRef.current) {\n setHtml(result);\n setLoading(false);\n }\n })\n .catch((err) => {\n console.warn(\"[MarkdownPreview] Shiki highlight error:\", err);\n if (!cancelled && mountedRef.current) {\n setError(true);\n setLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n mountedRef.current = false;\n };\n }, [code, language]);\n\n const [copied, setCopied] = useState(false);\n\n const handleCopy = () => {\n navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const isOversized = code.length > FILE_PREVIEW_LIMITS.SHIKI_MAX_CODE_BLOCK_SIZE;\n\n if (loading || error || (!html && !isOversized)) {\n return (\n <pre className=\"md-pre-loading\">\n <div className=\"md-pre-header\">\n <span className=\"md-lang-badge\">{language}</span>\n </div>\n <code className={`language-${language}`}>{code}</code>\n </pre>\n );\n }\n\n if (isOversized) {\n return (\n <pre className=\"md-pre-loading\">\n <div className=\"md-pre-header\">\n <span className=\"md-lang-badge\">{language}</span>\n <span className=\"md-lang-badge\" style={{ fontSize: \"0.6em\", opacity: 0.7 }}>{t.oversizedCodeBlock}</span>\n </div>\n <code className={`language-${language}`}>{code}</code>\n </pre>\n );\n }\n\n return (\n <div className=\"md-code-block\">\n <div className=\"md-pre-header\">\n <span className=\"md-lang-badge\">{language}</span>\n <button onClick={handleCopy} className=\"md-copy-btn\" title=\"Copy code\">\n {copied ? (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n ) : (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg>\n )}\n </button>\n </div>\n <div dangerouslySetInnerHTML={{ __html: html }} />\n </div>\n );\n}\n\nexport function MarkdownPreview({ content }: MarkdownPreviewProps) {\n const [viewMode, setViewMode] = useState<ViewMode>(\"preview\");\n const components = useMemo(() => ({ pre: ShikiPreBlock }), []);\n const t = useLocale();\n\n return (\n <div className=\"fv-markdown\">\n <div className=\"fv-view-mode-bar\">\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-markdown__content\">\n {viewMode === \"preview\" ? (\n <div className=\"fv-markdown__preview\">\n <article className=\"fv-prose\">\n <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>\n {content}\n </ReactMarkdown>\n </article>\n </div>\n ) : (\n <ShikiSourceView content={content} fileName=\"markdown.md\" language=\"markdown\" />\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ import * as react from 'react';
2
+ import { PreviewSource } from './core/types.js';
3
+
4
+ interface PdfPreviewProps {
5
+ content?: string | null;
6
+ source?: PreviewSource;
7
+ fileName: string;
8
+ }
9
+ declare function PdfPreview({ content, source, fileName }: PdfPreviewProps): react.JSX.Element;
10
+
11
+ export { PdfPreview };