@l1yp/file-viewer 0.1.0 → 0.1.2

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 (439) hide show
  1. package/README.md +2 -0
  2. package/dist/FileViewer.js +2 -0
  3. package/dist/FileViewer.js.map +1 -0
  4. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  5. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  6. package/dist/components/AppxViewer.js +2 -0
  7. package/dist/components/AppxViewer.js.map +1 -0
  8. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  9. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  10. package/dist/components/ArchiveBrowser.js +2 -0
  11. package/dist/components/ArchiveBrowser.js.map +1 -0
  12. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  13. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/AudioViewer.js +2 -0
  15. package/dist/components/AudioViewer.js.map +1 -0
  16. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  17. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  18. package/dist/components/BinObjBrowser.js +2 -0
  19. package/dist/components/BinObjBrowser.js.map +1 -0
  20. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  21. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  22. package/dist/components/CertViewer.js +2 -0
  23. package/dist/components/CertViewer.js.map +1 -0
  24. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  25. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  26. package/dist/components/ClassViewer.js +2 -0
  27. package/dist/components/ClassViewer.js.map +1 -0
  28. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  29. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  30. package/dist/components/CodeBlock.js +2 -0
  31. package/dist/components/CodeBlock.js.map +1 -0
  32. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  33. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  34. package/dist/components/CodeView.js +2 -0
  35. package/dist/components/CodeView.js.map +1 -0
  36. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +2 -0
  37. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  38. package/dist/components/CsvViewer.js +2 -0
  39. package/dist/components/CsvViewer.js.map +1 -0
  40. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  41. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  42. package/dist/components/DicomViewer.js +2 -0
  43. package/dist/components/DicomViewer.js.map +1 -0
  44. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  45. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  46. package/dist/components/DocxNavigationPane.js +9 -0
  47. package/dist/components/DocxNavigationPane.js.map +1 -0
  48. package/dist/components/DocxNavigationPane.vue.d.ts +23 -0
  49. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +98 -0
  50. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/DocxViewer.js +3 -1
  52. package/dist/components/DocxViewer.js.map +1 -0
  53. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +72 -37
  54. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/DrawioViewer.js +2 -0
  56. package/dist/components/DrawioViewer.js.map +1 -0
  57. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  58. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/DxfViewer.js +2 -0
  60. package/dist/components/DxfViewer.js.map +1 -0
  61. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  62. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/EbookViewer.js +2 -0
  64. package/dist/components/EbookViewer.js.map +1 -0
  65. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  66. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/EmlViewer.js +2 -0
  68. package/dist/components/EmlViewer.js.map +1 -0
  69. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  70. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/EplViewer.js +2 -0
  72. package/dist/components/EplViewer.js.map +1 -0
  73. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  74. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/FileViewerBody.js +3 -1
  76. package/dist/components/FileViewerBody.js.map +1 -0
  77. package/dist/components/FileViewerBody.vue.d.ts +5 -0
  78. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +250 -247
  79. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -0
  80. package/dist/components/FileViewerPanel.js +2 -0
  81. package/dist/components/FileViewerPanel.js.map +1 -0
  82. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +2 -0
  83. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  84. package/dist/components/FontViewer.js +2 -0
  85. package/dist/components/FontViewer.js.map +1 -0
  86. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  87. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  88. package/dist/components/FrontmatterCard.js +2 -0
  89. package/dist/components/FrontmatterCard.js.map +1 -0
  90. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js +2 -0
  91. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  92. package/dist/components/GeoViewer.js +2 -0
  93. package/dist/components/GeoViewer.js.map +1 -0
  94. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  95. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  96. package/dist/components/HarViewer.js +2 -0
  97. package/dist/components/HarViewer.js.map +1 -0
  98. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  99. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  100. package/dist/components/HexViewer.js +2 -0
  101. package/dist/components/HexViewer.js.map +1 -0
  102. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  103. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  104. package/dist/components/HprofViewer.js +2 -0
  105. package/dist/components/HprofViewer.js.map +1 -0
  106. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  107. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  108. package/dist/components/ImageLightbox.js +2 -0
  109. package/dist/components/ImageLightbox.js.map +1 -0
  110. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +2 -0
  111. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -0
  112. package/dist/components/InlineText.js +2 -0
  113. package/dist/components/InlineText.js.map +1 -0
  114. package/dist/components/JksViewer.js +2 -0
  115. package/dist/components/JksViewer.js.map +1 -0
  116. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  117. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  118. package/dist/components/JsonlViewer.js +2 -0
  119. package/dist/components/JsonlViewer.js.map +1 -0
  120. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  121. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  122. package/dist/components/LogViewer.js +2 -0
  123. package/dist/components/LogViewer.js.map +1 -0
  124. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  125. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  126. package/dist/components/LrcViewer.js +2 -0
  127. package/dist/components/LrcViewer.js.map +1 -0
  128. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  129. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  130. package/dist/components/MarkdownTable.js +2 -0
  131. package/dist/components/MarkdownTable.js.map +1 -0
  132. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js +2 -0
  133. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  134. package/dist/components/MarkdownText.js +2 -0
  135. package/dist/components/MarkdownText.js.map +1 -0
  136. package/dist/components/MhtViewer.js +2 -0
  137. package/dist/components/MhtViewer.js.map +1 -0
  138. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  139. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  140. package/dist/components/MindMapView.js +2 -0
  141. package/dist/components/MindMapView.js.map +1 -0
  142. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js +2 -0
  143. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  144. package/dist/components/MissingDependency.js +2 -0
  145. package/dist/components/MissingDependency.js.map +1 -0
  146. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js +2 -0
  147. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js.map +1 -0
  148. package/dist/components/ModelViewer.js +2 -0
  149. package/dist/components/ModelViewer.js.map +1 -0
  150. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  151. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  152. package/dist/components/MsiViewer.js +2 -0
  153. package/dist/components/MsiViewer.js.map +1 -0
  154. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  155. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  156. package/dist/components/NotebookViewer.js +2 -0
  157. package/dist/components/NotebookViewer.js.map +1 -0
  158. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  159. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  160. package/dist/components/PdfViewer.js +2 -0
  161. package/dist/components/PdfViewer.js.map +1 -0
  162. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  163. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  164. package/dist/components/PptxNavigationPane.js +9 -0
  165. package/dist/components/PptxNavigationPane.js.map +1 -0
  166. package/dist/components/PptxNavigationPane.vue.d.ts +20 -0
  167. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +120 -0
  168. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  169. package/dist/components/PptxViewer.js +3 -1
  170. package/dist/components/PptxViewer.js.map +1 -0
  171. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +71 -39
  172. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  173. package/dist/components/PsdViewer.js +2 -0
  174. package/dist/components/PsdViewer.js.map +1 -0
  175. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  176. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  177. package/dist/components/QmcAudioViewer.js +2 -0
  178. package/dist/components/QmcAudioViewer.js.map +1 -0
  179. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  180. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/components/RasterViewer.js +2 -0
  182. package/dist/components/RasterViewer.js.map +1 -0
  183. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  184. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  185. package/dist/components/RtfViewer.js +2 -0
  186. package/dist/components/RtfViewer.js.map +1 -0
  187. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  188. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  189. package/dist/components/SevenZipBrowser.js +2 -0
  190. package/dist/components/SevenZipBrowser.js.map +1 -0
  191. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  192. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  193. package/dist/components/SqliteViewer.js +2 -0
  194. package/dist/components/SqliteViewer.js.map +1 -0
  195. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  196. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  197. package/dist/components/SubtitleViewer.js +2 -0
  198. package/dist/components/SubtitleViewer.js.map +1 -0
  199. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  200. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  201. package/dist/components/XlsxChart.js +2 -0
  202. package/dist/components/XlsxChart.js.map +1 -0
  203. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +2 -0
  204. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  205. package/dist/components/XlsxViewer.js +3 -1
  206. package/dist/components/XlsxViewer.js.map +1 -0
  207. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +301 -304
  208. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  209. package/dist/components/XmindViewer.js +2 -0
  210. package/dist/components/XmindViewer.js.map +1 -0
  211. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  212. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  213. package/dist/components/ZipBrowser.js +3 -1
  214. package/dist/components/ZipBrowser.js.map +1 -0
  215. package/dist/components/ZipBrowser.vue.d.ts +3 -1
  216. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +154 -122
  217. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  218. package/dist/components/ZoomImage.js +2 -0
  219. package/dist/components/ZoomImage.js.map +1 -0
  220. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +2 -0
  221. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -0
  222. package/dist/components/epl/EplCodeBlock.js +2 -0
  223. package/dist/components/epl/EplCodeBlock.js.map +1 -0
  224. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  225. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  226. package/dist/components/epl/EplDoc.js +2 -0
  227. package/dist/components/epl/EplDoc.js.map +1 -0
  228. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js +2 -0
  229. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js.map +1 -0
  230. package/dist/components/epl/EplMethod.js +2 -0
  231. package/dist/components/epl/EplMethod.js.map +1 -0
  232. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js +2 -0
  233. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js.map +1 -0
  234. package/dist/components/epl/EplResource.js +2 -0
  235. package/dist/components/epl/EplResource.js.map +1 -0
  236. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js +2 -0
  237. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -0
  238. package/dist/components/epl/EplTree.js +2 -0
  239. package/dist/components/epl/EplTree.js.map +1 -0
  240. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js +2 -0
  241. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js.map +1 -0
  242. package/dist/components/epl/EplVarTable.js +2 -0
  243. package/dist/components/epl/EplVarTable.js.map +1 -0
  244. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js +2 -0
  245. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  246. package/dist/components/epl/numbering.js +2 -0
  247. package/dist/components/epl/numbering.js.map +1 -0
  248. package/dist/components/icons/Icon.js +2 -0
  249. package/dist/components/icons/Icon.js.map +1 -0
  250. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js +2 -0
  251. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js.map +1 -0
  252. package/dist/components/icons/iconPaths.js +2 -0
  253. package/dist/components/icons/iconPaths.js.map +1 -0
  254. package/dist/components/optionalViewer.js +2 -0
  255. package/dist/components/optionalViewer.js.map +1 -0
  256. package/dist/components/ui/ClampBlock.js +2 -0
  257. package/dist/components/ui/ClampBlock.js.map +1 -0
  258. package/dist/composables/useElementNavigation.d.ts +21 -0
  259. package/dist/composables/useElementNavigation.js +63 -0
  260. package/dist/composables/useElementNavigation.js.map +1 -0
  261. package/dist/composables/useFileViewer.js +2 -0
  262. package/dist/composables/useFileViewer.js.map +1 -0
  263. package/dist/composables/useLightbox.js +2 -0
  264. package/dist/composables/useLightbox.js.map +1 -0
  265. package/dist/composables/useLogRules.js +2 -0
  266. package/dist/composables/useLogRules.js.map +1 -0
  267. package/dist/lib/aes.js +2 -0
  268. package/dist/lib/aes.js.map +1 -0
  269. package/dist/lib/appx.js +2 -0
  270. package/dist/lib/appx.js.map +1 -0
  271. package/dist/lib/archiveHtml.d.ts +49 -0
  272. package/dist/lib/archiveHtml.js +377 -0
  273. package/dist/lib/archiveHtml.js.map +1 -0
  274. package/dist/lib/asn1.js +2 -0
  275. package/dist/lib/asn1.js.map +1 -0
  276. package/dist/lib/binobj.js +2 -0
  277. package/dist/lib/binobj.js.map +1 -0
  278. package/dist/lib/brotli.js +2 -0
  279. package/dist/lib/brotli.js.map +1 -0
  280. package/dist/lib/bzip2.js +2 -0
  281. package/dist/lib/bzip2.js.map +1 -0
  282. package/dist/lib/classfile.js +2 -0
  283. package/dist/lib/classfile.js.map +1 -0
  284. package/dist/lib/codePreview.js +2 -0
  285. package/dist/lib/codePreview.js.map +1 -0
  286. package/dist/lib/codefold.js +2 -0
  287. package/dist/lib/codefold.js.map +1 -0
  288. package/dist/lib/codeformat.js +2 -0
  289. package/dist/lib/codeformat.js.map +1 -0
  290. package/dist/lib/csv.js +2 -0
  291. package/dist/lib/csv.js.map +1 -0
  292. package/dist/lib/des.js +2 -0
  293. package/dist/lib/des.js.map +1 -0
  294. package/dist/lib/diagram.js +2 -0
  295. package/dist/lib/diagram.js.map +1 -0
  296. package/dist/lib/dicom.js +2 -0
  297. package/dist/lib/dicom.js.map +1 -0
  298. package/dist/lib/dicomCodecs.js +2 -0
  299. package/dist/lib/dicomCodecs.js.map +1 -0
  300. package/dist/lib/dicomRender.js +2 -0
  301. package/dist/lib/dicomRender.js.map +1 -0
  302. package/dist/lib/docxNavigation.d.ts +31 -0
  303. package/dist/lib/docxNavigation.js +65 -0
  304. package/dist/lib/docxNavigation.js.map +1 -0
  305. package/dist/lib/dxf.js +2 -0
  306. package/dist/lib/dxf.js.map +1 -0
  307. package/dist/lib/eml.js +2 -0
  308. package/dist/lib/eml.js.map +1 -0
  309. package/dist/lib/encoding.js +2 -0
  310. package/dist/lib/encoding.js.map +1 -0
  311. package/dist/lib/epl/code/codeDataParser.js +2 -0
  312. package/dist/lib/epl/code/codeDataParser.js.map +1 -0
  313. package/dist/lib/epl/code/codeLines.js +2 -0
  314. package/dist/lib/epl/code/codeLines.js.map +1 -0
  315. package/dist/lib/epl/code/textCode.js +2 -0
  316. package/dist/lib/epl/code/textCode.js.map +1 -0
  317. package/dist/lib/epl/eplDocument.js +2 -0
  318. package/dist/lib/epl/eplDocument.js.map +1 -0
  319. package/dist/lib/epl/imageList.js +2 -0
  320. package/dist/lib/epl/imageList.js.map +1 -0
  321. package/dist/lib/epl/index.js +2 -0
  322. package/dist/lib/epl/index.js.map +1 -0
  323. package/dist/lib/epl/io/binaryReader.js +2 -0
  324. package/dist/lib/epl/io/binaryReader.js.map +1 -0
  325. package/dist/lib/epl/io/encoding.js +2 -0
  326. package/dist/lib/epl/io/encoding.js.map +1 -0
  327. package/dist/lib/epl/model/eplSystemId.js +2 -0
  328. package/dist/lib/epl/model/eplSystemId.js.map +1 -0
  329. package/dist/lib/epl/names/eplLibData.js +2 -0
  330. package/dist/lib/epl/names/eplLibData.js.map +1 -0
  331. package/dist/lib/epl/names/idToNameMap.js +2 -0
  332. package/dist/lib/epl/names/idToNameMap.js.map +1 -0
  333. package/dist/lib/epl/names/libNameProvider.js +2 -0
  334. package/dist/lib/epl/names/libNameProvider.js.map +1 -0
  335. package/dist/lib/epl/program.js +2 -0
  336. package/dist/lib/epl/program.js.map +1 -0
  337. package/dist/lib/epl/projectFileReader.js +2 -0
  338. package/dist/lib/epl/projectFileReader.js.map +1 -0
  339. package/dist/lib/fb2.js +2 -0
  340. package/dist/lib/fb2.js.map +1 -0
  341. package/dist/lib/fileType.js +2 -0
  342. package/dist/lib/fileType.js.map +1 -0
  343. package/dist/lib/fonts.js +2 -0
  344. package/dist/lib/fonts.js.map +1 -0
  345. package/dist/lib/format.js +2 -0
  346. package/dist/lib/format.js.map +1 -0
  347. package/dist/lib/frontmatter.js +2 -0
  348. package/dist/lib/frontmatter.js.map +1 -0
  349. package/dist/lib/geo.js +2 -0
  350. package/dist/lib/geo.js.map +1 -0
  351. package/dist/lib/har.js +2 -0
  352. package/dist/lib/har.js.map +1 -0
  353. package/dist/lib/highlight.js +2 -0
  354. package/dist/lib/highlight.js.map +1 -0
  355. package/dist/lib/highlightLangs.js +2 -0
  356. package/dist/lib/highlightLangs.js.map +1 -0
  357. package/dist/lib/hprof.js +2 -0
  358. package/dist/lib/hprof.js.map +1 -0
  359. package/dist/lib/ipynb.js +2 -0
  360. package/dist/lib/ipynb.js.map +1 -0
  361. package/dist/lib/jks.js +2 -0
  362. package/dist/lib/jks.js.map +1 -0
  363. package/dist/lib/jsonl.js +2 -0
  364. package/dist/lib/jsonl.js.map +1 -0
  365. package/dist/lib/lrc.js +2 -0
  366. package/dist/lib/lrc.js.map +1 -0
  367. package/dist/lib/lzma.js +2 -0
  368. package/dist/lib/lzma.js.map +1 -0
  369. package/dist/lib/markdown/blocks.js +2 -0
  370. package/dist/lib/markdown/blocks.js.map +1 -0
  371. package/dist/lib/markdown/inline.js +2 -0
  372. package/dist/lib/markdown/inline.js.map +1 -0
  373. package/dist/lib/markdown/math.js +2 -0
  374. package/dist/lib/markdown/math.js.map +1 -0
  375. package/dist/lib/markdown/segmentize.js +2 -0
  376. package/dist/lib/markdown/segmentize.js.map +1 -0
  377. package/dist/lib/markdown/styles.js +2 -0
  378. package/dist/lib/markdown/styles.js.map +1 -0
  379. package/dist/lib/mht.js +2 -0
  380. package/dist/lib/mht.js.map +1 -0
  381. package/dist/lib/midi.js +2 -0
  382. package/dist/lib/midi.js.map +1 -0
  383. package/dist/lib/mime.js +2 -0
  384. package/dist/lib/mime.js.map +1 -0
  385. package/dist/lib/mindmap.js +2 -0
  386. package/dist/lib/mindmap.js.map +1 -0
  387. package/dist/lib/mobi.js +2 -0
  388. package/dist/lib/mobi.js.map +1 -0
  389. package/dist/lib/msi.js +2 -0
  390. package/dist/lib/msi.js.map +1 -0
  391. package/dist/lib/optionalDep.js +2 -0
  392. package/dist/lib/optionalDep.js.map +1 -0
  393. package/dist/lib/pkcs12.js +2 -0
  394. package/dist/lib/pkcs12.js.map +1 -0
  395. package/dist/lib/pptxNavigation.d.ts +7 -0
  396. package/dist/lib/pptxNavigation.js +29 -0
  397. package/dist/lib/pptxNavigation.js.map +1 -0
  398. package/dist/lib/qmc.js +2 -0
  399. package/dist/lib/qmc.js.map +1 -0
  400. package/dist/lib/rar.js +2 -0
  401. package/dist/lib/rar.js.map +1 -0
  402. package/dist/lib/rawPreview.js +2 -0
  403. package/dist/lib/rawPreview.js.map +1 -0
  404. package/dist/lib/rc2.js +2 -0
  405. package/dist/lib/rc2.js.map +1 -0
  406. package/dist/lib/resolveInput.js +2 -0
  407. package/dist/lib/resolveInput.js.map +1 -0
  408. package/dist/lib/rtf.js +2 -0
  409. package/dist/lib/rtf.js.map +1 -0
  410. package/dist/lib/sevenZip.js +2 -0
  411. package/dist/lib/sevenZip.js.map +1 -0
  412. package/dist/lib/sha1.js +2 -0
  413. package/dist/lib/sha1.js.map +1 -0
  414. package/dist/lib/sha256.js +2 -0
  415. package/dist/lib/sha256.js.map +1 -0
  416. package/dist/lib/speechAudio.js +2 -0
  417. package/dist/lib/speechAudio.js.map +1 -0
  418. package/dist/lib/sqlite.js +2 -0
  419. package/dist/lib/sqlite.js.map +1 -0
  420. package/dist/lib/subtitle.js +2 -0
  421. package/dist/lib/subtitle.js.map +1 -0
  422. package/dist/lib/tarball.js +2 -0
  423. package/dist/lib/tarball.js.map +1 -0
  424. package/dist/lib/viewerFormats.js +2 -0
  425. package/dist/lib/viewerFormats.js.map +1 -0
  426. package/dist/lib/viewerKind.js +2 -0
  427. package/dist/lib/viewerKind.js.map +1 -0
  428. package/dist/lib/x509.js +2 -0
  429. package/dist/lib/x509.js.map +1 -0
  430. package/dist/lib/xlsx.js +2 -0
  431. package/dist/lib/xlsx.js.map +1 -0
  432. package/dist/lib/xlsxCharts.js +2 -0
  433. package/dist/lib/xlsxCharts.js.map +1 -0
  434. package/dist/lib/xmind.js +2 -0
  435. package/dist/lib/xmind.js.map +1 -0
  436. package/dist/lib/zip.js +2 -0
  437. package/dist/lib/zip.js.map +1 -0
  438. package/dist/style.css +1 -1
  439. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotebookViewer.js","names":[],"sources":["../../src/components/NotebookViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { parseNotebook, type NbCell } from '@/lib/ipynb'\nimport { highlightCode } from '@/lib/highlight'\nimport { formatBytes } from '@/lib/format'\nimport MarkdownText from '@/components/MarkdownText'\n\n/**\n * Side-viewer Jupyter Notebook (.ipynb) renderer. Fetches the notebook bytes from the download URL and parses\n * the JSON in-browser (see lib/ipynb.ts), then walks the cells: markdown cells reuse the app's markdown engine,\n * code cells reuse highlight.js, and each output is shown by its best-fit type (stream / text / image / rich\n * HTML / error). Mirrors the other byte-fetching viewers (DICOM/DOCX/…): a real download URL in, a self-owned\n * fetch + render.\n *\n * Safety: a cell's rich `text/html` output is hostile markup (it comes from arbitrary kernels), so it renders\n * the SAME way the panel's HTML preview does — in a fully sandboxed iframe (opaque origin, no `allow-scripts`)\n * whose markup carries an injected `script-src 'none'` CSP, so NO upstream script runs. (Interactive widgets\n * like Plotly therefore won't execute — download the notebook to run it; static tables/plots render fine.)\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst cells = ref<NbCell[]>([])\nconst language = ref('python')\n\n// Notebooks with many embedded plot images can balloon; gate very large ones behind a confirm (in-memory parse).\nconst SIZE_LIMIT = 20 * 1024 * 1024\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\n// Same hardening as FileViewerPanel's HTML preview — kill all script/plugin/base/form vectors. Multiple CSPs\n// intersect, so a fragment's own markup can't loosen this.\nconst NO_SCRIPT_CSP =\n `<meta http-equiv=\"Content-Security-Policy\" ` +\n `content=\"script-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'\">`\n\nfunction safeHtml(html: string): string {\n return (\n `<!doctype html><html><head><meta charset=\"utf-8\">${NO_SCRIPT_CSP}` +\n `<style>body{margin:0;padding:8px;font:13px system-ui,-apple-system,sans-serif;color:#222}` +\n `table{border-collapse:collapse}th,td{border:1px solid #ddd;padding:4px 8px}</style></head>` +\n `<body>${html}</body></html>`\n )\n}\n\nfunction highlight(source: string): string {\n try {\n return highlightCode(source, language.value)\n } catch {\n return escapeHtml(source)\n }\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const parsed = parseNotebook(await res.text())\n cells.value = parsed.cells\n language.value = parsed.language\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction load(): void {\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nwatch(() => props.url, load, { immediate: true })\n</script>\n\n<template>\n <div class=\"nb\">\n <p v-if=\"status === 'loading'\" class=\"nb__msg\">正在解析 Notebook…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"nb__msg nb__gate\">\n <p>该 Notebook 较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"nb__btn\" @click=\"doLoad\">解析并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"nb__msg nb__msg--error\">\n <p>解析失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"nb__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <div v-else class=\"nb__scroll\">\n <div v-for=\"(cell, i) in cells\" :key=\"i\" class=\"nb__cell\">\n <!-- markdown cell: the app's markdown engine -->\n <div v-if=\"cell.type === 'markdown'\" class=\"nb__md\">\n <MarkdownText :text=\"cell.source\" kp=\"nb\" />\n </div>\n\n <!-- raw cell: verbatim -->\n <pre v-else-if=\"cell.type === 'raw'\" class=\"nb__raw\">{{ cell.source }}</pre>\n\n <!-- code cell: prompt + highlighted source + outputs -->\n <div v-else class=\"nb__code\">\n <div class=\"nb__in\">\n <span class=\"nb__prompt\">[{{ cell.executionCount ?? ' ' }}]</span>\n <pre class=\"nb__src\"><code class=\"hljs\" v-html=\"highlight(cell.source)\" /></pre>\n </div>\n\n <div\n v-for=\"(out, j) in cell.outputs\"\n :key=\"j\"\n class=\"nb__out\"\n :class=\"{\n 'nb__out--err': out.kind === 'error' || (out.kind === 'stream' && out.isError),\n }\"\n >\n <pre v-if=\"out.kind === 'stream'\" class=\"nb__stream\">{{ out.text }}</pre>\n <pre v-else-if=\"out.kind === 'text' || out.kind === 'json'\" class=\"nb__text\">{{\n out.text\n }}</pre>\n <pre v-else-if=\"out.kind === 'error'\" class=\"nb__errtext\">{{ out.text }}</pre>\n <div v-else-if=\"out.kind === 'markdown'\" class=\"nb__md nb__md--out\">\n <MarkdownText :text=\"out.text\" :kp=\"`nbo-${i}-${j}`\" />\n </div>\n <img v-else-if=\"out.kind === 'image'\" class=\"nb__img\" :src=\"out.src\" alt=\"输出图像\" />\n <iframe\n v-else-if=\"out.kind === 'html'\"\n class=\"nb__html\"\n :srcdoc=\"safeHtml(out.html)\"\n title=\"Notebook 富文本输出\"\n sandbox=\"\"\n loading=\"lazy\"\n referrerpolicy=\"no-referrer\"\n />\n </div>\n </div>\n </div>\n\n <p v-if=\"!cells.length\" class=\"nb__msg\">(空 Notebook)</p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.nb {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.nb__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.nb__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.nb__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.nb__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.nb__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.nb__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 14px 16px 70px;\n}\n.nb__cell {\n margin-bottom: 14px;\n}\n\n/* markdown cell — lean on the shared markdown typography; just constrain width. */\n.nb__md {\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--text);\n word-break: break-word;\n}\n.nb__md--out {\n padding: 6px 0 0;\n}\n.nb__raw {\n margin: 0;\n padding: 10px 12px;\n font-family: var(--font-mono);\n font-size: 12.5px;\n line-height: 1.5;\n color: var(--muted);\n background: var(--code-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* code cell — claude.ai artifact look: a prompt gutter + highlighted source, outputs stacked beneath. */\n.nb__code {\n border: 1px solid var(--border);\n border-radius: 10px;\n overflow: hidden;\n background: var(--code-bg);\n}\n.nb__in {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 10px;\n}\n.nb__prompt {\n flex-shrink: 0;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--accent);\n user-select: none;\n padding-top: 1px;\n}\n.nb__src {\n flex: 1 1 auto;\n min-width: 0;\n margin: 0;\n overflow-x: auto;\n font-family: var(--font-mono);\n font-size: 12.5px;\n line-height: 1.55;\n color: var(--text);\n white-space: pre;\n tab-size: 4;\n}\n.nb__src code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n\n.nb__out {\n border-top: 1px solid var(--border);\n padding: 8px 10px 8px 38px;\n background: var(--surface);\n}\n.nb__out--err {\n background: color-mix(in srgb, var(--danger) 7%, var(--surface));\n}\n.nb__stream,\n.nb__text,\n.nb__errtext {\n margin: 0;\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--text);\n}\n.nb__errtext {\n color: var(--danger);\n}\n.nb__img {\n display: block;\n max-width: 100%;\n height: auto;\n border-radius: 4px;\n background: #fff;\n}\n.nb__html {\n display: block;\n width: 100%;\n height: 300px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
@@ -123,3 +123,5 @@ var _ = { class: "nb" }, v = {
123
123
  });
124
124
  //#endregion
125
125
  export { R as default };
126
+
127
+ //# sourceMappingURL=NotebookViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotebookViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/NotebookViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { parseNotebook, type NbCell } from '@/lib/ipynb'\nimport { highlightCode } from '@/lib/highlight'\nimport { formatBytes } from '@/lib/format'\nimport MarkdownText from '@/components/MarkdownText'\n\n/**\n * Side-viewer Jupyter Notebook (.ipynb) renderer. Fetches the notebook bytes from the download URL and parses\n * the JSON in-browser (see lib/ipynb.ts), then walks the cells: markdown cells reuse the app's markdown engine,\n * code cells reuse highlight.js, and each output is shown by its best-fit type (stream / text / image / rich\n * HTML / error). Mirrors the other byte-fetching viewers (DICOM/DOCX/…): a real download URL in, a self-owned\n * fetch + render.\n *\n * Safety: a cell's rich `text/html` output is hostile markup (it comes from arbitrary kernels), so it renders\n * the SAME way the panel's HTML preview does — in a fully sandboxed iframe (opaque origin, no `allow-scripts`)\n * whose markup carries an injected `script-src 'none'` CSP, so NO upstream script runs. (Interactive widgets\n * like Plotly therefore won't execute — download the notebook to run it; static tables/plots render fine.)\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst cells = ref<NbCell[]>([])\nconst language = ref('python')\n\n// Notebooks with many embedded plot images can balloon; gate very large ones behind a confirm (in-memory parse).\nconst SIZE_LIMIT = 20 * 1024 * 1024\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\n// Same hardening as FileViewerPanel's HTML preview — kill all script/plugin/base/form vectors. Multiple CSPs\n// intersect, so a fragment's own markup can't loosen this.\nconst NO_SCRIPT_CSP =\n `<meta http-equiv=\"Content-Security-Policy\" ` +\n `content=\"script-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'\">`\n\nfunction safeHtml(html: string): string {\n return (\n `<!doctype html><html><head><meta charset=\"utf-8\">${NO_SCRIPT_CSP}` +\n `<style>body{margin:0;padding:8px;font:13px system-ui,-apple-system,sans-serif;color:#222}` +\n `table{border-collapse:collapse}th,td{border:1px solid #ddd;padding:4px 8px}</style></head>` +\n `<body>${html}</body></html>`\n )\n}\n\nfunction highlight(source: string): string {\n try {\n return highlightCode(source, language.value)\n } catch {\n return escapeHtml(source)\n }\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const parsed = parseNotebook(await res.text())\n cells.value = parsed.cells\n language.value = parsed.language\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction load(): void {\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nwatch(() => props.url, load, { immediate: true })\n</script>\n\n<template>\n <div class=\"nb\">\n <p v-if=\"status === 'loading'\" class=\"nb__msg\">正在解析 Notebook…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"nb__msg nb__gate\">\n <p>该 Notebook 较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"nb__btn\" @click=\"doLoad\">解析并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"nb__msg nb__msg--error\">\n <p>解析失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"nb__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <div v-else class=\"nb__scroll\">\n <div v-for=\"(cell, i) in cells\" :key=\"i\" class=\"nb__cell\">\n <!-- markdown cell: the app's markdown engine -->\n <div v-if=\"cell.type === 'markdown'\" class=\"nb__md\">\n <MarkdownText :text=\"cell.source\" kp=\"nb\" />\n </div>\n\n <!-- raw cell: verbatim -->\n <pre v-else-if=\"cell.type === 'raw'\" class=\"nb__raw\">{{ cell.source }}</pre>\n\n <!-- code cell: prompt + highlighted source + outputs -->\n <div v-else class=\"nb__code\">\n <div class=\"nb__in\">\n <span class=\"nb__prompt\">[{{ cell.executionCount ?? ' ' }}]</span>\n <pre class=\"nb__src\"><code class=\"hljs\" v-html=\"highlight(cell.source)\" /></pre>\n </div>\n\n <div\n v-for=\"(out, j) in cell.outputs\"\n :key=\"j\"\n class=\"nb__out\"\n :class=\"{\n 'nb__out--err': out.kind === 'error' || (out.kind === 'stream' && out.isError),\n }\"\n >\n <pre v-if=\"out.kind === 'stream'\" class=\"nb__stream\">{{ out.text }}</pre>\n <pre v-else-if=\"out.kind === 'text' || out.kind === 'json'\" class=\"nb__text\">{{\n out.text\n }}</pre>\n <pre v-else-if=\"out.kind === 'error'\" class=\"nb__errtext\">{{ out.text }}</pre>\n <div v-else-if=\"out.kind === 'markdown'\" class=\"nb__md nb__md--out\">\n <MarkdownText :text=\"out.text\" :kp=\"`nbo-${i}-${j}`\" />\n </div>\n <img v-else-if=\"out.kind === 'image'\" class=\"nb__img\" :src=\"out.src\" alt=\"输出图像\" />\n <iframe\n v-else-if=\"out.kind === 'html'\"\n class=\"nb__html\"\n :srcdoc=\"safeHtml(out.html)\"\n title=\"Notebook 富文本输出\"\n sandbox=\"\"\n loading=\"lazy\"\n referrerpolicy=\"no-referrer\"\n />\n </div>\n </div>\n </div>\n\n <p v-if=\"!cells.length\" class=\"nb__msg\">(空 Notebook)</p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.nb {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.nb__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.nb__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.nb__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.nb__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.nb__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.nb__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 14px 16px 70px;\n}\n.nb__cell {\n margin-bottom: 14px;\n}\n\n/* markdown cell — lean on the shared markdown typography; just constrain width. */\n.nb__md {\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--text);\n word-break: break-word;\n}\n.nb__md--out {\n padding: 6px 0 0;\n}\n.nb__raw {\n margin: 0;\n padding: 10px 12px;\n font-family: var(--font-mono);\n font-size: 12.5px;\n line-height: 1.5;\n color: var(--muted);\n background: var(--code-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* code cell — claude.ai artifact look: a prompt gutter + highlighted source, outputs stacked beneath. */\n.nb__code {\n border: 1px solid var(--border);\n border-radius: 10px;\n overflow: hidden;\n background: var(--code-bg);\n}\n.nb__in {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 10px;\n}\n.nb__prompt {\n flex-shrink: 0;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--accent);\n user-select: none;\n padding-top: 1px;\n}\n.nb__src {\n flex: 1 1 auto;\n min-width: 0;\n margin: 0;\n overflow-x: auto;\n font-family: var(--font-mono);\n font-size: 12.5px;\n line-height: 1.55;\n color: var(--text);\n white-space: pre;\n tab-size: 4;\n}\n.nb__src code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n\n.nb__out {\n border-top: 1px solid var(--border);\n padding: 8px 10px 8px 38px;\n background: var(--surface);\n}\n.nb__out--err {\n background: color-mix(in srgb, var(--danger) 7%, var(--surface));\n}\n.nb__stream,\n.nb__text,\n.nb__errtext {\n margin: 0;\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--text);\n}\n.nb__errtext {\n color: var(--danger);\n}\n.nb__img {\n display: block;\n max-width: 100%;\n height: auto;\n border-radius: 4px;\n background: #fff;\n}\n.nb__html {\n display: block;\n width: 100%;\n height: 300px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: #fff;\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./PdfViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-2a30ef87"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=PdfViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdfViewer.js","names":[],"sources":["../../src/components/PdfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentProxy, RenderTask } from 'pdfjs-dist'\n// pdfjs runs its parser/rasterizer in a web worker. Vite's `?url` emits the real worker asset and returns a\n// servable URL (root-relative in the same-origin prod build, a dev URL under `pnpm dev`); we point pdfjs at\n// it. The `.mjs` is served as text/javascript by Ktor (see AGENTS.md) so the module worker loads in prod.\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n\n/**\n * Side-viewer PDF (`.pdf`) renderer. Fetches the document bytes from the download URL and rasterizes each\n * page to a <canvas> entirely in-browser via pdfjs-dist (https://github.com/mozilla/pdf.js) — a vertically\n * scrollable list of pages (white sheets on a neutral \"desk\", like the docx/pptx viewers). No CDN / network\n * beyond the file fetch: pdfjs bundles its own worker, fetched lazily as a same-origin asset (see above).\n *\n * Two entry shapes mirror the other viewers (docx/xlsx/pptx/zip): a real download URL for a held attachment,\n * or a blob URL minted by the archive browsers from already-extracted bytes. Pages re-rasterize when the pane\n * is resized (debounced) so text stays crisp at the new width.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .pdf bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst pageCount = ref(0)\n// The scroll container the page canvases render into. Always mounted (the loading/error states overlay it).\nconst docEl = ref<HTMLElement | null>(null)\n\n// Live document proxy + in-flight render tasks — torn down on re-render / unmount (the worker and page\n// rasterizers don't GC on their own).\nlet pdfDoc: PDFDocumentProxy | null = null\nlet renderTasks: RenderTask[] = []\n// Bumped on every (re)render so a stale async pass (older file, or a superseded resize) can bail out.\nlet renderToken = 0\n// Cap the backing-store resolution: device pixels × this, clamped, keeps big/high-DPI pages from minting\n// multi-hundred-MB canvases while staying crisp.\nconst MAX_SCALE = 2\n\nfunction cancelRenders(): void {\n for (const t of renderTasks) {\n try {\n t.cancel()\n } catch {\n /* already settled — ignore */\n }\n }\n renderTasks = []\n}\n\nfunction disposeDoc(): void {\n cancelRenders()\n if (pdfDoc) {\n try {\n pdfDoc.destroy()\n } catch {\n /* already torn down — ignore */\n }\n pdfDoc = null\n }\n}\n\n// Rasterize every page of the live document into the container at its current width. Called on load and on\n// resize; guarded by `renderToken` so an outdated pass (file switched, or a newer resize started) stops early.\nasync function renderPages(): Promise<void> {\n const el = docEl.value\n const doc = pdfDoc\n if (!el || !doc) return\n const token = ++renderToken\n cancelRenders()\n el.innerHTML = ''\n // Available CSS width for a page (minus the container's horizontal padding). Floor at a sane minimum so a\n // momentarily-zero clientWidth (not yet laid out) doesn't render a 0-px canvas.\n const style = getComputedStyle(el)\n const padX = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)\n const cssWidth = Math.max(120, el.clientWidth - padX)\n const dpr = Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, MAX_SCALE)\n for (let n = 1; n <= doc.numPages; n++) {\n let page\n try {\n page = await doc.getPage(n)\n } catch {\n if (token !== renderToken) return\n continue // skip an unreadable page rather than failing the whole doc\n }\n if (token !== renderToken) return\n const base = page.getViewport({ scale: 1 })\n const cssScale = cssWidth / base.width\n const viewport = page.getViewport({ scale: cssScale * dpr })\n const canvas = document.createElement('canvas')\n canvas.className = 'pf__page'\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n // Display at the CSS width; the extra backing-store pixels (× dpr) make text sharp on HiDPI screens.\n canvas.style.width = `${Math.round(cssWidth)}px`\n canvas.style.height = `${Math.round(cssWidth * (base.height / base.width))}px`\n if (!canvas.getContext('2d')) continue // no 2D context available — skip rather than crash the render\n el.appendChild(canvas)\n // Pass `canvas` (the preferred param in pdfjs v5); it derives the 2D context itself.\n const task = page.render({ canvas, viewport })\n renderTasks.push(task)\n try {\n await task.promise\n } catch {\n // RenderingCancelledException on supersede/unmount is expected — only a live token cares about failures.\n if (token !== renderToken) return\n }\n if (token !== renderToken) return\n }\n}\n\nasync function load(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n disposeDoc()\n el.innerHTML = ''\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer load may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const doc = await pdfjsLib.getDocument({ data: new Uint8Array(buf) }).promise\n if (props.url !== url || docEl.value !== el) {\n try {\n doc.destroy()\n } catch {\n /* ignore */\n }\n return\n }\n pdfDoc = doc\n pageCount.value = doc.numPages\n await renderPages()\n if (props.url !== url || docEl.value !== el) return\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// Re-rasterize on width changes (debounced) so pages stay crisp as the pane is dragged wider/narrower. Only\n// the width matters; height changes don't affect page scale.\nlet resizeTimer: ReturnType<typeof setTimeout> | undefined\nlet lastWidth = 0\nlet ro: ResizeObserver | null = null\nfunction onResize(): void {\n const el = docEl.value\n if (!el || !pdfDoc) return\n const w = el.clientWidth\n if (Math.abs(w - lastWidth) < 8) return // ignore sub-pixel / negligible jitter\n lastWidth = w\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n if (pdfDoc) void renderPages()\n }, 180)\n}\n\nonMounted(() => {\n load()\n if (typeof ResizeObserver !== 'undefined' && docEl.value) {\n lastWidth = docEl.value.clientWidth\n ro = new ResizeObserver(onResize)\n ro.observe(docEl.value)\n }\n})\nwatch(() => props.url, load)\nonBeforeUnmount(() => {\n clearTimeout(resizeTimer)\n ro?.disconnect()\n renderToken++ // invalidate any in-flight render pass\n disposeDoc()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pf\">\n <!-- Always mounted so the renderer has a sized, scrollable container; states overlay it. -->\n <div ref=\"docEl\" class=\"pf__doc\" />\n <div v-if=\"status === 'loading'\" class=\"pf__overlay\">\n <p class=\"pf__msg\">正在渲染 PDF…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pf__overlay\">\n <div class=\"pf__msg pf__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pf__retry\" @click=\"load\">重试</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pf {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n/* Scroll region holding the rendered pages — white sheets on a neutral \"desk\", like the docx/pptx viewers. */\n.pf__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n padding: 18px 16px 40px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n}\n/* Each page canvas: a white sheet with a soft shadow. Width is set inline (the rasterized CSS width); the\n * canvas paints its own white, so no background needed here. */\n.pf__doc :deep(.pf__page) {\n display: block;\n max-width: 100%;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n border-radius: 2px;\n background: #fff;\n}\n/* Overlay covers the (empty) container while loading or on error, without unmounting it. */\n.pf__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n.pf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.pf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.pf__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.pf__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n</style>\n"],"mappings":""}
@@ -112,3 +112,5 @@ var f = { class: "pf" }, p = {
112
112
  });
113
113
  //#endregion
114
114
  export { _ as default };
115
+
116
+ //# sourceMappingURL=PdfViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdfViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PdfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentProxy, RenderTask } from 'pdfjs-dist'\n// pdfjs runs its parser/rasterizer in a web worker. Vite's `?url` emits the real worker asset and returns a\n// servable URL (root-relative in the same-origin prod build, a dev URL under `pnpm dev`); we point pdfjs at\n// it. The `.mjs` is served as text/javascript by Ktor (see AGENTS.md) so the module worker loads in prod.\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n\n/**\n * Side-viewer PDF (`.pdf`) renderer. Fetches the document bytes from the download URL and rasterizes each\n * page to a <canvas> entirely in-browser via pdfjs-dist (https://github.com/mozilla/pdf.js) — a vertically\n * scrollable list of pages (white sheets on a neutral \"desk\", like the docx/pptx viewers). No CDN / network\n * beyond the file fetch: pdfjs bundles its own worker, fetched lazily as a same-origin asset (see above).\n *\n * Two entry shapes mirror the other viewers (docx/xlsx/pptx/zip): a real download URL for a held attachment,\n * or a blob URL minted by the archive browsers from already-extracted bytes. Pages re-rasterize when the pane\n * is resized (debounced) so text stays crisp at the new width.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .pdf bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst pageCount = ref(0)\n// The scroll container the page canvases render into. Always mounted (the loading/error states overlay it).\nconst docEl = ref<HTMLElement | null>(null)\n\n// Live document proxy + in-flight render tasks — torn down on re-render / unmount (the worker and page\n// rasterizers don't GC on their own).\nlet pdfDoc: PDFDocumentProxy | null = null\nlet renderTasks: RenderTask[] = []\n// Bumped on every (re)render so a stale async pass (older file, or a superseded resize) can bail out.\nlet renderToken = 0\n// Cap the backing-store resolution: device pixels × this, clamped, keeps big/high-DPI pages from minting\n// multi-hundred-MB canvases while staying crisp.\nconst MAX_SCALE = 2\n\nfunction cancelRenders(): void {\n for (const t of renderTasks) {\n try {\n t.cancel()\n } catch {\n /* already settled — ignore */\n }\n }\n renderTasks = []\n}\n\nfunction disposeDoc(): void {\n cancelRenders()\n if (pdfDoc) {\n try {\n pdfDoc.destroy()\n } catch {\n /* already torn down — ignore */\n }\n pdfDoc = null\n }\n}\n\n// Rasterize every page of the live document into the container at its current width. Called on load and on\n// resize; guarded by `renderToken` so an outdated pass (file switched, or a newer resize started) stops early.\nasync function renderPages(): Promise<void> {\n const el = docEl.value\n const doc = pdfDoc\n if (!el || !doc) return\n const token = ++renderToken\n cancelRenders()\n el.innerHTML = ''\n // Available CSS width for a page (minus the container's horizontal padding). Floor at a sane minimum so a\n // momentarily-zero clientWidth (not yet laid out) doesn't render a 0-px canvas.\n const style = getComputedStyle(el)\n const padX = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)\n const cssWidth = Math.max(120, el.clientWidth - padX)\n const dpr = Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, MAX_SCALE)\n for (let n = 1; n <= doc.numPages; n++) {\n let page\n try {\n page = await doc.getPage(n)\n } catch {\n if (token !== renderToken) return\n continue // skip an unreadable page rather than failing the whole doc\n }\n if (token !== renderToken) return\n const base = page.getViewport({ scale: 1 })\n const cssScale = cssWidth / base.width\n const viewport = page.getViewport({ scale: cssScale * dpr })\n const canvas = document.createElement('canvas')\n canvas.className = 'pf__page'\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n // Display at the CSS width; the extra backing-store pixels (× dpr) make text sharp on HiDPI screens.\n canvas.style.width = `${Math.round(cssWidth)}px`\n canvas.style.height = `${Math.round(cssWidth * (base.height / base.width))}px`\n if (!canvas.getContext('2d')) continue // no 2D context available — skip rather than crash the render\n el.appendChild(canvas)\n // Pass `canvas` (the preferred param in pdfjs v5); it derives the 2D context itself.\n const task = page.render({ canvas, viewport })\n renderTasks.push(task)\n try {\n await task.promise\n } catch {\n // RenderingCancelledException on supersede/unmount is expected — only a live token cares about failures.\n if (token !== renderToken) return\n }\n if (token !== renderToken) return\n }\n}\n\nasync function load(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n disposeDoc()\n el.innerHTML = ''\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer load may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const doc = await pdfjsLib.getDocument({ data: new Uint8Array(buf) }).promise\n if (props.url !== url || docEl.value !== el) {\n try {\n doc.destroy()\n } catch {\n /* ignore */\n }\n return\n }\n pdfDoc = doc\n pageCount.value = doc.numPages\n await renderPages()\n if (props.url !== url || docEl.value !== el) return\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// Re-rasterize on width changes (debounced) so pages stay crisp as the pane is dragged wider/narrower. Only\n// the width matters; height changes don't affect page scale.\nlet resizeTimer: ReturnType<typeof setTimeout> | undefined\nlet lastWidth = 0\nlet ro: ResizeObserver | null = null\nfunction onResize(): void {\n const el = docEl.value\n if (!el || !pdfDoc) return\n const w = el.clientWidth\n if (Math.abs(w - lastWidth) < 8) return // ignore sub-pixel / negligible jitter\n lastWidth = w\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n if (pdfDoc) void renderPages()\n }, 180)\n}\n\nonMounted(() => {\n load()\n if (typeof ResizeObserver !== 'undefined' && docEl.value) {\n lastWidth = docEl.value.clientWidth\n ro = new ResizeObserver(onResize)\n ro.observe(docEl.value)\n }\n})\nwatch(() => props.url, load)\nonBeforeUnmount(() => {\n clearTimeout(resizeTimer)\n ro?.disconnect()\n renderToken++ // invalidate any in-flight render pass\n disposeDoc()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pf\">\n <!-- Always mounted so the renderer has a sized, scrollable container; states overlay it. -->\n <div ref=\"docEl\" class=\"pf__doc\" />\n <div v-if=\"status === 'loading'\" class=\"pf__overlay\">\n <p class=\"pf__msg\">正在渲染 PDF…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pf__overlay\">\n <div class=\"pf__msg pf__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pf__retry\" @click=\"load\">重试</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pf {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n/* Scroll region holding the rendered pages — white sheets on a neutral \"desk\", like the docx/pptx viewers. */\n.pf__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n padding: 18px 16px 40px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n}\n/* Each page canvas: a white sheet with a soft shadow. Width is set inline (the rasterized CSS width); the\n * canvas paints its own white, so no background needed here. */\n.pf__doc :deep(.pf__page) {\n display: block;\n max-width: 100%;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n border-radius: 2px;\n background: #fff;\n}\n/* Overlay covers the (empty) container while loading or on error, without unmounting it. */\n.pf__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n.pf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.pf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.pf__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.pf__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import e from "../_virtual/_plugin-vue_export-helper.js";
2
+ import t from "./PptxNavigationPane.vue_vue_type_script_setup_true_lang.js";
3
+ /* empty css */
4
+ //#region src/components/PptxNavigationPane.vue
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-1de8828c"]]);
6
+ //#endregion
7
+ export { n as default };
8
+
9
+ //# sourceMappingURL=PptxNavigationPane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PptxNavigationPane.js","names":[],"sources":["../../src/components/PptxNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n nextTick,\n onBeforeUnmount,\n onMounted,\n useTemplateRef,\n watch,\n type ComponentPublicInstance,\n} from 'vue'\nimport type { PptxViewer, SlideHandle } from '@aiden0z/pptx-renderer'\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PptxNavigationItem } from '@/lib/pptxNavigation'\n\nconst props = defineProps<{\n viewer: PptxViewer\n items: readonly PptxNavigationItem[]\n activeIndex: number\n}>()\n\nconst emit = defineEmits<{ navigate: [index: number] }>()\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst listEl = useTemplateRef<HTMLElement>('list')\n\nconst thumbElements = new Map<number, HTMLElement>()\nconst thumbHandles = new Map<number, SlideHandle>()\nlet observer: IntersectionObserver | null = null\n\nfunction disposeThumbnails(): void {\n observer?.disconnect()\n observer = null\n for (const handle of thumbHandles.values()) handle.dispose()\n thumbHandles.clear()\n for (const element of thumbElements.values()) element.innerHTML = ''\n}\n\nfunction mountThumbnail(index: number, element: HTMLElement): void {\n if (thumbHandles.has(index)) return\n const handle = props.viewer.renderThumbnailToContainer(index, element, { width: 104 })\n if (!handle) return\n thumbHandles.set(index, handle)\n void handle.ready.catch(() => {\n if (thumbHandles.get(index) !== handle) return\n handle.dispose()\n thumbHandles.delete(index)\n element.innerHTML = ''\n })\n}\n\nasync function setupThumbnails(): Promise<void> {\n disposeThumbnails()\n if (collapsed.value) return\n await nextTick()\n const root = listEl.value\n if (!root) return\n\n if (typeof IntersectionObserver === 'undefined') {\n for (const [index, element] of thumbElements) mountThumbnail(index, element)\n return\n }\n\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue\n const element = entry.target as HTMLElement\n const index = Number(element.dataset.slideIndex)\n if (Number.isInteger(index)) mountThumbnail(index, element)\n observer?.unobserve(element)\n }\n },\n { root, rootMargin: '140px 0px' },\n )\n for (const element of thumbElements.values()) observer.observe(element)\n}\n\nfunction setThumbElement(index: number, value: Element | ComponentPublicInstance | null): void {\n const previous = thumbElements.get(index)\n if (previous && previous !== value) observer?.unobserve(previous)\n if (!(value instanceof HTMLElement)) {\n thumbElements.delete(index)\n return\n }\n thumbElements.set(index, value)\n observer?.observe(value)\n}\n\nwatch(() => props.viewer, setupThumbnails, { flush: 'post' })\nwatch(() => props.items, setupThumbnails, { flush: 'post' })\nwatch(collapsed, setupThumbnails, { flush: 'post' })\nwatch(\n () => props.activeIndex,\n async (index) => {\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>(`[data-nav-slide-index=\"${index}\"]`)\n ?.scrollIntoView({ block: 'nearest' })\n },\n)\n\nonMounted(setupThumbnails)\nonBeforeUnmount(disposeThumbnails)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"{ 'pn--collapsed': collapsed }\" aria-label=\"幻灯片导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开幻灯片导航\"\n title=\"展开幻灯片导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div>\n <strong class=\"pn__title\">幻灯片</strong>\n <span class=\"pn__count\">{{ items.length }} 张</span>\n </div>\n <button\n type=\"button\"\n class=\"pn__collapse\"\n aria-label=\"收起幻灯片导航\"\n title=\"收起幻灯片导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"list\" class=\"pn__list\" aria-label=\"幻灯片列表\">\n <button\n v-for=\"item in items\"\n :key=\"item.index\"\n type=\"button\"\n class=\"pn__item\"\n :class=\"{ 'pn__item--active': item.index === activeIndex }\"\n :data-nav-slide-index=\"item.index\"\n :aria-current=\"item.index === activeIndex ? 'page' : undefined\"\n :title=\"`${item.index + 1}. ${item.title}`\"\n @click=\"emit('navigate', item.index)\"\n >\n <span class=\"pn__number\">{{ item.index + 1 }}</span>\n <span class=\"pn__preview-wrap\">\n <span\n :ref=\"(value) => setThumbElement(item.index, value)\"\n class=\"pn__preview\"\n :data-slide-index=\"item.index\"\n aria-hidden=\"true\"\n />\n </span>\n <span class=\"pn__label\">{{ item.title }}</span>\n </button>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n flex: 0 0 142px;\n width: 142px;\n min-width: 0;\n display: flex;\n flex-direction: column;\n border-right: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n transition: flex-basis 160ms ease;\n}\n.pn--collapsed {\n flex-basis: 0;\n width: 0;\n border-right: 0;\n}\n.pn__header {\n min-height: 45px;\n padding: 9px 8px 8px 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n.pn__collapse,\n.pn__reopen {\n border: 0;\n color: var(--muted);\n background: transparent;\n cursor: pointer;\n}\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n place-items: center;\n border-radius: 7px;\n}\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n background: var(--surface);\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.pn__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 8px 7px 22px;\n scrollbar-width: thin;\n}\n.pn__item {\n position: relative;\n width: 100%;\n min-width: 0;\n margin: 0 0 8px;\n padding: 5px;\n display: block;\n border: 1px solid transparent;\n border-radius: 8px;\n color: var(--muted);\n background: transparent;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.pn__item--active {\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n border-radius: 5px;\n color: #fff;\n background: rgba(20, 20, 20, 0.68);\n font-size: 9.5px;\n font-weight: 650;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n.pn__item--active .pn__number {\n color: #fff;\n background: var(--accent);\n}\n.pn__preview-wrap {\n height: 76px;\n min-width: 0;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-2);\n border-radius: 4px;\n background: #fff;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n.pn__preview {\n width: 104px;\n min-height: 1px;\n display: block;\n overflow: hidden;\n pointer-events: none;\n}\n.pn__label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n font-size: 10.5px;\n line-height: 1.35;\n}\n\n@container (max-width: 500px) {\n .pn {\n flex-basis: 124px;\n width: 124px;\n }\n .pn--collapsed {\n flex-basis: 0;\n width: 0;\n }\n .pn__preview-wrap {\n height: 66px;\n }\n}\n</style>\n"],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { PptxViewer } from '@aiden0z/pptx-renderer';
2
+ import type { PptxNavigationItem } from '@/lib/pptxNavigation';
3
+ type __VLS_Props = {
4
+ viewer: PptxViewer;
5
+ items: readonly PptxNavigationItem[];
6
+ activeIndex: number;
7
+ };
8
+ type __VLS_ModelProps = {
9
+ 'collapsed'?: boolean;
10
+ };
11
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
12
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ navigate: (index: number) => any;
14
+ "update:collapsed": (value: boolean) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
+ onNavigate?: ((index: number) => any) | undefined;
17
+ "onUpdate:collapsed"?: ((value: boolean) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,120 @@
1
+ import e from "./icons/Icon.js";
2
+ import { Fragment as t, createElementBlock as n, createElementVNode as r, createVNode as i, defineComponent as a, mergeModels as o, nextTick as s, normalizeClass as c, onBeforeUnmount as l, onMounted as u, openBlock as d, renderList as f, toDisplayString as p, useModel as m, useTemplateRef as h, watch as g } from "vue";
3
+ //#region src/components/PptxNavigationPane.vue?vue&type=script&setup=true&lang.ts
4
+ var _ = { class: "pn__header" }, v = { class: "pn__count" }, y = {
5
+ ref: "list",
6
+ class: "pn__list",
7
+ "aria-label": "幻灯片列表"
8
+ }, b = [
9
+ "data-nav-slide-index",
10
+ "aria-current",
11
+ "title",
12
+ "onClick"
13
+ ], x = { class: "pn__number" }, S = { class: "pn__preview-wrap" }, C = ["data-slide-index"], w = { class: "pn__label" }, T = /*@__PURE__*/ a({
14
+ __name: "PptxNavigationPane",
15
+ props: /*@__PURE__*/ o({
16
+ viewer: {},
17
+ items: {},
18
+ activeIndex: {}
19
+ }, {
20
+ collapsed: {
21
+ type: Boolean,
22
+ default: !1
23
+ },
24
+ collapsedModifiers: {}
25
+ }),
26
+ emits: /*@__PURE__*/ o(["navigate"], ["update:collapsed"]),
27
+ setup(a, { emit: o }) {
28
+ let T = a, E = o, D = m(a, "collapsed"), O = h("list"), k = /* @__PURE__ */ new Map(), A = /* @__PURE__ */ new Map(), j = null;
29
+ function M() {
30
+ j?.disconnect(), j = null;
31
+ for (let e of A.values()) e.dispose();
32
+ A.clear();
33
+ for (let e of k.values()) e.innerHTML = "";
34
+ }
35
+ function N(e, t) {
36
+ if (A.has(e)) return;
37
+ let n = T.viewer.renderThumbnailToContainer(e, t, { width: 104 });
38
+ n && (A.set(e, n), n.ready.catch(() => {
39
+ A.get(e) === n && (n.dispose(), A.delete(e), t.innerHTML = "");
40
+ }));
41
+ }
42
+ async function P() {
43
+ if (M(), D.value) return;
44
+ await s();
45
+ let e = O.value;
46
+ if (e) {
47
+ if (typeof IntersectionObserver > "u") {
48
+ for (let [e, t] of k) N(e, t);
49
+ return;
50
+ }
51
+ j = new IntersectionObserver((e) => {
52
+ for (let t of e) {
53
+ if (!t.isIntersecting) continue;
54
+ let e = t.target, n = Number(e.dataset.slideIndex);
55
+ Number.isInteger(n) && N(n, e), j?.unobserve(e);
56
+ }
57
+ }, {
58
+ root: e,
59
+ rootMargin: "140px 0px"
60
+ });
61
+ for (let e of k.values()) j.observe(e);
62
+ }
63
+ }
64
+ function F(e, t) {
65
+ let n = k.get(e);
66
+ if (n && n !== t && j?.unobserve(n), !(t instanceof HTMLElement)) {
67
+ k.delete(e);
68
+ return;
69
+ }
70
+ k.set(e, t), j?.observe(t);
71
+ }
72
+ return g(() => T.viewer, P, { flush: "post" }), g(() => T.items, P, { flush: "post" }), g(D, P, { flush: "post" }), g(() => T.activeIndex, async (e) => {
73
+ await s(), O.value?.querySelector(`[data-nav-slide-index="${e}"]`)?.scrollIntoView({ block: "nearest" });
74
+ }), u(P), l(M), (o, s) => (d(), n("aside", {
75
+ class: c(["pn", { "pn--collapsed": D.value }]),
76
+ "aria-label": "幻灯片导航"
77
+ }, [D.value ? (d(), n("button", {
78
+ key: 0,
79
+ type: "button",
80
+ class: "pn__reopen",
81
+ "aria-label": "展开幻灯片导航",
82
+ title: "展开幻灯片导航",
83
+ onClick: s[0] ||= (e) => D.value = !1
84
+ }, [i(e, {
85
+ name: "chevronRight",
86
+ size: 15
87
+ })])) : (d(), n(t, { key: 1 }, [r("header", _, [r("div", null, [s[2] ||= r("strong", { class: "pn__title" }, "幻灯片", -1), r("span", v, p(a.items.length) + " 张", 1)]), r("button", {
88
+ type: "button",
89
+ class: "pn__collapse",
90
+ "aria-label": "收起幻灯片导航",
91
+ title: "收起幻灯片导航",
92
+ onClick: s[1] ||= (e) => D.value = !0
93
+ }, [i(e, {
94
+ name: "chevronLeft",
95
+ size: 15
96
+ })])]), r("nav", y, [(d(!0), n(t, null, f(a.items, (e) => (d(), n("button", {
97
+ key: e.index,
98
+ type: "button",
99
+ class: c(["pn__item", { "pn__item--active": e.index === a.activeIndex }]),
100
+ "data-nav-slide-index": e.index,
101
+ "aria-current": e.index === a.activeIndex ? "page" : void 0,
102
+ title: `${e.index + 1}. ${e.title}`,
103
+ onClick: (t) => E("navigate", e.index)
104
+ }, [
105
+ r("span", x, p(e.index + 1), 1),
106
+ r("span", S, [r("span", {
107
+ ref_for: !0,
108
+ ref: (t) => F(e.index, t),
109
+ class: "pn__preview",
110
+ "data-slide-index": e.index,
111
+ "aria-hidden": "true"
112
+ }, null, 8, C)]),
113
+ r("span", w, p(e.title), 1)
114
+ ], 10, b))), 128))], 512)], 64))], 2));
115
+ }
116
+ });
117
+ //#endregion
118
+ export { T as default };
119
+
120
+ //# sourceMappingURL=PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PptxNavigationPane.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PptxNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n nextTick,\n onBeforeUnmount,\n onMounted,\n useTemplateRef,\n watch,\n type ComponentPublicInstance,\n} from 'vue'\nimport type { PptxViewer, SlideHandle } from '@aiden0z/pptx-renderer'\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PptxNavigationItem } from '@/lib/pptxNavigation'\n\nconst props = defineProps<{\n viewer: PptxViewer\n items: readonly PptxNavigationItem[]\n activeIndex: number\n}>()\n\nconst emit = defineEmits<{ navigate: [index: number] }>()\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst listEl = useTemplateRef<HTMLElement>('list')\n\nconst thumbElements = new Map<number, HTMLElement>()\nconst thumbHandles = new Map<number, SlideHandle>()\nlet observer: IntersectionObserver | null = null\n\nfunction disposeThumbnails(): void {\n observer?.disconnect()\n observer = null\n for (const handle of thumbHandles.values()) handle.dispose()\n thumbHandles.clear()\n for (const element of thumbElements.values()) element.innerHTML = ''\n}\n\nfunction mountThumbnail(index: number, element: HTMLElement): void {\n if (thumbHandles.has(index)) return\n const handle = props.viewer.renderThumbnailToContainer(index, element, { width: 104 })\n if (!handle) return\n thumbHandles.set(index, handle)\n void handle.ready.catch(() => {\n if (thumbHandles.get(index) !== handle) return\n handle.dispose()\n thumbHandles.delete(index)\n element.innerHTML = ''\n })\n}\n\nasync function setupThumbnails(): Promise<void> {\n disposeThumbnails()\n if (collapsed.value) return\n await nextTick()\n const root = listEl.value\n if (!root) return\n\n if (typeof IntersectionObserver === 'undefined') {\n for (const [index, element] of thumbElements) mountThumbnail(index, element)\n return\n }\n\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue\n const element = entry.target as HTMLElement\n const index = Number(element.dataset.slideIndex)\n if (Number.isInteger(index)) mountThumbnail(index, element)\n observer?.unobserve(element)\n }\n },\n { root, rootMargin: '140px 0px' },\n )\n for (const element of thumbElements.values()) observer.observe(element)\n}\n\nfunction setThumbElement(index: number, value: Element | ComponentPublicInstance | null): void {\n const previous = thumbElements.get(index)\n if (previous && previous !== value) observer?.unobserve(previous)\n if (!(value instanceof HTMLElement)) {\n thumbElements.delete(index)\n return\n }\n thumbElements.set(index, value)\n observer?.observe(value)\n}\n\nwatch(() => props.viewer, setupThumbnails, { flush: 'post' })\nwatch(() => props.items, setupThumbnails, { flush: 'post' })\nwatch(collapsed, setupThumbnails, { flush: 'post' })\nwatch(\n () => props.activeIndex,\n async (index) => {\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>(`[data-nav-slide-index=\"${index}\"]`)\n ?.scrollIntoView({ block: 'nearest' })\n },\n)\n\nonMounted(setupThumbnails)\nonBeforeUnmount(disposeThumbnails)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"{ 'pn--collapsed': collapsed }\" aria-label=\"幻灯片导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开幻灯片导航\"\n title=\"展开幻灯片导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div>\n <strong class=\"pn__title\">幻灯片</strong>\n <span class=\"pn__count\">{{ items.length }} 张</span>\n </div>\n <button\n type=\"button\"\n class=\"pn__collapse\"\n aria-label=\"收起幻灯片导航\"\n title=\"收起幻灯片导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"list\" class=\"pn__list\" aria-label=\"幻灯片列表\">\n <button\n v-for=\"item in items\"\n :key=\"item.index\"\n type=\"button\"\n class=\"pn__item\"\n :class=\"{ 'pn__item--active': item.index === activeIndex }\"\n :data-nav-slide-index=\"item.index\"\n :aria-current=\"item.index === activeIndex ? 'page' : undefined\"\n :title=\"`${item.index + 1}. ${item.title}`\"\n @click=\"emit('navigate', item.index)\"\n >\n <span class=\"pn__number\">{{ item.index + 1 }}</span>\n <span class=\"pn__preview-wrap\">\n <span\n :ref=\"(value) => setThumbElement(item.index, value)\"\n class=\"pn__preview\"\n :data-slide-index=\"item.index\"\n aria-hidden=\"true\"\n />\n </span>\n <span class=\"pn__label\">{{ item.title }}</span>\n </button>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n flex: 0 0 142px;\n width: 142px;\n min-width: 0;\n display: flex;\n flex-direction: column;\n border-right: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n transition: flex-basis 160ms ease;\n}\n.pn--collapsed {\n flex-basis: 0;\n width: 0;\n border-right: 0;\n}\n.pn__header {\n min-height: 45px;\n padding: 9px 8px 8px 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n.pn__collapse,\n.pn__reopen {\n border: 0;\n color: var(--muted);\n background: transparent;\n cursor: pointer;\n}\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n place-items: center;\n border-radius: 7px;\n}\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n background: var(--surface);\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.pn__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 8px 7px 22px;\n scrollbar-width: thin;\n}\n.pn__item {\n position: relative;\n width: 100%;\n min-width: 0;\n margin: 0 0 8px;\n padding: 5px;\n display: block;\n border: 1px solid transparent;\n border-radius: 8px;\n color: var(--muted);\n background: transparent;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.pn__item--active {\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n border-radius: 5px;\n color: #fff;\n background: rgba(20, 20, 20, 0.68);\n font-size: 9.5px;\n font-weight: 650;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n.pn__item--active .pn__number {\n color: #fff;\n background: var(--accent);\n}\n.pn__preview-wrap {\n height: 76px;\n min-width: 0;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-2);\n border-radius: 4px;\n background: #fff;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n.pn__preview {\n width: 104px;\n min-height: 1px;\n display: block;\n overflow: hidden;\n pointer-events: none;\n}\n.pn__label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n font-size: 10.5px;\n line-height: 1.35;\n}\n\n@container (max-width: 500px) {\n .pn {\n flex-basis: 124px;\n width: 124px;\n }\n .pn--collapsed {\n flex-basis: 0;\n width: 0;\n }\n .pn__preview-wrap {\n height: 66px;\n }\n}\n</style>\n"],"mappings":"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"}
@@ -2,6 +2,8 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./PptxViewer.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/PptxViewer.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-47d76691"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-72d11ba8"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=PptxViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PptxViewer.js","names":[],"sources":["../../src/components/PptxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue'\nimport { PptxViewer, RECOMMENDED_ZIP_LIMITS } from '@aiden0z/pptx-renderer'\nimport PptxNavigationPane from '@/components/PptxNavigationPane.vue'\nimport { buildPptxNavigation, type PptxNavigationItem } from '@/lib/pptxNavigation'\n// pdfjs is only used as a fallback to rasterize SmartArt / EMF graphics the native renderer can't draw. The\n// library dynamic-imports these two ESM files inside its OWN web worker, so we hand it servable URLs (Vite's\n// `?url` emits the real assets and returns their URL — root-relative in the same-origin prod build, a dev\n// URL under `pnpm dev`). They're fetched lazily by that worker only when a slide actually needs the fallback.\nimport pdfModuleUrl from 'pdfjs-dist/build/pdf.min.mjs?url'\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\n/**\n * Side-viewer PowerPoint (`.pptx`/`.pptm`/`.ppsx`) renderer. Fetches the deck bytes from the download URL and\n * renders the OOXML PresentationML to HTML/SVG entirely in-browser via @aiden0z/pptx-renderer\n * (https://github.com/aiden0z/pptx-renderer) — a vertically scrollable list of slides, each scaled to fit the\n * pane (`fitMode: 'contain'`, the library default). No CDN / network beyond the file fetch (+ the lazy pdfjs\n * fallback above): the renderer bundles its own jszip + echarts (charts) and inlines media. The library writes\n * inline styles only, so nothing leaks onto the page.\n *\n * Two entry shapes mirror the other viewers (docx/xlsx/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .pptx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// Keep the scroll viewport separate from the renderer mount: `fitMode: 'contain'` measures the mount's\n// `clientWidth`, so putting visual padding on that same element makes the slide consume the padded width and\n// overflow horizontally. Both stay mounted while loading so `open()` always sees real dimensions.\nconst scrollEl = useTemplateRef<HTMLElement>('scrollEl')\nconst docEl = useTemplateRef<HTMLElement>('docEl')\nconst navViewer = shallowRef<PptxViewer | null>(null)\nconst navItems = shallowRef<PptxNavigationItem[]>([])\nconst activeSlide = shallowRef(0)\nconst navCollapsed = shallowRef(false)\n// Live viewer instance — destroyed on re-render / unmount (it holds echarts instances, observers, and its\n// pdfjs worker, none of which GC on their own).\nlet viewer: PptxViewer | null = null\nlet navigationLock: number | null = null\nlet navigationLockTimer: ReturnType<typeof setTimeout> | undefined\n\nfunction disposeViewer(): void {\n navigationLock = null\n if (navigationLockTimer) clearTimeout(navigationLockTimer)\n navigationLockTimer = undefined\n navViewer.value = null\n navItems.value = []\n if (viewer) {\n try {\n viewer.destroy()\n } catch {\n /* already torn down — ignore */\n }\n viewer = null\n }\n}\n\nasync function render(): Promise<void> {\n const el = docEl.value\n const scroll = scrollEl.value\n if (!props.url || !el || !scroll) return\n status.value = 'loading'\n errorMsg.value = ''\n disposeViewer()\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el || scrollEl.value !== scroll) return\n const v = await PptxViewer.open(buf, el, {\n zipLimits: RECOMMENDED_ZIP_LIMITS,\n scrollContainer: scroll, // the outer viewport drives which slides the windowed list mounts\n listOptions: { windowed: true }, // virtualize: only mount slides near the viewport (large decks stay fast)\n pdfjs: { moduleUrl: pdfModuleUrl, workerUrl: pdfWorkerUrl },\n onSlideChange: (index) => {\n if (navigationLock !== null && index !== navigationLock) return\n if (props.url === url) activeSlide.value = index\n },\n })\n // Stale guard again: the file may have changed while parse/render ran — tear the new viewer down.\n if (props.url !== url || docEl.value !== el || scrollEl.value !== scroll) {\n try {\n v.destroy()\n } catch {\n /* ignore */\n }\n return\n }\n viewer = v\n navViewer.value = v\n navItems.value = v.presentationData ? buildPptxNavigation(v.presentationData) : []\n activeSlide.value = v.currentSlideIndex\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction navigateToSlide(index: number): void {\n navigationLock = index\n activeSlide.value = index\n if (navigationLockTimer) clearTimeout(navigationLockTimer)\n navigationLockTimer = setTimeout(() => {\n navigationLock = null\n navigationLockTimer = undefined\n }, 1000)\n void viewer?.goToSlide(index, { behavior: 'smooth', block: 'start' })\n}\n\nonMounted(render)\nwatch(() => props.url, render)\nonBeforeUnmount(() => {\n disposeViewer()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pv\">\n <PptxNavigationPane\n v-if=\"status === 'ready' && navViewer\"\n v-model:collapsed=\"navCollapsed\"\n :viewer=\"navViewer\"\n :items=\"navItems\"\n :active-index=\"activeSlide\"\n @navigate=\"navigateToSlide\"\n />\n <div class=\"pv__stage\">\n <!-- The padded scroll viewport wraps a padding-free mount whose clientWidth is the real slide width. -->\n <div ref=\"scrollEl\" class=\"pv__scroll\">\n <div ref=\"docEl\" class=\"pv__doc\" />\n </div>\n <!-- Loading / error as an overlay so the container underneath keeps its dimensions. -->\n <div v-if=\"status === 'loading'\" class=\"pv__overlay\">\n <p class=\"pv__msg\">正在渲染 PPT…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pv__overlay\">\n <div class=\"pv__msg pv__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pv__retry\" @click=\"render\">重试</button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pv {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.pv__stage {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n/* Scroll region holding the rendered slides. The deck keeps its native appearance (the library scales each\n * slide to fit this width); we only tint the surrounding \"desk\" to the app surface so it sits naturally in\n * both light and dark themes. The renderer writes inline styles, so no :deep overrides are needed. */\n.pv__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n padding: 16px 14px 40px;\n}\n.pv__doc {\n width: 100%;\n min-width: 0;\n}\n/* Overlay covers the (empty) container while loading or on error, without unmounting it. */\n.pv__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n.pv__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.pv__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.pv__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.pv__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n</style>\n"],"mappings":""}
@@ -1,73 +1,105 @@
1
- import { createCommentVNode as e, createElementBlock as t, createElementVNode as n, defineComponent as r, onBeforeUnmount as i, onMounted as a, openBlock as o, ref as s, toDisplayString as c, watch as l } from "vue";
2
- import u from "pdfjs-dist/build/pdf.worker.min.mjs?url";
3
- import { PptxViewer as d, RECOMMENDED_ZIP_LIMITS as f } from "@aiden0z/pptx-renderer";
4
- import p from "pdfjs-dist/build/pdf.min.mjs?url";
1
+ import e from "./PptxNavigationPane.js";
2
+ import { buildPptxNavigation as t } from "../lib/pptxNavigation.js";
3
+ import { createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, defineComponent as o, onBeforeUnmount as s, onMounted as c, openBlock as l, shallowRef as u, toDisplayString as d, useTemplateRef as f, watch as p } from "vue";
4
+ import m from "pdfjs-dist/build/pdf.worker.min.mjs?url";
5
+ import { PptxViewer as h, RECOMMENDED_ZIP_LIMITS as g } from "@aiden0z/pptx-renderer";
6
+ import _ from "pdfjs-dist/build/pdf.min.mjs?url";
5
7
  //#region src/components/PptxViewer.vue?vue&type=script&setup=true&lang.ts
6
- var m = { class: "pv" }, h = {
8
+ var v = { class: "pv" }, y = { class: "pv__stage" }, b = {
7
9
  key: 0,
8
10
  class: "pv__overlay"
9
- }, g = {
11
+ }, x = {
10
12
  key: 1,
11
13
  class: "pv__overlay"
12
- }, _ = { class: "pv__msg pv__msg--error" }, v = /*@__PURE__*/ r({
14
+ }, S = { class: "pv__msg pv__msg--error" }, C = /*@__PURE__*/ o({
13
15
  __name: "PptxViewer",
14
16
  props: {
15
17
  url: {},
16
18
  name: {},
17
19
  size: {}
18
20
  },
19
- setup(r) {
20
- let v = r, y = s("loading"), b = s(""), x = s(null), S = null;
21
- function C() {
22
- if (S) {
21
+ setup(o) {
22
+ let C = o, w = u("loading"), T = u(""), E = f("scrollEl"), D = f("docEl"), O = u(null), k = u([]), A = u(0), j = u(!1), M = null, N = null, P;
23
+ function F() {
24
+ if (N = null, P && clearTimeout(P), P = void 0, O.value = null, k.value = [], M) {
23
25
  try {
24
- S.destroy();
26
+ M.destroy();
25
27
  } catch {}
26
- S = null;
28
+ M = null;
27
29
  }
28
30
  }
29
- async function w() {
30
- let e = x.value;
31
- if (!v.url || !e) return;
32
- y.value = "loading", b.value = "", C(), e.innerHTML = "";
33
- let t = v.url;
31
+ async function I() {
32
+ let e = D.value, n = E.value;
33
+ if (!C.url || !e || !n) return;
34
+ w.value = "loading", T.value = "", F(), e.innerHTML = "";
35
+ let r = C.url;
34
36
  try {
35
- let n = await fetch(t);
36
- if (!n.ok) throw Error(`HTTP ${n.status}`);
37
- let r = await n.arrayBuffer();
38
- if (v.url !== t || x.value !== e) return;
39
- let i = await d.open(r, e, {
40
- zipLimits: f,
41
- scrollContainer: e,
37
+ let i = await fetch(r);
38
+ if (!i.ok) throw Error(`HTTP ${i.status}`);
39
+ let a = await i.arrayBuffer();
40
+ if (C.url !== r || D.value !== e || E.value !== n) return;
41
+ let o = await h.open(a, e, {
42
+ zipLimits: g,
43
+ scrollContainer: n,
42
44
  listOptions: { windowed: !0 },
43
45
  pdfjs: {
44
- moduleUrl: p,
45
- workerUrl: u
46
+ moduleUrl: _,
47
+ workerUrl: m
48
+ },
49
+ onSlideChange: (e) => {
50
+ N !== null && e !== N || C.url === r && (A.value = e);
46
51
  }
47
52
  });
48
- if (v.url !== t || x.value !== e) {
53
+ if (C.url !== r || D.value !== e || E.value !== n) {
49
54
  try {
50
- i.destroy();
55
+ o.destroy();
51
56
  } catch {}
52
57
  return;
53
58
  }
54
- S = i, y.value = "ready";
59
+ M = o, O.value = o, k.value = o.presentationData ? t(o.presentationData) : [], A.value = o.currentSlideIndex, w.value = "ready";
55
60
  } catch (e) {
56
- b.value = e instanceof Error ? e.message : String(e), y.value = "error";
61
+ T.value = e instanceof Error ? e.message : String(e), w.value = "error";
57
62
  }
58
63
  }
59
- return a(w), l(() => v.url, w), i(() => {
60
- C(), x.value && (x.value.innerHTML = "");
61
- }), (r, i) => (o(), t("div", m, [n("div", {
64
+ function L(e) {
65
+ N = e, A.value = e, P && clearTimeout(P), P = setTimeout(() => {
66
+ N = null, P = void 0;
67
+ }, 1e3), M?.goToSlide(e, {
68
+ behavior: "smooth",
69
+ block: "start"
70
+ });
71
+ }
72
+ return c(I), p(() => C.url, I), s(() => {
73
+ F(), D.value && (D.value.innerHTML = "");
74
+ }), (t, o) => (l(), i("div", v, [w.value === "ready" && O.value ? (l(), n(e, {
75
+ key: 0,
76
+ collapsed: j.value,
77
+ "onUpdate:collapsed": o[0] ||= (e) => j.value = e,
78
+ viewer: O.value,
79
+ items: k.value,
80
+ "active-index": A.value,
81
+ onNavigate: L
82
+ }, null, 8, [
83
+ "collapsed",
84
+ "viewer",
85
+ "items",
86
+ "active-index"
87
+ ])) : r("", !0), a("div", y, [a("div", {
88
+ ref_key: "scrollEl",
89
+ ref: E,
90
+ class: "pv__scroll"
91
+ }, [a("div", {
62
92
  ref_key: "docEl",
63
- ref: x,
93
+ ref: D,
64
94
  class: "pv__doc"
65
- }, null, 512), y.value === "loading" ? (o(), t("div", h, [...i[0] ||= [n("p", { class: "pv__msg" }, "正在渲染 PPT…", -1)]])) : y.value === "error" ? (o(), t("div", g, [n("div", _, [n("p", null, "渲染失败:" + c(b.value), 1), n("button", {
95
+ }, null, 512)], 512), w.value === "loading" ? (l(), i("div", b, [...o[1] ||= [a("p", { class: "pv__msg" }, "正在渲染 PPT…", -1)]])) : w.value === "error" ? (l(), i("div", x, [a("div", S, [a("p", null, "渲染失败:" + d(T.value), 1), a("button", {
66
96
  type: "button",
67
97
  class: "pv__retry",
68
- onClick: w
69
- }, "重试")])])) : e("", !0)]));
98
+ onClick: I
99
+ }, "重试")])])) : r("", !0)])]));
70
100
  }
71
101
  });
72
102
  //#endregion
73
- export { v as default };
103
+ export { C as default };
104
+
105
+ //# sourceMappingURL=PptxViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PptxViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PptxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue'\nimport { PptxViewer, RECOMMENDED_ZIP_LIMITS } from '@aiden0z/pptx-renderer'\nimport PptxNavigationPane from '@/components/PptxNavigationPane.vue'\nimport { buildPptxNavigation, type PptxNavigationItem } from '@/lib/pptxNavigation'\n// pdfjs is only used as a fallback to rasterize SmartArt / EMF graphics the native renderer can't draw. The\n// library dynamic-imports these two ESM files inside its OWN web worker, so we hand it servable URLs (Vite's\n// `?url` emits the real assets and returns their URL — root-relative in the same-origin prod build, a dev\n// URL under `pnpm dev`). They're fetched lazily by that worker only when a slide actually needs the fallback.\nimport pdfModuleUrl from 'pdfjs-dist/build/pdf.min.mjs?url'\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\n/**\n * Side-viewer PowerPoint (`.pptx`/`.pptm`/`.ppsx`) renderer. Fetches the deck bytes from the download URL and\n * renders the OOXML PresentationML to HTML/SVG entirely in-browser via @aiden0z/pptx-renderer\n * (https://github.com/aiden0z/pptx-renderer) — a vertically scrollable list of slides, each scaled to fit the\n * pane (`fitMode: 'contain'`, the library default). No CDN / network beyond the file fetch (+ the lazy pdfjs\n * fallback above): the renderer bundles its own jszip + echarts (charts) and inlines media. The library writes\n * inline styles only, so nothing leaks onto the page.\n *\n * Two entry shapes mirror the other viewers (docx/xlsx/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .pptx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// Keep the scroll viewport separate from the renderer mount: `fitMode: 'contain'` measures the mount's\n// `clientWidth`, so putting visual padding on that same element makes the slide consume the padded width and\n// overflow horizontally. Both stay mounted while loading so `open()` always sees real dimensions.\nconst scrollEl = useTemplateRef<HTMLElement>('scrollEl')\nconst docEl = useTemplateRef<HTMLElement>('docEl')\nconst navViewer = shallowRef<PptxViewer | null>(null)\nconst navItems = shallowRef<PptxNavigationItem[]>([])\nconst activeSlide = shallowRef(0)\nconst navCollapsed = shallowRef(false)\n// Live viewer instance — destroyed on re-render / unmount (it holds echarts instances, observers, and its\n// pdfjs worker, none of which GC on their own).\nlet viewer: PptxViewer | null = null\nlet navigationLock: number | null = null\nlet navigationLockTimer: ReturnType<typeof setTimeout> | undefined\n\nfunction disposeViewer(): void {\n navigationLock = null\n if (navigationLockTimer) clearTimeout(navigationLockTimer)\n navigationLockTimer = undefined\n navViewer.value = null\n navItems.value = []\n if (viewer) {\n try {\n viewer.destroy()\n } catch {\n /* already torn down — ignore */\n }\n viewer = null\n }\n}\n\nasync function render(): Promise<void> {\n const el = docEl.value\n const scroll = scrollEl.value\n if (!props.url || !el || !scroll) return\n status.value = 'loading'\n errorMsg.value = ''\n disposeViewer()\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el || scrollEl.value !== scroll) return\n const v = await PptxViewer.open(buf, el, {\n zipLimits: RECOMMENDED_ZIP_LIMITS,\n scrollContainer: scroll, // the outer viewport drives which slides the windowed list mounts\n listOptions: { windowed: true }, // virtualize: only mount slides near the viewport (large decks stay fast)\n pdfjs: { moduleUrl: pdfModuleUrl, workerUrl: pdfWorkerUrl },\n onSlideChange: (index) => {\n if (navigationLock !== null && index !== navigationLock) return\n if (props.url === url) activeSlide.value = index\n },\n })\n // Stale guard again: the file may have changed while parse/render ran — tear the new viewer down.\n if (props.url !== url || docEl.value !== el || scrollEl.value !== scroll) {\n try {\n v.destroy()\n } catch {\n /* ignore */\n }\n return\n }\n viewer = v\n navViewer.value = v\n navItems.value = v.presentationData ? buildPptxNavigation(v.presentationData) : []\n activeSlide.value = v.currentSlideIndex\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction navigateToSlide(index: number): void {\n navigationLock = index\n activeSlide.value = index\n if (navigationLockTimer) clearTimeout(navigationLockTimer)\n navigationLockTimer = setTimeout(() => {\n navigationLock = null\n navigationLockTimer = undefined\n }, 1000)\n void viewer?.goToSlide(index, { behavior: 'smooth', block: 'start' })\n}\n\nonMounted(render)\nwatch(() => props.url, render)\nonBeforeUnmount(() => {\n disposeViewer()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pv\">\n <PptxNavigationPane\n v-if=\"status === 'ready' && navViewer\"\n v-model:collapsed=\"navCollapsed\"\n :viewer=\"navViewer\"\n :items=\"navItems\"\n :active-index=\"activeSlide\"\n @navigate=\"navigateToSlide\"\n />\n <div class=\"pv__stage\">\n <!-- The padded scroll viewport wraps a padding-free mount whose clientWidth is the real slide width. -->\n <div ref=\"scrollEl\" class=\"pv__scroll\">\n <div ref=\"docEl\" class=\"pv__doc\" />\n </div>\n <!-- Loading / error as an overlay so the container underneath keeps its dimensions. -->\n <div v-if=\"status === 'loading'\" class=\"pv__overlay\">\n <p class=\"pv__msg\">正在渲染 PPT…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pv__overlay\">\n <div class=\"pv__msg pv__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pv__retry\" @click=\"render\">重试</button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pv {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.pv__stage {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n/* Scroll region holding the rendered slides. The deck keeps its native appearance (the library scales each\n * slide to fit this width); we only tint the surrounding \"desk\" to the app surface so it sits naturally in\n * both light and dark themes. The renderer writes inline styles, so no :deep overrides are needed. */\n.pv__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n padding: 16px 14px 40px;\n}\n.pv__doc {\n width: 100%;\n min-width: 0;\n}\n/* Overlay covers the (empty) container while loading or on error, without unmounting it. */\n.pv__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n.pv__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.pv__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.pv__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.pv__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./PsdViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-5fbd3b9c"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=PsdViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PsdViewer.js","names":[],"sources":["../../src/components/PsdViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, shallowRef, watch } from 'vue'\nimport type { Layer, Psd } from 'ag-psd'\nimport { formatBytes } from '@/lib/format'\nimport { isMissingModule } from '@/lib/optionalDep'\nimport Icon from '@/components/icons/Icon.vue'\nimport MissingDependency from '@/components/MissingDependency.vue'\n\n/**\n * Side-viewer for Photoshop documents (.psd/.psb). Fetches the bytes and parses them in-browser with ag-psd\n * (dynamically imported), then — unlike the generic RasterViewer which only shows the flattened composite —\n * exposes the LAYER TREE: a panel of every layer/group with a thumbnail and a visibility toggle, and it\n * RE-COMPOSITES live onto a canvas as you toggle layers (honouring per-layer opacity and blend mode; group\n * visibility cascades to its children). A flattened PSD (no layers) falls back to ag-psd's composite.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\n// Set when the PSD engine (ag-psd, an optional peer) isn't installed → show the install card, not a raw error.\nconst missingDeps = ref<string[] | null>(null)\nconst fit = ref(true)\n\n// Reading a PSD decodes EVERY layer's pixels (needed for the layer panel), so gate very large files behind a\n// confirm the way RasterViewer does — a huge multi-layer PSD would otherwise lock the tab while decoding.\nconst SIZE_LIMIT = 60 * 1024 * 1024\n\ninterface PsdNode {\n id: number\n name: string\n depth: number\n isGroup: boolean\n opacity: number\n blend: string\n left: number\n top: number\n canvas?: HTMLCanvasElement // leaf pixels\n thumb?: string // leaf thumbnail data URL\n parentId: number\n children: PsdNode[]\n}\n\nlet width = 0\nlet height = 0\nconst drawOrder = shallowRef<PsdNode[]>([]) // leaves, bottom → top (compositing order)\nconst panelRows = shallowRef<PsdNode[]>([]) // groups + leaves, top → bottom (Photoshop panel order)\nconst nodeById = new Map<number, PsdNode>()\nconst hidden = ref(new Set<number>()) // ids the user (or the file) has hidden\nlet fallbackCanvas: HTMLCanvasElement | undefined // flattened composite, when there are no layers\nconst stage = ref<HTMLCanvasElement>()\n\nconst layerCount = ref(0)\n\n// Photoshop blend mode → canvas globalCompositeOperation (unknown → normal).\nconst BLEND: Record<string, GlobalCompositeOperation> = {\n normal: 'source-over',\n multiply: 'multiply',\n screen: 'screen',\n overlay: 'overlay',\n darken: 'darken',\n lighten: 'lighten',\n 'color dodge': 'color-dodge',\n 'linear dodge': 'lighter',\n 'color burn': 'color-burn',\n 'hard light': 'hard-light',\n 'soft light': 'soft-light',\n difference: 'difference',\n exclusion: 'exclusion',\n hue: 'hue',\n saturation: 'saturation',\n color: 'color',\n luminosity: 'luminosity',\n}\n\nfunction makeThumb(src: HTMLCanvasElement): string | undefined {\n if (!src.width || !src.height) return undefined\n const S = 34\n const c = document.createElement('canvas')\n c.width = S\n c.height = S\n const ctx = c.getContext('2d')\n if (!ctx) return undefined\n const s = Math.min(S / src.width, S / src.height)\n const w = src.width * s\n const h = src.height * s\n ctx.drawImage(src, (S - w) / 2, (S - h) / 2, w, h)\n return c.toDataURL()\n}\n\nfunction build(psd: Psd): void {\n nodeById.clear()\n const hid = new Set<number>()\n let id = 0\n function rec(layers: Layer[] | undefined, depth: number, parentId: number): PsdNode[] {\n const out: PsdNode[] = []\n for (const l of layers ?? []) {\n const isGroup = Array.isArray(l.children)\n const n: PsdNode = {\n id: id++,\n name: l.name || (isGroup ? '组' : '图层'),\n depth,\n isGroup,\n opacity: typeof l.opacity === 'number' ? l.opacity : 1,\n blend: l.blendMode || 'normal',\n left: l.left ?? 0,\n top: l.top ?? 0,\n canvas: isGroup ? undefined : (l.canvas as HTMLCanvasElement | undefined),\n parentId,\n children: [],\n }\n nodeById.set(n.id, n)\n if (l.hidden) hid.add(n.id)\n if (n.canvas) n.thumb = makeThumb(n.canvas)\n if (isGroup) n.children = rec(l.children, depth + 1, n.id)\n out.push(n)\n }\n return out\n }\n const tree = rec(psd.children, 0, -1)\n\n // Compositing order: leaves in document (bottom → top) order.\n const leaves: PsdNode[] = []\n const collectDraw = (ns: PsdNode[]): void => {\n for (const n of ns) {\n if (n.isGroup) collectDraw(n.children)\n else if (n.canvas) leaves.push(n)\n }\n }\n collectDraw(tree)\n\n // Panel order: top → bottom (reverse each level), group header before its indented children.\n const rows: PsdNode[] = []\n const collectPanel = (ns: PsdNode[]): void => {\n for (const n of [...ns].reverse()) {\n rows.push(n)\n if (n.isGroup) collectPanel(n.children)\n }\n }\n collectPanel(tree)\n\n drawOrder.value = leaves\n panelRows.value = rows\n hidden.value = hid\n layerCount.value = leaves.length\n fallbackCanvas = leaves.length ? undefined : (psd.canvas as HTMLCanvasElement | undefined)\n}\n\n// A leaf is visible only if neither it nor any ancestor group is hidden.\nfunction visible(id: number): boolean {\n let n: PsdNode | undefined = nodeById.get(id)\n while (n) {\n if (hidden.value.has(n.id)) return false\n n = n.parentId >= 0 ? nodeById.get(n.parentId) : undefined\n }\n return true\n}\n\nfunction recomposite(): void {\n const cv = stage.value\n if (!cv) return\n cv.width = width || fallbackCanvas?.width || 1\n cv.height = height || fallbackCanvas?.height || 1\n const ctx = cv.getContext('2d')\n if (!ctx) return\n ctx.clearRect(0, 0, cv.width, cv.height)\n if (fallbackCanvas) {\n ctx.drawImage(fallbackCanvas, 0, 0)\n return\n }\n for (const leaf of drawOrder.value) {\n if (!leaf.canvas || !visible(leaf.id)) continue\n ctx.globalAlpha = Math.max(0, Math.min(1, leaf.opacity))\n ctx.globalCompositeOperation = BLEND[leaf.blend] ?? 'source-over'\n ctx.drawImage(leaf.canvas, leaf.left, leaf.top)\n }\n ctx.globalAlpha = 1\n ctx.globalCompositeOperation = 'source-over'\n}\n\nfunction toggle(n: PsdNode): void {\n const next = new Set(hidden.value)\n if (next.has(n.id)) next.delete(n.id)\n else next.add(n.id)\n hidden.value = next\n recomposite()\n}\n\nfunction load(): void {\n if (!props.url) return\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n missingDeps.value = null\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n const { readPsd } = await import('ag-psd')\n const psd = readPsd(buf, { skipThumbnail: true })\n width = psd.width\n height = psd.height\n build(psd)\n status.value = 'ready'\n // wait for the canvas element to mount, then paint\n requestAnimationFrame(recomposite)\n } catch (e) {\n if (isMissingModule(e)) missingDeps.value = ['ag-psd']\n else errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(() => {\n drawOrder.value = []\n panelRows.value = []\n nodeById.clear()\n})\n</script>\n\n<template>\n <div class=\"psd\">\n <p v-if=\"status === 'loading'\" class=\"psd__msg\">正在解码 PSD…</p>\n <div v-else-if=\"status === 'gate'\" class=\"psd__msg psd__gate\">\n <p>该 PSD 较大({{ formatBytes(props.size) }}),将在浏览器内逐图层解码。是否继续?</p>\n <button type=\"button\" class=\"psd__btn\" @click=\"doLoad\">解码并预览</button>\n </div>\n <MissingDependency\n v-else-if=\"missingDeps\"\n :pkg=\"missingDeps\"\n label=\"PSD\"\n :download-url=\"props.url\"\n :download-name=\"props.name\"\n />\n <div v-else-if=\"status === 'error'\" class=\"psd__msg psd__msg--error\">\n <p>解码失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"psd__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else>\n <div class=\"psd__bar\">\n <span class=\"psd__badge\">PSD</span>\n <span class=\"psd__dim\">{{ width }}×{{ height }}</span>\n <span v-if=\"layerCount\" class=\"psd__dim\">{{ layerCount }} 图层</span>\n <span v-if=\"props.size\" class=\"psd__dim\">{{ formatBytes(props.size) }}</span>\n <span class=\"psd__spacer\" />\n <button\n type=\"button\"\n class=\"psd__toggle\"\n :class=\"{ 'psd__toggle--on': !fit }\"\n @click=\"fit = !fit\"\n >\n {{ fit ? '原始大小' : '适应' }}\n </button>\n </div>\n\n <div class=\"psd__stage\" :class=\"{ 'psd__stage--fit': fit }\">\n <canvas ref=\"stage\" class=\"psd__canvas\" :class=\"{ 'psd__canvas--fit': fit }\" />\n </div>\n\n <ul v-if=\"panelRows.length\" class=\"psd__layers\">\n <li\n v-for=\"n in panelRows\"\n :key=\"n.id\"\n class=\"psd__layer\"\n :class=\"{ 'psd__layer--group': n.isGroup, 'psd__layer--off': !visible(n.id) }\"\n :style=\"{ paddingLeft: 10 + n.depth * 14 + 'px' }\"\n >\n <button\n type=\"button\"\n class=\"psd__eye\"\n :class=\"{ 'psd__eye--off': hidden.has(n.id) }\"\n :title=\"hidden.has(n.id) ? '显示' : '隐藏'\"\n @click=\"toggle(n)\"\n >\n <Icon name=\"eye\" :size=\"14\" />\n </button>\n <span class=\"psd__thumb\">\n <img v-if=\"n.thumb\" :src=\"n.thumb\" alt=\"\" />\n <span v-else class=\"psd__thumb--group\" />\n </span>\n <span class=\"psd__name\">{{ n.name }}</span>\n <span v-if=\"n.blend !== 'normal'\" class=\"psd__blend\">{{ n.blend }}</span>\n <span v-if=\"n.opacity < 1\" class=\"psd__op\">{{ Math.round(n.opacity * 100) }}%</span>\n </li>\n </ul>\n </template>\n </div>\n</template>\n\n<style scoped>\n.psd {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n.psd__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.psd__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.psd__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.psd__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.psd__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.psd__bar {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n font-size: 12px;\n color: var(--muted);\n}\n.psd__badge {\n padding: 2px 9px;\n border-radius: 999px;\n background: var(--surface-2);\n color: var(--accent);\n font-weight: 600;\n}\n.psd__dim {\n color: var(--faint);\n}\n.psd__spacer {\n flex: 1 1 auto;\n}\n.psd__toggle {\n min-width: 26px;\n height: 26px;\n padding: 0 10px;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.psd__toggle:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.psd__toggle--on {\n color: var(--accent);\n border-color: var(--accent);\n background: var(--surface-2);\n}\n\n/* Checkerboard so layer transparency reads clearly. */\n.psd__stage {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n --sq: 10px;\n background-color: #fff;\n background-image:\n linear-gradient(45deg, #e6e6e6 25%, transparent 25%),\n linear-gradient(-45deg, #e6e6e6 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #e6e6e6 75%),\n linear-gradient(-45deg, transparent 75%, #e6e6e6 75%);\n background-size: calc(var(--sq) * 2) calc(var(--sq) * 2);\n background-position:\n 0 0,\n 0 var(--sq),\n var(--sq) calc(var(--sq) * -1),\n calc(var(--sq) * -1) 0;\n}\n.psd__stage--fit {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n}\n.psd__canvas {\n display: block;\n image-rendering: auto;\n}\n.psd__canvas--fit {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.psd__layers {\n list-style: none;\n margin: 0;\n padding: 4px 0;\n flex-shrink: 0;\n max-height: 40%;\n overflow: auto;\n overscroll-behavior: contain;\n border-top: 1px solid var(--border);\n background: var(--surface);\n}\n.psd__layer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 12px 4px 0;\n font-size: 12.5px;\n color: var(--text);\n}\n.psd__layer--group {\n font-weight: 600;\n color: var(--muted);\n}\n.psd__layer--off {\n opacity: 0.45;\n}\n.psd__eye {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 22px;\n flex-shrink: 0;\n padding: 0;\n color: var(--accent);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.psd__eye--off {\n color: var(--faint);\n}\n.psd__thumb {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n flex-shrink: 0;\n border: 1px solid var(--border);\n border-radius: 4px;\n background: repeating-conic-gradient(#eee 0% 25%, #fff 0% 50%) 0 / 10px 10px;\n overflow: hidden;\n}\n.psd__thumb img {\n max-width: 100%;\n max-height: 100%;\n display: block;\n}\n.psd__thumb--group {\n width: 14px;\n height: 11px;\n border: 1.4px solid var(--faint);\n border-radius: 2px;\n background: transparent;\n}\n.psd__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.psd__blend {\n flex-shrink: 0;\n font-size: 10.5px;\n color: var(--accent);\n}\n.psd__op {\n flex-shrink: 0;\n font-family: var(--font-mono, monospace);\n font-size: 11px;\n color: var(--faint);\n}\n</style>\n"],"mappings":""}