@l1yp/file-viewer 0.1.1 → 0.1.3

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 (440) hide show
  1. package/README.md +1 -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 +2 -0
  47. package/dist/components/DocxNavigationPane.js.map +1 -0
  48. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +2 -0
  49. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  50. package/dist/components/DocxViewer.js +2 -0
  51. package/dist/components/DocxViewer.js.map +1 -0
  52. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  53. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  54. package/dist/components/DrawioViewer.js +2 -0
  55. package/dist/components/DrawioViewer.js.map +1 -0
  56. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  57. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  58. package/dist/components/DxfViewer.js +2 -0
  59. package/dist/components/DxfViewer.js.map +1 -0
  60. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  61. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  62. package/dist/components/EbookViewer.js +2 -0
  63. package/dist/components/EbookViewer.js.map +1 -0
  64. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  65. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  66. package/dist/components/EmlViewer.js +2 -0
  67. package/dist/components/EmlViewer.js.map +1 -0
  68. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  69. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  70. package/dist/components/EplViewer.js +2 -0
  71. package/dist/components/EplViewer.js.map +1 -0
  72. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  73. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  74. package/dist/components/FileViewerBody.js +3 -1
  75. package/dist/components/FileViewerBody.js.map +1 -0
  76. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +2 -0
  77. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -0
  78. package/dist/components/FileViewerPanel.js +2 -0
  79. package/dist/components/FileViewerPanel.js.map +1 -0
  80. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +2 -0
  81. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  82. package/dist/components/FontViewer.js +2 -0
  83. package/dist/components/FontViewer.js.map +1 -0
  84. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  85. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  86. package/dist/components/FrontmatterCard.js +2 -0
  87. package/dist/components/FrontmatterCard.js.map +1 -0
  88. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js +2 -0
  89. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  90. package/dist/components/GeoViewer.js +2 -0
  91. package/dist/components/GeoViewer.js.map +1 -0
  92. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  93. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  94. package/dist/components/HarViewer.js +2 -0
  95. package/dist/components/HarViewer.js.map +1 -0
  96. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  97. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  98. package/dist/components/HexViewer.js +2 -0
  99. package/dist/components/HexViewer.js.map +1 -0
  100. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  101. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  102. package/dist/components/HprofViewer.js +2 -0
  103. package/dist/components/HprofViewer.js.map +1 -0
  104. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  105. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  106. package/dist/components/ImageLightbox.js +2 -0
  107. package/dist/components/ImageLightbox.js.map +1 -0
  108. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +2 -0
  109. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -0
  110. package/dist/components/InlineText.js +2 -0
  111. package/dist/components/InlineText.js.map +1 -0
  112. package/dist/components/JksViewer.js +2 -0
  113. package/dist/components/JksViewer.js.map +1 -0
  114. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  115. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  116. package/dist/components/JsonlViewer.js +3 -1
  117. package/dist/components/JsonlViewer.js.map +1 -0
  118. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  119. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  120. package/dist/components/LogViewer.js +2 -0
  121. package/dist/components/LogViewer.js.map +1 -0
  122. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  123. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  124. package/dist/components/LrcViewer.js +2 -0
  125. package/dist/components/LrcViewer.js.map +1 -0
  126. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  127. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  128. package/dist/components/MarkdownTable.js +2 -0
  129. package/dist/components/MarkdownTable.js.map +1 -0
  130. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js +2 -0
  131. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  132. package/dist/components/MarkdownText.js +2 -0
  133. package/dist/components/MarkdownText.js.map +1 -0
  134. package/dist/components/MhtViewer.js +2 -0
  135. package/dist/components/MhtViewer.js.map +1 -0
  136. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  137. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  138. package/dist/components/MindMapView.js +2 -0
  139. package/dist/components/MindMapView.js.map +1 -0
  140. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js +2 -0
  141. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  142. package/dist/components/MissingDependency.js +2 -0
  143. package/dist/components/MissingDependency.js.map +1 -0
  144. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js +2 -0
  145. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js.map +1 -0
  146. package/dist/components/ModelViewer.js +2 -0
  147. package/dist/components/ModelViewer.js.map +1 -0
  148. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  149. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  150. package/dist/components/MsiViewer.js +2 -0
  151. package/dist/components/MsiViewer.js.map +1 -0
  152. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  153. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  154. package/dist/components/NotebookViewer.js +2 -0
  155. package/dist/components/NotebookViewer.js.map +1 -0
  156. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  157. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  158. package/dist/components/PdfNavigationPane.js +9 -0
  159. package/dist/components/PdfNavigationPane.js.map +1 -0
  160. package/dist/components/PdfNavigationPane.vue.d.ts +24 -0
  161. package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js +149 -0
  162. package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  163. package/dist/components/PdfViewer.js +3 -1
  164. package/dist/components/PdfViewer.js.map +1 -0
  165. package/dist/components/PdfViewer.vue.d.ts +0 -11
  166. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +133 -69
  167. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  168. package/dist/components/PptxNavigationPane.js +2 -0
  169. package/dist/components/PptxNavigationPane.js.map +1 -0
  170. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +2 -0
  171. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  172. package/dist/components/PptxViewer.js +3 -1
  173. package/dist/components/PptxViewer.js.map +1 -0
  174. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +55 -49
  175. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  176. package/dist/components/PsdViewer.js +2 -0
  177. package/dist/components/PsdViewer.js.map +1 -0
  178. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  179. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  180. package/dist/components/QmcAudioViewer.js +2 -0
  181. package/dist/components/QmcAudioViewer.js.map +1 -0
  182. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  183. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  184. package/dist/components/RasterViewer.js +2 -0
  185. package/dist/components/RasterViewer.js.map +1 -0
  186. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  187. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  188. package/dist/components/RtfViewer.js +2 -0
  189. package/dist/components/RtfViewer.js.map +1 -0
  190. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  191. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  192. package/dist/components/SevenZipBrowser.js +2 -0
  193. package/dist/components/SevenZipBrowser.js.map +1 -0
  194. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  195. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  196. package/dist/components/SqliteViewer.js +2 -0
  197. package/dist/components/SqliteViewer.js.map +1 -0
  198. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  199. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  200. package/dist/components/SubtitleViewer.js +2 -0
  201. package/dist/components/SubtitleViewer.js.map +1 -0
  202. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  203. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  204. package/dist/components/XlsxChart.js +2 -0
  205. package/dist/components/XlsxChart.js.map +1 -0
  206. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +2 -0
  207. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  208. package/dist/components/XlsxViewer.js +2 -0
  209. package/dist/components/XlsxViewer.js.map +1 -0
  210. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  211. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  212. package/dist/components/XmindViewer.js +2 -0
  213. package/dist/components/XmindViewer.js.map +1 -0
  214. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  215. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  216. package/dist/components/ZipBrowser.js +2 -0
  217. package/dist/components/ZipBrowser.js.map +1 -0
  218. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  219. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  220. package/dist/components/ZoomImage.js +3 -1
  221. package/dist/components/ZoomImage.js.map +1 -0
  222. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +82 -80
  223. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -0
  224. package/dist/components/epl/EplCodeBlock.js +2 -0
  225. package/dist/components/epl/EplCodeBlock.js.map +1 -0
  226. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  227. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  228. package/dist/components/epl/EplDoc.js +2 -0
  229. package/dist/components/epl/EplDoc.js.map +1 -0
  230. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js +2 -0
  231. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js.map +1 -0
  232. package/dist/components/epl/EplMethod.js +2 -0
  233. package/dist/components/epl/EplMethod.js.map +1 -0
  234. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js +2 -0
  235. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js.map +1 -0
  236. package/dist/components/epl/EplResource.js +2 -0
  237. package/dist/components/epl/EplResource.js.map +1 -0
  238. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js +2 -0
  239. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -0
  240. package/dist/components/epl/EplTree.js +2 -0
  241. package/dist/components/epl/EplTree.js.map +1 -0
  242. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js +2 -0
  243. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js.map +1 -0
  244. package/dist/components/epl/EplVarTable.js +2 -0
  245. package/dist/components/epl/EplVarTable.js.map +1 -0
  246. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js +2 -0
  247. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  248. package/dist/components/epl/numbering.js +2 -0
  249. package/dist/components/epl/numbering.js.map +1 -0
  250. package/dist/components/icons/Icon.js +2 -0
  251. package/dist/components/icons/Icon.js.map +1 -0
  252. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js +2 -0
  253. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js.map +1 -0
  254. package/dist/components/icons/iconPaths.js +2 -0
  255. package/dist/components/icons/iconPaths.js.map +1 -0
  256. package/dist/components/optionalViewer.js +2 -0
  257. package/dist/components/optionalViewer.js.map +1 -0
  258. package/dist/components/ui/ClampBlock.js +2 -0
  259. package/dist/components/ui/ClampBlock.js.map +1 -0
  260. package/dist/composables/useElementNavigation.js +2 -0
  261. package/dist/composables/useElementNavigation.js.map +1 -0
  262. package/dist/composables/useFileViewer.js +2 -0
  263. package/dist/composables/useFileViewer.js.map +1 -0
  264. package/dist/composables/useLightbox.js +2 -0
  265. package/dist/composables/useLightbox.js.map +1 -0
  266. package/dist/composables/useLogRules.js +2 -0
  267. package/dist/composables/useLogRules.js.map +1 -0
  268. package/dist/lib/aes.js +2 -0
  269. package/dist/lib/aes.js.map +1 -0
  270. package/dist/lib/appx.js +2 -0
  271. package/dist/lib/appx.js.map +1 -0
  272. package/dist/lib/archiveHtml.js +2 -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.js +2 -0
  303. package/dist/lib/docxNavigation.js.map +1 -0
  304. package/dist/lib/dxf.js +2 -0
  305. package/dist/lib/dxf.js.map +1 -0
  306. package/dist/lib/eml.js +2 -0
  307. package/dist/lib/eml.js.map +1 -0
  308. package/dist/lib/encoding.js +2 -0
  309. package/dist/lib/encoding.js.map +1 -0
  310. package/dist/lib/epl/code/codeDataParser.js +2 -0
  311. package/dist/lib/epl/code/codeDataParser.js.map +1 -0
  312. package/dist/lib/epl/code/codeLines.js +2 -0
  313. package/dist/lib/epl/code/codeLines.js.map +1 -0
  314. package/dist/lib/epl/code/textCode.js +2 -0
  315. package/dist/lib/epl/code/textCode.js.map +1 -0
  316. package/dist/lib/epl/eplDocument.js +2 -0
  317. package/dist/lib/epl/eplDocument.js.map +1 -0
  318. package/dist/lib/epl/imageList.js +2 -0
  319. package/dist/lib/epl/imageList.js.map +1 -0
  320. package/dist/lib/epl/index.js +2 -0
  321. package/dist/lib/epl/index.js.map +1 -0
  322. package/dist/lib/epl/io/binaryReader.js +2 -0
  323. package/dist/lib/epl/io/binaryReader.js.map +1 -0
  324. package/dist/lib/epl/io/encoding.js +2 -0
  325. package/dist/lib/epl/io/encoding.js.map +1 -0
  326. package/dist/lib/epl/model/eplSystemId.js +2 -0
  327. package/dist/lib/epl/model/eplSystemId.js.map +1 -0
  328. package/dist/lib/epl/names/eplLibData.js +2 -0
  329. package/dist/lib/epl/names/eplLibData.js.map +1 -0
  330. package/dist/lib/epl/names/idToNameMap.js +2 -0
  331. package/dist/lib/epl/names/idToNameMap.js.map +1 -0
  332. package/dist/lib/epl/names/libNameProvider.js +2 -0
  333. package/dist/lib/epl/names/libNameProvider.js.map +1 -0
  334. package/dist/lib/epl/program.js +2 -0
  335. package/dist/lib/epl/program.js.map +1 -0
  336. package/dist/lib/epl/projectFileReader.js +2 -0
  337. package/dist/lib/epl/projectFileReader.js.map +1 -0
  338. package/dist/lib/fb2.js +2 -0
  339. package/dist/lib/fb2.js.map +1 -0
  340. package/dist/lib/fileType.js +2 -0
  341. package/dist/lib/fileType.js.map +1 -0
  342. package/dist/lib/fonts.js +2 -0
  343. package/dist/lib/fonts.js.map +1 -0
  344. package/dist/lib/format.js +2 -0
  345. package/dist/lib/format.js.map +1 -0
  346. package/dist/lib/frontmatter.js +2 -0
  347. package/dist/lib/frontmatter.js.map +1 -0
  348. package/dist/lib/geo.js +2 -0
  349. package/dist/lib/geo.js.map +1 -0
  350. package/dist/lib/har.js +2 -0
  351. package/dist/lib/har.js.map +1 -0
  352. package/dist/lib/highlight.js +2 -0
  353. package/dist/lib/highlight.js.map +1 -0
  354. package/dist/lib/highlightLangs.js +2 -0
  355. package/dist/lib/highlightLangs.js.map +1 -0
  356. package/dist/lib/hprof.js +2 -0
  357. package/dist/lib/hprof.js.map +1 -0
  358. package/dist/lib/ipynb.js +2 -0
  359. package/dist/lib/ipynb.js.map +1 -0
  360. package/dist/lib/jks.js +2 -0
  361. package/dist/lib/jks.js.map +1 -0
  362. package/dist/lib/jsonl.js +2 -0
  363. package/dist/lib/jsonl.js.map +1 -0
  364. package/dist/lib/lrc.js +2 -0
  365. package/dist/lib/lrc.js.map +1 -0
  366. package/dist/lib/lzma.js +2 -0
  367. package/dist/lib/lzma.js.map +1 -0
  368. package/dist/lib/markdown/blocks.js +2 -0
  369. package/dist/lib/markdown/blocks.js.map +1 -0
  370. package/dist/lib/markdown/inline.js +2 -0
  371. package/dist/lib/markdown/inline.js.map +1 -0
  372. package/dist/lib/markdown/math.js +2 -0
  373. package/dist/lib/markdown/math.js.map +1 -0
  374. package/dist/lib/markdown/segmentize.js +2 -0
  375. package/dist/lib/markdown/segmentize.js.map +1 -0
  376. package/dist/lib/markdown/styles.js +2 -0
  377. package/dist/lib/markdown/styles.js.map +1 -0
  378. package/dist/lib/mht.js +2 -0
  379. package/dist/lib/mht.js.map +1 -0
  380. package/dist/lib/midi.js +2 -0
  381. package/dist/lib/midi.js.map +1 -0
  382. package/dist/lib/mime.js +2 -0
  383. package/dist/lib/mime.js.map +1 -0
  384. package/dist/lib/mindmap.js +2 -0
  385. package/dist/lib/mindmap.js.map +1 -0
  386. package/dist/lib/mobi.js +2 -0
  387. package/dist/lib/mobi.js.map +1 -0
  388. package/dist/lib/msi.js +2 -0
  389. package/dist/lib/msi.js.map +1 -0
  390. package/dist/lib/optionalDep.js +2 -0
  391. package/dist/lib/optionalDep.js.map +1 -0
  392. package/dist/lib/pdfNavigation.d.ts +11 -0
  393. package/dist/lib/pdfNavigation.js +32 -0
  394. package/dist/lib/pdfNavigation.js.map +1 -0
  395. package/dist/lib/pkcs12.js +2 -0
  396. package/dist/lib/pkcs12.js.map +1 -0
  397. package/dist/lib/pptxNavigation.js +2 -0
  398. package/dist/lib/pptxNavigation.js.map +1 -0
  399. package/dist/lib/qmc.js +2 -0
  400. package/dist/lib/qmc.js.map +1 -0
  401. package/dist/lib/rar.js +2 -0
  402. package/dist/lib/rar.js.map +1 -0
  403. package/dist/lib/rawPreview.js +2 -0
  404. package/dist/lib/rawPreview.js.map +1 -0
  405. package/dist/lib/rc2.js +2 -0
  406. package/dist/lib/rc2.js.map +1 -0
  407. package/dist/lib/resolveInput.js +2 -0
  408. package/dist/lib/resolveInput.js.map +1 -0
  409. package/dist/lib/rtf.js +2 -0
  410. package/dist/lib/rtf.js.map +1 -0
  411. package/dist/lib/sevenZip.js +2 -0
  412. package/dist/lib/sevenZip.js.map +1 -0
  413. package/dist/lib/sha1.js +2 -0
  414. package/dist/lib/sha1.js.map +1 -0
  415. package/dist/lib/sha256.js +2 -0
  416. package/dist/lib/sha256.js.map +1 -0
  417. package/dist/lib/speechAudio.js +2 -0
  418. package/dist/lib/speechAudio.js.map +1 -0
  419. package/dist/lib/sqlite.js +2 -0
  420. package/dist/lib/sqlite.js.map +1 -0
  421. package/dist/lib/subtitle.js +2 -0
  422. package/dist/lib/subtitle.js.map +1 -0
  423. package/dist/lib/tarball.js +2 -0
  424. package/dist/lib/tarball.js.map +1 -0
  425. package/dist/lib/viewerFormats.js +2 -0
  426. package/dist/lib/viewerFormats.js.map +1 -0
  427. package/dist/lib/viewerKind.js +2 -0
  428. package/dist/lib/viewerKind.js.map +1 -0
  429. package/dist/lib/x509.js +2 -0
  430. package/dist/lib/x509.js.map +1 -0
  431. package/dist/lib/xlsx.js +2 -0
  432. package/dist/lib/xlsx.js.map +1 -0
  433. package/dist/lib/xlsxCharts.js +2 -0
  434. package/dist/lib/xlsxCharts.js.map +1 -0
  435. package/dist/lib/xmind.js +2 -0
  436. package/dist/lib/xmind.js.map +1 -0
  437. package/dist/lib/zip.js +2 -0
  438. package/dist/lib/zip.js.map +1 -0
  439. package/dist/style.css +1 -1
  440. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MindMapView.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/MindMapView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'\nimport { fileExt } from '@/lib/fileType'\nimport { parseMindMap, type MindMap, type MindNode } from '@/lib/mindmap'\n\n/**\n * The side viewer's mind-map mode: render a parsed OPML / FreeMind tree (see lib/mindmap.ts) as an interactive,\n * hand-rolled SVG mind map — no third-party graph library (consistent with the codebase's hand-rolled renderers).\n *\n * Layout is a tidy left-to-right tree (root at the left, branches growing right) — the clearest fit for a narrow\n * side panel, and what web mind-map viewers (e.g. markmap) do. FreeMind's left/right `POSITION` split is folded\n * into a single right-growing tree on purpose. Nodes wrap to a max width and pack vertically (leaves take the next\n * slot; a parent centers on its children). Click a node with children to collapse/expand; drag the background to\n * pan, wheel to zoom (around the cursor), and the toolbar zooms / fits. The XML source stays one toggle away in\n * the panel header.\n *\n * Text is sized deterministically (a CJK-aware per-char width estimate, no DOM measurement) so layout matches the\n * rendered `<tspan>`s without a measure pass — SSR-safe and reflow-free.\n */\n// `text` drives the OPML/FreeMind path (parsed here); `map` lets a caller (e.g. XmindViewer) hand in an\n// already-parsed tree so the same renderer draws XMind too.\nconst props = defineProps<{ text?: string; name: string; map?: MindMap }>()\n\n// Geometry constants (px). Exposed to the template via <script setup> top-level bindings.\nconst FONT = 13\nconst LINE_H = 19\nconst PAD_X = 11\nconst PAD_Y = 7\nconst MAX_W = 240 // max inner (text) width before wrapping\nconst MIN_W = 22\nconst H_GAP = 46 // horizontal gap between depth columns\nconst V_GAP = 14 // vertical gap between sibling subtrees\n\n// Depth-cycling palette for edges + branch accents (used when a node has no explicit color).\nconst PALETTE = [\n '#6ea8fe',\n '#63e6be',\n '#ffd43b',\n '#ff8787',\n '#da77f2',\n '#4dabf7',\n '#ffa94d',\n '#9775fa',\n]\n\nconst baseName = computed(() => {\n const n = props.name || ''\n const e = fileExt(n)\n return e ? n.slice(0, n.length - e.length - 1) : n\n})\n\n// Parse is memoized by `props.text`; while the text is unchanged the same tree (and thus node ids) is returned,\n// so the collapse Set stays valid. A parse failure is surfaced as `error`.\nconst result = computed<{ map?: MindMap; error?: string }>(() => {\n if (props.map) return { map: props.map }\n if (!props.text) return {}\n try {\n const ext = fileExt(props.name).toLowerCase()\n return { map: parseMindMap(props.text, ext === 'opml' ? 'opml' : 'mm', baseName.value) }\n } catch (e) {\n return { error: e instanceof Error ? e.message : String(e) }\n }\n})\n\n// Collapsed node ids (reactive Set — Vue tracks has/add/delete, so the layout recomputes on toggle).\nconst collapsed = reactive(new Set<number>())\n\nfunction seedFolded(n: MindNode): void {\n if (n.foldedInit && n.children.length) collapsed.add(n.id)\n for (const c of n.children) seedFolded(c)\n}\n\n// New file (or first parse): reset collapse to the authored fold state and fit the view once laid out.\nwatch(\n () => result.value.map,\n (map) => {\n collapsed.clear()\n if (map) seedFolded(map.root)\n void nextTick(fit)\n },\n { immediate: true },\n)\n\n// ---- Layout --------------------------------------------------------------------------------------------------\ninterface LaidNode {\n node: MindNode\n x: number\n y: number // vertical center\n w: number\n h: number\n depth: number\n lines: string[]\n hasChildren: boolean\n collapsed: boolean\n}\ninterface LaidEdge {\n x1: number\n y1: number\n x2: number\n y2: number\n color: string\n}\n\n// CJK / fullwidth / emoji read as ~1em; Latin as ~0.56em. Good enough to size boxes without measuring the DOM.\nfunction isWide(ch: string): boolean {\n const c = ch.codePointAt(0) ?? 0\n return (\n (c >= 0x1100 && c <= 0x115f) ||\n (c >= 0x2e80 && c <= 0xa4cf) ||\n (c >= 0xac00 && c <= 0xd7a3) ||\n (c >= 0xf900 && c <= 0xfaff) ||\n (c >= 0xfe30 && c <= 0xfe4f) ||\n (c >= 0xff00 && c <= 0xff60) ||\n (c >= 0xffe0 && c <= 0xffe6) ||\n (c >= 0x1f300 && c <= 0x1faff)\n )\n}\nfunction strW(s: string): number {\n let w = 0\n for (const ch of s) w += isWide(ch) ? FONT * 1.02 : FONT * 0.56\n return w\n}\n// Break a line into wrap units: each wide char is its own unit; Latin runs break after spaces / - / _ / /.\nfunction units(line: string): string[] {\n const out: string[] = []\n let buf = ''\n for (const ch of line) {\n if (isWide(ch)) {\n if (buf) {\n out.push(buf)\n buf = ''\n }\n out.push(ch)\n } else {\n buf += ch\n if (ch === ' ' || ch === '-' || ch === '_' || ch === '/') {\n out.push(buf)\n buf = ''\n }\n }\n }\n if (buf) out.push(buf)\n return out\n}\nfunction wrapText(text: string): { lines: string[]; width: number } {\n const src = (text || '').replace(/\\s+/g, ' ').trim() || '(无标题)'\n const lines: string[] = []\n let cur = ''\n let curW = 0\n for (const u of units(src)) {\n const uw = strW(u)\n if (uw > MAX_W) {\n // A single unit wider than the box (e.g. a long URL) — hard-break by characters.\n if (cur) {\n lines.push(cur.trimEnd())\n cur = ''\n curW = 0\n }\n let part = ''\n for (const ch of u) {\n if (strW(part + ch) > MAX_W) {\n lines.push(part)\n part = ch\n } else part += ch\n }\n cur = part\n curW = strW(part)\n continue\n }\n if (cur && curW + uw > MAX_W) {\n lines.push(cur.trimEnd())\n cur = u.trimStart()\n curW = strW(cur)\n } else {\n cur += u\n curW += uw\n }\n }\n if (cur.trim()) lines.push(cur.trimEnd())\n if (!lines.length) lines.push('(无标题)')\n let width = 0\n for (const ln of lines) width = Math.max(width, strW(ln))\n return { lines, width: Math.max(MIN_W, Math.min(MAX_W, Math.ceil(width))) }\n}\n\nconst layout = computed(() => {\n const map = result.value.map\n if (!map) return { nodes: [] as LaidNode[], edges: [] as LaidEdge[], width: 0, height: 0 }\n\n // Measure pass: text → wrapped lines + box size, pruning collapsed subtrees.\n interface M {\n node: MindNode\n lines: string[]\n w: number\n h: number\n depth: number\n children: M[]\n hasChildren: boolean\n }\n function build(node: MindNode, depth: number): M {\n const { lines, width } = wrapText(node.text)\n const isCollapsed = collapsed.has(node.id)\n const children =\n !isCollapsed && node.children.length ? node.children.map((c) => build(c, depth + 1)) : []\n return {\n node,\n lines,\n w: width + PAD_X * 2,\n h: lines.length * LINE_H + PAD_Y * 2,\n depth,\n children,\n hasChildren: node.children.length > 0,\n }\n }\n const rootM = build(map.root, 0)\n\n // Column x: each depth starts after the previous column's widest box.\n const colW: number[] = []\n ;(function scan(m: M) {\n colW[m.depth] = Math.max(colW[m.depth] ?? 0, m.w)\n m.children.forEach(scan)\n })(rootM)\n const colX: number[] = [0]\n for (let d = 1; d < colW.length; d++) colX[d] = colX[d - 1] + (colW[d - 1] ?? 0) + H_GAP\n\n // Vertical packing: leaves take the running cursor; parents center on their children.\n const nodes: LaidNode[] = []\n const edges: LaidEdge[] = []\n let cursor = 0\n function place(m: M): number {\n const x = colX[m.depth]\n let y: number\n if (m.children.length === 0) {\n y = cursor + m.h / 2\n cursor += m.h + V_GAP\n } else {\n const ys = m.children.map(place)\n y = (ys[0] + ys[ys.length - 1]) / 2\n for (let i = 0; i < m.children.length; i++) {\n const cm = m.children[i]\n edges.push({\n x1: x + m.w,\n y1: y,\n x2: colX[cm.depth],\n y2: ys[i],\n color: cm.node.color || cm.node.bg || PALETTE[cm.depth % PALETTE.length],\n })\n }\n }\n nodes.push({\n node: m.node,\n x,\n y,\n w: m.w,\n h: m.h,\n depth: m.depth,\n lines: m.lines,\n hasChildren: m.hasChildren,\n collapsed: collapsed.has(m.node.id),\n })\n return y\n }\n place(rootM)\n\n let width = 0\n let height = 0\n for (const n of nodes) {\n width = Math.max(width, n.x + n.w)\n height = Math.max(height, n.y + n.h / 2)\n }\n return { nodes, edges, width, height }\n})\n\nfunction edgePath(e: LaidEdge): string {\n const dx = Math.max(18, (e.x2 - e.x1) * 0.5)\n return `M${e.x1},${e.y1} C${e.x1 + dx},${e.y1} ${e.x2 - dx},${e.y2} ${e.x2},${e.y2}`\n}\nfunction boxStyle(n: LaidNode): Record<string, string> {\n const s: Record<string, string> = {}\n if (n.node.bg) s.fill = n.node.bg\n if (n.node.color) s.stroke = n.node.color\n return s\n}\nfunction textStyle(n: LaidNode): Record<string, string> {\n return n.node.color ? { fill: n.node.color } : {}\n}\n\n// ---- Pan / zoom ----------------------------------------------------------------------------------------------\nconst viewport = ref<HTMLElement>()\nconst tx = ref(0)\nconst ty = ref(0)\nconst k = ref(1)\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.min(hi, Math.max(lo, v))\n}\n\n// Fit the laid-out content into the viewport (centered, a touch of padding), capped so tiny maps aren't huge.\nfunction fit(): void {\n const el = viewport.value\n const L = layout.value\n if (!el || !L.nodes.length) return\n const pad = 24\n const cw = el.clientWidth || 1\n const ch = el.clientHeight || 1\n const scale = clamp(\n Math.min((cw - pad * 2) / Math.max(L.width, 1), (ch - pad * 2) / Math.max(L.height, 1)),\n 0.2,\n 1.25,\n )\n k.value = scale\n tx.value = (cw - L.width * scale) / 2\n ty.value = (ch - L.height * scale) / 2\n}\n\nfunction zoomAround(cx: number, cy: number, factor: number): void {\n const nk = clamp(k.value * factor, 0.1, 4)\n const wx = (cx - tx.value) / k.value\n const wy = (cy - ty.value) / k.value\n tx.value = cx - wx * nk\n ty.value = cy - wy * nk\n k.value = nk\n}\nfunction zoomBy(factor: number): void {\n const el = viewport.value\n if (!el) return\n zoomAround(el.clientWidth / 2, el.clientHeight / 2, factor)\n}\nfunction onWheel(e: WheelEvent): void {\n e.preventDefault()\n const el = viewport.value\n if (!el) return\n const r = el.getBoundingClientRect()\n zoomAround(e.clientX - r.left, e.clientY - r.top, Math.exp(-e.deltaY * 0.0015))\n}\n\n// Pan only when the drag starts on empty canvas, so a click that lands on a node toggles it instead of panning.\nlet panning = false\nlet sx = 0\nlet sy = 0\nlet stx = 0\nlet sty = 0\nfunction onBgPointerDown(e: PointerEvent): void {\n if (e.button !== 0) return\n if ((e.target as Element)?.closest('[data-node]')) return\n panning = true\n sx = e.clientX\n sy = e.clientY\n stx = tx.value\n sty = ty.value\n ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)\n}\nfunction onPointerMove(e: PointerEvent): void {\n if (!panning) return\n tx.value = stx + (e.clientX - sx)\n ty.value = sty + (e.clientY - sy)\n}\nfunction onPointerUp(e: PointerEvent): void {\n if (!panning) return\n panning = false\n try {\n ;(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId)\n } catch {\n /* capture already released */\n }\n}\n\nfunction toggle(n: LaidNode): void {\n if (!n.hasChildren) return\n if (collapsed.has(n.node.id)) collapsed.delete(n.node.id)\n else collapsed.add(n.node.id)\n}\n\nconst summary = computed(() => {\n const map = result.value.map\n if (!map) return ''\n const fmt = map.format === 'opml' ? 'OPML' : map.format === 'xmind' ? 'XMind' : 'FreeMind'\n return `${fmt} · ${map.count} 个节点`\n})\n\n// Refit when the panel is resized (the splitter drag changes our width).\nlet ro: ResizeObserver | undefined\nonMounted(() => {\n fit()\n if (typeof ResizeObserver !== 'undefined' && viewport.value) {\n ro = new ResizeObserver(() => fit())\n ro.observe(viewport.value)\n }\n})\nonBeforeUnmount(() => ro?.disconnect())\n</script>\n\n<template>\n <div class=\"mm\">\n <p v-if=\"result.error\" class=\"mm__msg mm__msg--error\">{{ result.error }}</p>\n <p v-else-if=\"!layout.nodes.length\" class=\"mm__msg\">空的思维导图</p>\n\n <template v-else>\n <div\n ref=\"viewport\"\n class=\"mm__canvas\"\n :class=\"{ 'mm__canvas--panning': panning }\"\n @wheel=\"onWheel\"\n @pointerdown=\"onBgPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointerleave=\"onPointerUp\"\n >\n <svg class=\"mm__svg\" width=\"100%\" height=\"100%\">\n <g :transform=\"`translate(${tx},${ty}) scale(${k})`\">\n <path\n v-for=\"(e, i) in layout.edges\"\n :key=\"'e' + i\"\n class=\"mm__edge\"\n :d=\"edgePath(e)\"\n :stroke=\"e.color\"\n />\n <g\n v-for=\"n in layout.nodes\"\n :key=\"n.node.id\"\n data-node\n :transform=\"`translate(${n.x},${n.y - n.h / 2})`\"\n :class=\"{ 'mm__node--clickable': n.hasChildren }\"\n >\n <rect\n class=\"mm__box\"\n :class=\"{\n 'mm__box--root': n.depth === 0,\n 'mm__box--branch': n.depth > 0 && n.hasChildren,\n }\"\n :width=\"n.w\"\n :height=\"n.h\"\n rx=\"8\"\n :style=\"boxStyle(n)\"\n @click=\"toggle(n)\"\n />\n <!-- each line is centered in its LINE_H band (dominant-baseline:central) → the whole block,\n box height = lines*LINE_H + 2*PAD_Y, sits vertically centered without a measure pass -->\n <text class=\"mm__text\" :style=\"textStyle(n)\" @click=\"toggle(n)\">\n <tspan\n v-for=\"(ln, li) in n.lines\"\n :key=\"li\"\n :x=\"PAD_X\"\n :y=\"PAD_Y + li * LINE_H + LINE_H / 2\"\n >\n {{ ln }}\n </tspan>\n </text>\n <title v-if=\"n.node.note\">{{ n.node.note }}</title>\n\n <!-- collapse/expand pip on the right edge (only when the node has children).\n The +/− are drawn as stroked lines (not glyphs) so they stay perfectly centered in the\n circle regardless of font metrics. -->\n <g v-if=\"n.hasChildren\" class=\"mm__pip\" @click.stop=\"toggle(n)\">\n <circle :cx=\"n.w\" :cy=\"n.h / 2\" r=\"7.5\" />\n <line class=\"mm__pipsign\" :x1=\"n.w - 4\" :y1=\"n.h / 2\" :x2=\"n.w + 4\" :y2=\"n.h / 2\" />\n <line\n v-if=\"n.collapsed\"\n class=\"mm__pipsign\"\n :x1=\"n.w\"\n :y1=\"n.h / 2 - 4\"\n :x2=\"n.w\"\n :y2=\"n.h / 2 + 4\"\n />\n <text\n v-if=\"n.collapsed\"\n class=\"mm__pipcount\"\n :x=\"n.w + 12\"\n :y=\"n.h / 2\"\n dominant-baseline=\"central\"\n >\n {{ n.node.children.length }}\n </text>\n </g>\n\n <!-- external link glyph (top-right), opens in a new tab without triggering the toggle -->\n <a\n v-if=\"n.node.link\"\n :href=\"n.node.link\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n @pointerdown.stop\n >\n <text class=\"mm__link\" :x=\"n.w - 5\" y=\"-3\" text-anchor=\"end\">↗</text>\n </a>\n </g>\n </g>\n </svg>\n </div>\n\n <div class=\"mm__toolbar\">\n <span class=\"mm__summary\">{{ summary }}</span>\n <span class=\"mm__zoom\">\n <button type=\"button\" class=\"mm__tbtn\" title=\"放大\" @click=\"zoomBy(1.2)\">+</button>\n <button type=\"button\" class=\"mm__tbtn\" title=\"缩小\" @click=\"zoomBy(1 / 1.2)\">−</button>\n <button type=\"button\" class=\"mm__tbtn mm__tbtn--text\" title=\"适应窗口\" @click=\"fit\">\n 适应\n </button>\n </span>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.mm {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-2);\n}\n.mm__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mm__msg--error {\n color: var(--danger);\n}\n\n.mm__canvas {\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n cursor: grab;\n touch-action: none;\n}\n.mm__canvas--panning {\n cursor: grabbing;\n}\n.mm__svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n/* Edges: stroke color comes from the node/depth palette (inline `stroke`); width/fill fixed here. */\n.mm__edge {\n fill: none;\n stroke-width: 2;\n stroke-opacity: 0.55;\n}\n\n.mm__node--clickable .mm__box,\n.mm__node--clickable .mm__text {\n cursor: pointer;\n}\n.mm__box {\n fill: var(--surface);\n stroke: var(--border-2);\n stroke-width: 1.2;\n transition: filter 0.12s;\n}\n.mm__node--clickable:hover .mm__box {\n filter: brightness(1.04);\n stroke: var(--accent);\n}\n.mm__box--root {\n fill: var(--accent);\n stroke: var(--accent);\n}\n.mm__box--branch {\n stroke-width: 1.5;\n}\n.mm__text {\n font-family: var(--font-sans, inherit);\n font-size: 13px;\n fill: var(--text);\n dominant-baseline: central;\n}\n/* Root box is filled with the accent → its label must read on that fill. */\n.mm__box--root + .mm__text {\n fill: #fff;\n font-weight: 600;\n}\n\n.mm__pip {\n cursor: pointer;\n}\n.mm__pip circle {\n fill: var(--surface);\n stroke: var(--border-2);\n stroke-width: 1.2;\n}\n.mm__pip:hover circle {\n stroke: var(--accent);\n}\n.mm__pipsign {\n stroke: var(--muted);\n stroke-width: 1.6;\n stroke-linecap: round;\n}\n.mm__pip:hover .mm__pipsign {\n stroke: var(--accent);\n}\n.mm__pipcount {\n font-family: var(--font-mono, monospace);\n font-size: 10.5px;\n fill: var(--faint);\n user-select: none;\n}\n\n.mm__link {\n font-size: 12px;\n fill: var(--accent);\n cursor: pointer;\n}\n\n/* Floating toolbar, bottom-right of the canvas. */\n.mm__toolbar {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 7px 12px;\n background: color-mix(in srgb, var(--surface) 86%, transparent);\n border-top: 1px solid var(--border);\n backdrop-filter: blur(4px);\n}\n.mm__summary {\n font-size: 11.5px;\n letter-spacing: 0.02em;\n color: var(--faint);\n}\n.mm__zoom {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.mm__tbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 26px;\n padding: 0 6px;\n font-family: inherit;\n font-size: 14px;\n line-height: 1;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 7px;\n cursor: pointer;\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n.mm__tbtn:hover {\n color: var(--accent);\n border-color: var(--accent);\n}\n.mm__tbtn--text {\n font-size: 12px;\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "../_virtual/_plugin-vue_export-helper.js";
5
5
  var n = /*#__PURE__*/ t(e, [["__scopeId", "data-v-edb397a0"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=MissingDependency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MissingDependency.js","names":[],"sources":["../../src/components/MissingDependency.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\n/**\n * Shown when an OPTIONAL peer dependency for a format isn't installed in the consumer's app (see\n * docs/file-viewer-package-design.md §4). Tells the user which package to install to enable this viewer,\n * offers a one-click copy of the `npm i …` command, and keeps the file downloadable in the meantime.\n */\nconst props = defineProps<{\n /** The missing npm package name(s). */\n pkg: string | string[]\n /** Human label for the format (e.g. \"PDF\", \"Word 文档\"). */\n label?: string\n /** Download URL for the raw file, so it stays reachable even without a viewer. */\n downloadUrl?: string\n downloadName?: string\n}>()\n\nconst pkgs = computed(() => (Array.isArray(props.pkg) ? props.pkg : [props.pkg]))\nconst installCmd = computed(() => `npm i ${pkgs.value.join(' ')}`)\n\nconst copied = ref(false)\nlet timer: ReturnType<typeof setTimeout> | undefined\nasync function copyCmd(): Promise<void> {\n try {\n await navigator.clipboard?.writeText(installCmd.value)\n copied.value = true\n clearTimeout(timer)\n timer = setTimeout(() => (copied.value = false), 1400)\n } catch {\n /* clipboard unavailable — ignore */\n }\n}\n</script>\n\n<template>\n <div class=\"missdep\">\n <div class=\"missdep__icon\" aria-hidden=\"true\">\n <svg\n viewBox=\"0 0 24 24\"\n width=\"34\"\n height=\"34\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n >\n <path\n d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"\n />\n <path d=\"M12 8v4\" stroke-linecap=\"round\" />\n <path d=\"M12 16h.01\" stroke-linecap=\"round\" />\n </svg>\n </div>\n <p class=\"missdep__title\">预览{{ label ? ` ${label} ` : '此格式' }}需要额外依赖</p>\n <p class=\"missdep__sub\">该格式查看器依赖以下未安装的 npm 包,请在你的项目中安装后重试:</p>\n <div class=\"missdep__cmd\">\n <code>{{ installCmd }}</code>\n <button type=\"button\" class=\"missdep__copy\" @click=\"copyCmd\">\n {{ copied ? '已复制' : '复制' }}\n </button>\n </div>\n <a v-if=\"downloadUrl\" class=\"missdep__dl\" :href=\"downloadUrl\" :download=\"downloadName\"\n >下载原文件</a\n >\n </div>\n</template>\n\n<style scoped>\n.missdep {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 32px;\n text-align: center;\n color: var(--faint);\n}\n.missdep__icon {\n color: var(--faint);\n}\n.missdep__title {\n margin: 4px 0 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.missdep__sub {\n margin: 0;\n max-width: 340px;\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--muted);\n}\n.missdep__cmd {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n padding: 7px 8px 7px 12px;\n background: var(--code-bg);\n border: 1px solid var(--code-border);\n border-radius: 8px;\n}\n.missdep__cmd code {\n font-family: var(--font-mono);\n font-size: 12.5px;\n color: var(--text);\n}\n.missdep__copy {\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.missdep__copy:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.missdep__dl {\n margin-top: 10px;\n font-size: 12.5px;\n color: var(--muted);\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.missdep__dl:hover {\n color: var(--accent);\n}\n</style>\n"],"mappings":""}
@@ -35,3 +35,5 @@ var l = { class: "missdep" }, u = { class: "missdep__title" }, d = { class: "mis
35
35
  });
36
36
  //#endregion
37
37
  export { p as default };
38
+
39
+ //# sourceMappingURL=MissingDependency.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MissingDependency.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/MissingDependency.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\n/**\n * Shown when an OPTIONAL peer dependency for a format isn't installed in the consumer's app (see\n * docs/file-viewer-package-design.md §4). Tells the user which package to install to enable this viewer,\n * offers a one-click copy of the `npm i …` command, and keeps the file downloadable in the meantime.\n */\nconst props = defineProps<{\n /** The missing npm package name(s). */\n pkg: string | string[]\n /** Human label for the format (e.g. \"PDF\", \"Word 文档\"). */\n label?: string\n /** Download URL for the raw file, so it stays reachable even without a viewer. */\n downloadUrl?: string\n downloadName?: string\n}>()\n\nconst pkgs = computed(() => (Array.isArray(props.pkg) ? props.pkg : [props.pkg]))\nconst installCmd = computed(() => `npm i ${pkgs.value.join(' ')}`)\n\nconst copied = ref(false)\nlet timer: ReturnType<typeof setTimeout> | undefined\nasync function copyCmd(): Promise<void> {\n try {\n await navigator.clipboard?.writeText(installCmd.value)\n copied.value = true\n clearTimeout(timer)\n timer = setTimeout(() => (copied.value = false), 1400)\n } catch {\n /* clipboard unavailable — ignore */\n }\n}\n</script>\n\n<template>\n <div class=\"missdep\">\n <div class=\"missdep__icon\" aria-hidden=\"true\">\n <svg\n viewBox=\"0 0 24 24\"\n width=\"34\"\n height=\"34\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n >\n <path\n d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"\n />\n <path d=\"M12 8v4\" stroke-linecap=\"round\" />\n <path d=\"M12 16h.01\" stroke-linecap=\"round\" />\n </svg>\n </div>\n <p class=\"missdep__title\">预览{{ label ? ` ${label} ` : '此格式' }}需要额外依赖</p>\n <p class=\"missdep__sub\">该格式查看器依赖以下未安装的 npm 包,请在你的项目中安装后重试:</p>\n <div class=\"missdep__cmd\">\n <code>{{ installCmd }}</code>\n <button type=\"button\" class=\"missdep__copy\" @click=\"copyCmd\">\n {{ copied ? '已复制' : '复制' }}\n </button>\n </div>\n <a v-if=\"downloadUrl\" class=\"missdep__dl\" :href=\"downloadUrl\" :download=\"downloadName\"\n >下载原文件</a\n >\n </div>\n</template>\n\n<style scoped>\n.missdep {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 32px;\n text-align: center;\n color: var(--faint);\n}\n.missdep__icon {\n color: var(--faint);\n}\n.missdep__title {\n margin: 4px 0 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.missdep__sub {\n margin: 0;\n max-width: 340px;\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--muted);\n}\n.missdep__cmd {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n padding: 7px 8px 7px 12px;\n background: var(--code-bg);\n border: 1px solid var(--code-border);\n border-radius: 8px;\n}\n.missdep__cmd code {\n font-family: var(--font-mono);\n font-size: 12.5px;\n color: var(--text);\n}\n.missdep__copy {\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.missdep__copy:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.missdep__dl {\n margin-top: 10px;\n font-size: 12.5px;\n color: var(--muted);\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.missdep__dl:hover {\n color: var(--accent);\n}\n</style>\n"],"mappings":";;;;;;;;;;;EAQA,IAAM,IAAQ,GAUR,IAAO,QAAgB,MAAM,QAAQ,EAAM,GAAG,IAAI,EAAM,MAAM,CAAC,EAAM,GAAG,CAAE,GAC1E,IAAa,QAAe,SAAS,EAAK,MAAM,KAAK,GAAG,GAAG,GAE3D,IAAS,EAAI,EAAK,GACpB;EACJ,eAAe,IAAyB;GACtC,IAAI;IAIF,AAHA,MAAM,UAAU,WAAW,UAAU,EAAW,KAAK,GACrD,EAAO,QAAQ,IACf,aAAa,CAAK,GAClB,IAAQ,iBAAkB,EAAO,QAAQ,IAAQ,IAAI;GACvD,QAAQ,CAER;EACF;yBAIE,EA4BM,OA5BN,GA4BM;;GAXJ,EAAwE,KAAxE,GAA0B,OAAE,EAAG,EAAA,QAAK,IAAO,EAAA,MAAK,KAAA,KAAA,IAAc,UAAM,CAAA;YACpE,EAA8D,KAAA,EAA3D,OAAM,eAAc,GAAC,sCAAkC,EAAA;GAC1D,EAKM,OALN,GAKM,CAJJ,EAA6B,QAAA,MAAA,EAApB,EAAA,KAAU,GAAA,CAAA,GACnB,EAES,UAAA;IAFD,MAAK;IAAS,OAAM;IAAiB,SAAO;QAC/C,EAAA,QAAM,QAAA,IAAA,GAAA,CAAA,CAAA,CAAA;GAGJ,EAAA,eAAA,EAAA,GAAT,EAEC,KAAA;;IAFqB,OAAM;IAAe,MAAM,EAAA;IAAc,UAAU,EAAA;MACtE,SAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA"}
@@ -5,3 +5,5 @@ import t from "./ModelViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-6d7b6e6f"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=ModelViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelViewer.js","names":[],"sources":["../../src/components/ModelViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\nimport { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { fileExt } from '@/lib/fileType'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer 3D model renderer. Fetches the model bytes and renders them with three.js (async-loaded so the\n * ~1MB library only ships when a model is opened): STL / PLY / OBJ / glTF / GLB. Auto-centers and frames the\n * model, lights it, and gives OrbitControls (drag to rotate, wheel to zoom, right-drag to pan).\n *\n * Best-effort by format: STL/PLY/OBJ are self-contained. A `.glb` is fully embedded and renders cleanly; a\n * `.gltf` that references external `.bin`/textures can't resolve them here (we only hold the one file), so it\n * may render untextured or fail — download it with its assets to view fully. No materials library for OBJ\n * (.mtl isn't fetched), so OBJ shows in a neutral material.\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 host = ref<HTMLElement | null>(null)\n\n// Models can be large (dense meshes); gate very large files behind a confirm (everything decodes in-memory).\nconst SIZE_LIMIT = 96 * 1024 * 1024\n\n// three.js handles — kept out of Vue reactivity on purpose (mutated every animation frame).\nlet renderer: THREE.WebGLRenderer | null = null\nlet scene: THREE.Scene | null = null\nlet camera: THREE.PerspectiveCamera | null = null\nlet controls: OrbitControls | null = null\nlet current: THREE.Object3D | null = null\nlet raf = 0\nlet ro: ResizeObserver | null = null\n\nfunction disposeObject(obj: THREE.Object3D): void {\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.geometry) mesh.geometry.dispose()\n const mat = mesh.material\n if (Array.isArray(mat)) mat.forEach((m) => m.dispose())\n else if (mat) mat.dispose()\n })\n}\n\nfunction clearModel(): void {\n if (current && scene) {\n scene.remove(current)\n disposeObject(current)\n current = null\n }\n}\n\nfunction initScene(): void {\n const el = host.value\n if (!el || renderer) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n\n scene = new THREE.Scene()\n camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 2000)\n camera.position.set(2, 1.5, 3)\n\n renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })\n renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1))\n renderer.setSize(w, h)\n el.appendChild(renderer.domElement)\n\n scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 1.1))\n const key = new THREE.DirectionalLight(0xffffff, 1.4)\n key.position.set(4, 6, 5)\n scene.add(key)\n const fill = new THREE.DirectionalLight(0xffffff, 0.5)\n fill.position.set(-5, -2, -4)\n scene.add(fill)\n\n controls = new OrbitControls(camera, renderer.domElement)\n controls.enableDamping = true\n controls.dampingFactor = 0.08\n\n ro = new ResizeObserver(onResize)\n ro.observe(el)\n\n const tick = (): void => {\n raf = requestAnimationFrame(tick)\n controls?.update()\n if (renderer && scene && camera) renderer.render(scene, camera)\n }\n tick()\n}\n\nfunction onResize(): void {\n const el = host.value\n if (!el || !renderer || !camera) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n renderer.setSize(w, h)\n camera.aspect = w / h\n camera.updateProjectionMatrix()\n}\n\n// Center the model at the origin and pull the camera back to frame its bounding sphere.\nfunction frame(obj: THREE.Object3D): void {\n if (!camera || !controls) return\n const box = new THREE.Box3().setFromObject(obj)\n const size = box.getSize(new THREE.Vector3())\n const center = box.getCenter(new THREE.Vector3())\n obj.position.sub(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n const fov = (camera.fov * Math.PI) / 180\n const dist = (maxDim / (2 * Math.tan(fov / 2))) * 1.6\n camera.position.set(dist * 0.6, dist * 0.45, dist)\n camera.near = Math.max(dist / 1000, 0.001)\n camera.far = dist * 1000\n camera.updateProjectionMatrix()\n controls.target.set(0, 0, 0)\n controls.update()\n}\n\nconst NEUTRAL = (): THREE.Material =>\n new THREE.MeshStandardMaterial({\n color: 0x9aa7b4,\n metalness: 0.1,\n roughness: 0.7,\n side: THREE.DoubleSide,\n })\n\nasync function buildObject(buf: ArrayBuffer, ext: string): Promise<THREE.Object3D> {\n if (ext === 'stl') {\n const geom = new STLLoader().parse(buf)\n geom.computeVertexNormals()\n return new THREE.Mesh(geom, NEUTRAL())\n }\n if (ext === 'ply') {\n const geom = new PLYLoader().parse(buf)\n geom.computeVertexNormals()\n const mat = geom.hasAttribute('color')\n ? new THREE.MeshStandardMaterial({\n vertexColors: true,\n side: THREE.DoubleSide,\n roughness: 0.7,\n })\n : NEUTRAL()\n return new THREE.Mesh(geom, mat)\n }\n if (ext === 'obj') {\n const obj = new OBJLoader().parse(new TextDecoder().decode(buf))\n // OBJ without an .mtl comes in with no material — give meshes the neutral one.\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.isMesh) mesh.material = NEUTRAL()\n })\n return obj\n }\n // glTF / GLB\n const data: ArrayBuffer | string = ext === 'gltf' ? new TextDecoder().decode(buf) : buf\n const gltf = await new Promise<{ scene: THREE.Group }>((resolve, reject) => {\n new GLTFLoader().parse(data, '', (g) => resolve(g as unknown as { scene: THREE.Group }), reject)\n })\n return gltf.scene\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 buf = await res.arrayBuffer()\n initScene()\n if (!scene) throw new Error('WebGL 初始化失败')\n clearModel()\n const obj = await buildObject(buf, fileExt(props.name))\n current = obj\n scene.add(obj)\n frame(obj)\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\nonMounted(() => {\n watch(() => props.url, load, { immediate: true })\n})\n\nonBeforeUnmount(() => {\n cancelAnimationFrame(raf)\n ro?.disconnect()\n clearModel()\n controls?.dispose()\n if (renderer) {\n renderer.dispose()\n renderer.domElement.remove()\n }\n renderer = null\n scene = null\n camera = null\n controls = null\n})\n</script>\n\n<template>\n <div class=\"mdl\">\n <p v-if=\"status === 'loading'\" class=\"mdl__msg\">正在加载模型…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"mdl__msg mdl__gate\">\n <p>该模型较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"mdl__msg mdl__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <p class=\"mdl__hint\">\n 外部引用的 .gltf(需配套 .bin/贴图)或压缩网格可能无法在此渲染,可下载后用本地工具打开。\n </p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <!-- the canvas host is always present so three.js can mount into it; overlaid messages sit above it -->\n <div ref=\"host\" class=\"mdl__host\" :class=\"{ 'mdl__host--hidden': status !== 'ready' }\" />\n <div v-if=\"status === 'ready'\" class=\"mdl__hint mdl__hint--float\">\n 拖动旋转 · 滚轮缩放 · 右键平移\n </div>\n </div>\n</template>\n\n<style scoped>\n.mdl {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: radial-gradient(circle at 50% 35%, var(--surface-2), var(--code-bg));\n}\n\n.mdl__msg {\n position: relative;\n z-index: 2;\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mdl__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n color: var(--danger);\n}\n.mdl__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.mdl__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.mdl__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.mdl__host {\n flex: 1 1 auto;\n min-height: 0;\n touch-action: none;\n}\n.mdl__host--hidden {\n visibility: hidden;\n}\n.mdl__host :deep(canvas) {\n display: block;\n}\n\n.mdl__hint {\n color: var(--faint);\n font-size: 12px;\n line-height: 1.5;\n}\n.mdl__hint--float {\n position: absolute;\n left: 12px;\n bottom: 10px;\n z-index: 2;\n padding: 4px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--surface) 70%, transparent);\n pointer-events: none;\n}\n</style>\n"],"mappings":""}
@@ -155,3 +155,5 @@ var b = { class: "mdl" }, x = {
155
155
  });
156
156
  //#endregion
157
157
  export { E as default };
158
+
159
+ //# sourceMappingURL=ModelViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ModelViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\nimport { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { fileExt } from '@/lib/fileType'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer 3D model renderer. Fetches the model bytes and renders them with three.js (async-loaded so the\n * ~1MB library only ships when a model is opened): STL / PLY / OBJ / glTF / GLB. Auto-centers and frames the\n * model, lights it, and gives OrbitControls (drag to rotate, wheel to zoom, right-drag to pan).\n *\n * Best-effort by format: STL/PLY/OBJ are self-contained. A `.glb` is fully embedded and renders cleanly; a\n * `.gltf` that references external `.bin`/textures can't resolve them here (we only hold the one file), so it\n * may render untextured or fail — download it with its assets to view fully. No materials library for OBJ\n * (.mtl isn't fetched), so OBJ shows in a neutral material.\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 host = ref<HTMLElement | null>(null)\n\n// Models can be large (dense meshes); gate very large files behind a confirm (everything decodes in-memory).\nconst SIZE_LIMIT = 96 * 1024 * 1024\n\n// three.js handles — kept out of Vue reactivity on purpose (mutated every animation frame).\nlet renderer: THREE.WebGLRenderer | null = null\nlet scene: THREE.Scene | null = null\nlet camera: THREE.PerspectiveCamera | null = null\nlet controls: OrbitControls | null = null\nlet current: THREE.Object3D | null = null\nlet raf = 0\nlet ro: ResizeObserver | null = null\n\nfunction disposeObject(obj: THREE.Object3D): void {\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.geometry) mesh.geometry.dispose()\n const mat = mesh.material\n if (Array.isArray(mat)) mat.forEach((m) => m.dispose())\n else if (mat) mat.dispose()\n })\n}\n\nfunction clearModel(): void {\n if (current && scene) {\n scene.remove(current)\n disposeObject(current)\n current = null\n }\n}\n\nfunction initScene(): void {\n const el = host.value\n if (!el || renderer) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n\n scene = new THREE.Scene()\n camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 2000)\n camera.position.set(2, 1.5, 3)\n\n renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })\n renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1))\n renderer.setSize(w, h)\n el.appendChild(renderer.domElement)\n\n scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 1.1))\n const key = new THREE.DirectionalLight(0xffffff, 1.4)\n key.position.set(4, 6, 5)\n scene.add(key)\n const fill = new THREE.DirectionalLight(0xffffff, 0.5)\n fill.position.set(-5, -2, -4)\n scene.add(fill)\n\n controls = new OrbitControls(camera, renderer.domElement)\n controls.enableDamping = true\n controls.dampingFactor = 0.08\n\n ro = new ResizeObserver(onResize)\n ro.observe(el)\n\n const tick = (): void => {\n raf = requestAnimationFrame(tick)\n controls?.update()\n if (renderer && scene && camera) renderer.render(scene, camera)\n }\n tick()\n}\n\nfunction onResize(): void {\n const el = host.value\n if (!el || !renderer || !camera) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n renderer.setSize(w, h)\n camera.aspect = w / h\n camera.updateProjectionMatrix()\n}\n\n// Center the model at the origin and pull the camera back to frame its bounding sphere.\nfunction frame(obj: THREE.Object3D): void {\n if (!camera || !controls) return\n const box = new THREE.Box3().setFromObject(obj)\n const size = box.getSize(new THREE.Vector3())\n const center = box.getCenter(new THREE.Vector3())\n obj.position.sub(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n const fov = (camera.fov * Math.PI) / 180\n const dist = (maxDim / (2 * Math.tan(fov / 2))) * 1.6\n camera.position.set(dist * 0.6, dist * 0.45, dist)\n camera.near = Math.max(dist / 1000, 0.001)\n camera.far = dist * 1000\n camera.updateProjectionMatrix()\n controls.target.set(0, 0, 0)\n controls.update()\n}\n\nconst NEUTRAL = (): THREE.Material =>\n new THREE.MeshStandardMaterial({\n color: 0x9aa7b4,\n metalness: 0.1,\n roughness: 0.7,\n side: THREE.DoubleSide,\n })\n\nasync function buildObject(buf: ArrayBuffer, ext: string): Promise<THREE.Object3D> {\n if (ext === 'stl') {\n const geom = new STLLoader().parse(buf)\n geom.computeVertexNormals()\n return new THREE.Mesh(geom, NEUTRAL())\n }\n if (ext === 'ply') {\n const geom = new PLYLoader().parse(buf)\n geom.computeVertexNormals()\n const mat = geom.hasAttribute('color')\n ? new THREE.MeshStandardMaterial({\n vertexColors: true,\n side: THREE.DoubleSide,\n roughness: 0.7,\n })\n : NEUTRAL()\n return new THREE.Mesh(geom, mat)\n }\n if (ext === 'obj') {\n const obj = new OBJLoader().parse(new TextDecoder().decode(buf))\n // OBJ without an .mtl comes in with no material — give meshes the neutral one.\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.isMesh) mesh.material = NEUTRAL()\n })\n return obj\n }\n // glTF / GLB\n const data: ArrayBuffer | string = ext === 'gltf' ? new TextDecoder().decode(buf) : buf\n const gltf = await new Promise<{ scene: THREE.Group }>((resolve, reject) => {\n new GLTFLoader().parse(data, '', (g) => resolve(g as unknown as { scene: THREE.Group }), reject)\n })\n return gltf.scene\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 buf = await res.arrayBuffer()\n initScene()\n if (!scene) throw new Error('WebGL 初始化失败')\n clearModel()\n const obj = await buildObject(buf, fileExt(props.name))\n current = obj\n scene.add(obj)\n frame(obj)\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\nonMounted(() => {\n watch(() => props.url, load, { immediate: true })\n})\n\nonBeforeUnmount(() => {\n cancelAnimationFrame(raf)\n ro?.disconnect()\n clearModel()\n controls?.dispose()\n if (renderer) {\n renderer.dispose()\n renderer.domElement.remove()\n }\n renderer = null\n scene = null\n camera = null\n controls = null\n})\n</script>\n\n<template>\n <div class=\"mdl\">\n <p v-if=\"status === 'loading'\" class=\"mdl__msg\">正在加载模型…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"mdl__msg mdl__gate\">\n <p>该模型较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"mdl__msg mdl__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <p class=\"mdl__hint\">\n 外部引用的 .gltf(需配套 .bin/贴图)或压缩网格可能无法在此渲染,可下载后用本地工具打开。\n </p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <!-- the canvas host is always present so three.js can mount into it; overlaid messages sit above it -->\n <div ref=\"host\" class=\"mdl__host\" :class=\"{ 'mdl__host--hidden': status !== 'ready' }\" />\n <div v-if=\"status === 'ready'\" class=\"mdl__hint mdl__hint--float\">\n 拖动旋转 · 滚轮缩放 · 右键平移\n </div>\n </div>\n</template>\n\n<style scoped>\n.mdl {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: radial-gradient(circle at 50% 35%, var(--surface-2), var(--code-bg));\n}\n\n.mdl__msg {\n position: relative;\n z-index: 2;\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mdl__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n color: var(--danger);\n}\n.mdl__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.mdl__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.mdl__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.mdl__host {\n flex: 1 1 auto;\n min-height: 0;\n touch-action: none;\n}\n.mdl__host--hidden {\n visibility: hidden;\n}\n.mdl__host :deep(canvas) {\n display: block;\n}\n\n.mdl__hint {\n color: var(--faint);\n font-size: 12px;\n line-height: 1.5;\n}\n.mdl__hint--float {\n position: absolute;\n left: 12px;\n bottom: 10px;\n z-index: 2;\n padding: 4px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--surface) 70%, transparent);\n pointer-events: none;\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./MsiViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-0c6afdf2"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=MsiViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MsiViewer.js","names":[],"sources":["../../src/components/MsiViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { parseMsi, NotMsiError, type MsiInfo } from '@/lib/msi'\nimport { formatBytes } from '@/lib/format'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * The side viewer's MSI mode: fetch a Windows Installer `.msi`, parse its OLE compound-file structure\n * entirely in-browser (see lib/msi.ts), and surface the install metadata — the product identity (name /\n * version / codes / publisher) from the Property table, plus the SummaryInformation (platform, package\n * code, authoring tool, dates). Mirrors BinObjBrowser: the bytes come from the same download endpoint as\n * any other file and never leave the browser. An `.msi` is NOT a PE (that's BinObjBrowser) — it's the\n * same OLE2 container legacy Office docs use, which is why this is a separate viewer.\n *\n * Large-file gate: an `.msi` can be tens of MB and parsing needs the whole file in memory, so unless the\n * manifest size is known to be small (≤ LARGE_THRESHOLD) we show a confirm card and only fetch on click.\n */\nconst props = defineProps<{ url: string; name: string; size?: number }>()\n\nconst LARGE_THRESHOLD = 8 * 1024 * 1024 // 8 MB\n\ntype Status = 'confirm' | 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst info = ref<MsiInfo | null>(null)\nconst showProps = ref(false)\nconst showTables = ref(false)\n\nfunction decideInitial(): void {\n info.value = null\n showProps.value = false\n showTables.value = false\n errorMsg.value = ''\n if (props.size != null && props.size <= LARGE_THRESHOLD) void load()\n else status.value = 'confirm'\n}\n\nasync function load(): Promise<void> {\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 info.value = parseMsi(await res.arrayBuffer())\n status.value = 'ready'\n } catch (e) {\n errorMsg.value =\n e instanceof NotMsiError ? e.message : e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// The resolved product-identity rows (only the fields actually present).\nconst identity = computed<{ label: string; value: string }[]>(() => {\n const c = info.value\n if (!c) return []\n const rows: { label: string; value: string }[] = []\n const add = (label: string, v?: string): void => {\n if (v) rows.push({ label, value: v })\n }\n add('产品', c.productName)\n add('版本', c.productVersion)\n add('厂商', c.manufacturer)\n add('语言', c.language)\n add('产品码', c.productCode)\n add('升级码', c.upgradeCode)\n return rows\n})\n\n// Click a property/identity value → copy it; the row briefly shows \"已复制\".\nconst copied = ref('')\nlet copyTimer: ReturnType<typeof setTimeout> | undefined\nasync function copyValue(v: string): Promise<void> {\n try {\n await navigator.clipboard?.writeText(v)\n } catch {\n /* clipboard unavailable — ignore */\n }\n copied.value = v\n clearTimeout(copyTimer)\n copyTimer = setTimeout(() => (copied.value = ''), 1200)\n}\n\nonMounted(decideInitial)\nwatch(() => props.url, decideInitial)\nonBeforeUnmount(() => clearTimeout(copyTimer))\n</script>\n\n<template>\n <div class=\"msi\">\n <!-- large-file gate -->\n <div v-if=\"status === 'confirm'\" class=\"msi__confirm\">\n <Icon name=\"archive\" :size=\"32\" />\n <p class=\"msi__confirm-title\" :title=\"name\">{{ name }}</p>\n <p class=\"msi__confirm-sub\">\n {{\n size != null ? `文件较大(${formatBytes(size)})` : '文件大小未知'\n }},解析需将整个文件下载到浏览器。\n </p>\n <button type=\"button\" class=\"msi__btn\" @click=\"load\">加载并解析安装包</button>\n </div>\n\n <p v-else-if=\"status === 'loading'\" class=\"msi__msg\">正在解析安装包…</p>\n\n <div v-else-if=\"status === 'error'\" class=\"msi__msg msi__msg--error\">\n <p>{{ errorMsg }}</p>\n <button type=\"button\" class=\"msi__retry\" @click=\"load\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready' && info\">\n <div class=\"msi__summary\">\n <span class=\"msi__fmt\">MSI</span>\n <span v-if=\"info.productVersion\">v{{ info.productVersion }}</span>\n <span v-if=\"info.language\">{{ info.language }}</span>\n <span>{{ info.properties.length }} 个属性</span>\n <span>{{ info.tables.length }} 张表</span>\n </div>\n\n <!-- product identity (the headline) -->\n <div v-if=\"identity.length\" class=\"msi__card\">\n <h3 class=\"msi__h\">产品信息</h3>\n <dl class=\"msi__dl\">\n <template v-for=\"(r, i) in identity\" :key=\"i\">\n <dt class=\"msi__dt\">{{ r.label }}</dt>\n <dd class=\"msi__dd\">\n <button\n type=\"button\"\n class=\"msi__copyable\"\n :title=\"`点击复制:${r.value}`\"\n @click=\"copyValue(r.value)\"\n >\n <span>{{ r.value }}</span>\n <span v-if=\"copied === r.value\" class=\"msi__copied\">已复制</span>\n </button>\n </dd>\n </template>\n </dl>\n </div>\n\n <!-- summary information -->\n <div v-if=\"info.summary.length\" class=\"msi__card\">\n <h3 class=\"msi__h\">摘要信息</h3>\n <dl class=\"msi__dl\">\n <template v-for=\"(m, i) in info.summary\" :key=\"i\">\n <dt class=\"msi__dt\">{{ m.label }}</dt>\n <dd class=\"msi__dd msi__dd--plain\">{{ m.value }}</dd>\n </template>\n </dl>\n </div>\n\n <!-- full property table (collapsible — there can be many) -->\n <div v-if=\"info.properties.length\" class=\"msi__section\">\n <button type=\"button\" class=\"msi__toggle\" @click=\"showProps = !showProps\">\n <span class=\"msi__caret\" :class=\"{ 'msi__caret--open': showProps }\">▶</span>\n 全部属性({{ info.properties.length }})\n </button>\n <div v-if=\"showProps\" class=\"msi__props\">\n <button\n v-for=\"(p, i) in info.properties\"\n :key=\"i\"\n type=\"button\"\n class=\"msi__prow\"\n :title=\"`点击复制值:${p.value}`\"\n @click=\"copyValue(p.value)\"\n >\n <span class=\"msi__pname\">{{ p.name }}</span>\n <span class=\"msi__pval\">{{ copied === p.value ? '已复制' : p.value }}</span>\n </button>\n </div>\n </div>\n\n <!-- database tables present (collapsible) -->\n <div v-if=\"info.tables.length\" class=\"msi__section\">\n <button type=\"button\" class=\"msi__toggle\" @click=\"showTables = !showTables\">\n <span class=\"msi__caret\" :class=\"{ 'msi__caret--open': showTables }\">▶</span>\n 数据库表({{ info.tables.length }})\n </button>\n <div v-if=\"showTables\" class=\"msi__tables\">\n <span v-for=\"t in info.tables\" :key=\"t\" class=\"msi__chip\">{{ t }}</span>\n </div>\n </div>\n\n <p v-for=\"(n, i) in info.notes\" :key=\"i\" class=\"msi__note\">{{ n }}</p>\n </template>\n </div>\n</template>\n\n<style scoped>\n.msi {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface);\n}\n\n.msi__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.msi__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.msi__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.msi__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Large-file confirm card. */\n.msi__confirm {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 32px 28px;\n text-align: center;\n color: var(--muted);\n}\n.msi__confirm-title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13.5px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.msi__confirm-sub {\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--muted);\n}\n.msi__btn {\n margin-top: 4px;\n font-family: inherit;\n font-size: 13px;\n color: #fff;\n background: var(--accent);\n border: none;\n border-radius: 8px;\n padding: 8px 18px;\n cursor: pointer;\n}\n.msi__btn:hover {\n filter: brightness(1.06);\n}\n\n/* Summary bar. */\n.msi__summary {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px 12px;\n padding: 9px 16px;\n border-bottom: 1px solid var(--border);\n font-size: 11.5px;\n letter-spacing: 0.02em;\n color: var(--faint);\n}\n.msi__fmt {\n font-weight: 700;\n color: var(--accent);\n}\n\n/* Identity / summary cards. */\n.msi__card {\n flex-shrink: 0;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n}\n.msi__h {\n margin: 0 0 8px;\n font-size: 10.5px;\n font-weight: 700;\n letter-spacing: 0.08em;\n color: var(--faint);\n text-transform: uppercase;\n}\n.msi__dl {\n margin: 0;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 4px 12px;\n align-content: start;\n}\n.msi__dt {\n color: var(--faint);\n font-size: 12px;\n white-space: nowrap;\n}\n.msi__dd {\n margin: 0;\n min-width: 0;\n font-size: 12.5px;\n color: var(--text);\n}\n.msi__dd--plain {\n overflow-wrap: anywhere;\n}\n.msi__copyable {\n display: inline-flex;\n align-items: baseline;\n gap: 8px;\n max-width: 100%;\n padding: 0;\n background: transparent;\n border: none;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n text-align: left;\n cursor: pointer;\n overflow-wrap: anywhere;\n}\n.msi__copyable:hover {\n color: var(--accent);\n}\n.msi__copied {\n flex-shrink: 0;\n font-size: 10.5px;\n color: var(--accent);\n}\n\n/* Collapsible sections (properties / tables). */\n.msi__section {\n flex-shrink: 0;\n border-bottom: 1px solid var(--border);\n}\n.msi__toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 16px;\n background: transparent;\n border: none;\n font-family: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--muted);\n text-align: left;\n cursor: pointer;\n}\n.msi__toggle:hover {\n background: var(--surface-2);\n}\n.msi__caret {\n flex-shrink: 0;\n width: 9px;\n font-size: 9px;\n color: var(--faint);\n transition: transform 0.12s;\n}\n.msi__caret--open {\n transform: rotate(90deg);\n}\n\n.msi__props {\n padding: 2px 0 8px;\n}\n.msi__prow {\n display: flex;\n align-items: baseline;\n gap: 12px;\n width: 100%;\n padding: 4px 16px;\n background: transparent;\n border: none;\n font-family: var(--font-mono);\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n.msi__prow:hover {\n background: var(--surface-2);\n}\n.msi__pname {\n flex-shrink: 0;\n min-width: 38%;\n color: var(--accent);\n overflow-wrap: anywhere;\n}\n.msi__pval {\n flex: 1 1 auto;\n min-width: 0;\n color: var(--text);\n overflow-wrap: anywhere;\n}\n\n.msi__tables {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n padding: 2px 16px 12px;\n}\n.msi__chip {\n padding: 2px 9px;\n border-radius: 999px;\n background: var(--surface-2);\n border: 1px solid var(--border);\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--muted);\n}\n\n.msi__note {\n flex-shrink: 0;\n margin: 0;\n padding: 10px 16px;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface-2);\n}\n</style>\n"],"mappings":""}
@@ -134,3 +134,5 @@ var ee = { class: "msi" }, te = {
134
134
  });
135
135
  //#endregion
136
136
  export { H as default };
137
+
138
+ //# sourceMappingURL=MsiViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MsiViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/MsiViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { parseMsi, NotMsiError, type MsiInfo } from '@/lib/msi'\nimport { formatBytes } from '@/lib/format'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * The side viewer's MSI mode: fetch a Windows Installer `.msi`, parse its OLE compound-file structure\n * entirely in-browser (see lib/msi.ts), and surface the install metadata — the product identity (name /\n * version / codes / publisher) from the Property table, plus the SummaryInformation (platform, package\n * code, authoring tool, dates). Mirrors BinObjBrowser: the bytes come from the same download endpoint as\n * any other file and never leave the browser. An `.msi` is NOT a PE (that's BinObjBrowser) — it's the\n * same OLE2 container legacy Office docs use, which is why this is a separate viewer.\n *\n * Large-file gate: an `.msi` can be tens of MB and parsing needs the whole file in memory, so unless the\n * manifest size is known to be small (≤ LARGE_THRESHOLD) we show a confirm card and only fetch on click.\n */\nconst props = defineProps<{ url: string; name: string; size?: number }>()\n\nconst LARGE_THRESHOLD = 8 * 1024 * 1024 // 8 MB\n\ntype Status = 'confirm' | 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst info = ref<MsiInfo | null>(null)\nconst showProps = ref(false)\nconst showTables = ref(false)\n\nfunction decideInitial(): void {\n info.value = null\n showProps.value = false\n showTables.value = false\n errorMsg.value = ''\n if (props.size != null && props.size <= LARGE_THRESHOLD) void load()\n else status.value = 'confirm'\n}\n\nasync function load(): Promise<void> {\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 info.value = parseMsi(await res.arrayBuffer())\n status.value = 'ready'\n } catch (e) {\n errorMsg.value =\n e instanceof NotMsiError ? e.message : e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// The resolved product-identity rows (only the fields actually present).\nconst identity = computed<{ label: string; value: string }[]>(() => {\n const c = info.value\n if (!c) return []\n const rows: { label: string; value: string }[] = []\n const add = (label: string, v?: string): void => {\n if (v) rows.push({ label, value: v })\n }\n add('产品', c.productName)\n add('版本', c.productVersion)\n add('厂商', c.manufacturer)\n add('语言', c.language)\n add('产品码', c.productCode)\n add('升级码', c.upgradeCode)\n return rows\n})\n\n// Click a property/identity value → copy it; the row briefly shows \"已复制\".\nconst copied = ref('')\nlet copyTimer: ReturnType<typeof setTimeout> | undefined\nasync function copyValue(v: string): Promise<void> {\n try {\n await navigator.clipboard?.writeText(v)\n } catch {\n /* clipboard unavailable — ignore */\n }\n copied.value = v\n clearTimeout(copyTimer)\n copyTimer = setTimeout(() => (copied.value = ''), 1200)\n}\n\nonMounted(decideInitial)\nwatch(() => props.url, decideInitial)\nonBeforeUnmount(() => clearTimeout(copyTimer))\n</script>\n\n<template>\n <div class=\"msi\">\n <!-- large-file gate -->\n <div v-if=\"status === 'confirm'\" class=\"msi__confirm\">\n <Icon name=\"archive\" :size=\"32\" />\n <p class=\"msi__confirm-title\" :title=\"name\">{{ name }}</p>\n <p class=\"msi__confirm-sub\">\n {{\n size != null ? `文件较大(${formatBytes(size)})` : '文件大小未知'\n }},解析需将整个文件下载到浏览器。\n </p>\n <button type=\"button\" class=\"msi__btn\" @click=\"load\">加载并解析安装包</button>\n </div>\n\n <p v-else-if=\"status === 'loading'\" class=\"msi__msg\">正在解析安装包…</p>\n\n <div v-else-if=\"status === 'error'\" class=\"msi__msg msi__msg--error\">\n <p>{{ errorMsg }}</p>\n <button type=\"button\" class=\"msi__retry\" @click=\"load\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready' && info\">\n <div class=\"msi__summary\">\n <span class=\"msi__fmt\">MSI</span>\n <span v-if=\"info.productVersion\">v{{ info.productVersion }}</span>\n <span v-if=\"info.language\">{{ info.language }}</span>\n <span>{{ info.properties.length }} 个属性</span>\n <span>{{ info.tables.length }} 张表</span>\n </div>\n\n <!-- product identity (the headline) -->\n <div v-if=\"identity.length\" class=\"msi__card\">\n <h3 class=\"msi__h\">产品信息</h3>\n <dl class=\"msi__dl\">\n <template v-for=\"(r, i) in identity\" :key=\"i\">\n <dt class=\"msi__dt\">{{ r.label }}</dt>\n <dd class=\"msi__dd\">\n <button\n type=\"button\"\n class=\"msi__copyable\"\n :title=\"`点击复制:${r.value}`\"\n @click=\"copyValue(r.value)\"\n >\n <span>{{ r.value }}</span>\n <span v-if=\"copied === r.value\" class=\"msi__copied\">已复制</span>\n </button>\n </dd>\n </template>\n </dl>\n </div>\n\n <!-- summary information -->\n <div v-if=\"info.summary.length\" class=\"msi__card\">\n <h3 class=\"msi__h\">摘要信息</h3>\n <dl class=\"msi__dl\">\n <template v-for=\"(m, i) in info.summary\" :key=\"i\">\n <dt class=\"msi__dt\">{{ m.label }}</dt>\n <dd class=\"msi__dd msi__dd--plain\">{{ m.value }}</dd>\n </template>\n </dl>\n </div>\n\n <!-- full property table (collapsible — there can be many) -->\n <div v-if=\"info.properties.length\" class=\"msi__section\">\n <button type=\"button\" class=\"msi__toggle\" @click=\"showProps = !showProps\">\n <span class=\"msi__caret\" :class=\"{ 'msi__caret--open': showProps }\">▶</span>\n 全部属性({{ info.properties.length }})\n </button>\n <div v-if=\"showProps\" class=\"msi__props\">\n <button\n v-for=\"(p, i) in info.properties\"\n :key=\"i\"\n type=\"button\"\n class=\"msi__prow\"\n :title=\"`点击复制值:${p.value}`\"\n @click=\"copyValue(p.value)\"\n >\n <span class=\"msi__pname\">{{ p.name }}</span>\n <span class=\"msi__pval\">{{ copied === p.value ? '已复制' : p.value }}</span>\n </button>\n </div>\n </div>\n\n <!-- database tables present (collapsible) -->\n <div v-if=\"info.tables.length\" class=\"msi__section\">\n <button type=\"button\" class=\"msi__toggle\" @click=\"showTables = !showTables\">\n <span class=\"msi__caret\" :class=\"{ 'msi__caret--open': showTables }\">▶</span>\n 数据库表({{ info.tables.length }})\n </button>\n <div v-if=\"showTables\" class=\"msi__tables\">\n <span v-for=\"t in info.tables\" :key=\"t\" class=\"msi__chip\">{{ t }}</span>\n </div>\n </div>\n\n <p v-for=\"(n, i) in info.notes\" :key=\"i\" class=\"msi__note\">{{ n }}</p>\n </template>\n </div>\n</template>\n\n<style scoped>\n.msi {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface);\n}\n\n.msi__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.msi__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.msi__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.msi__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Large-file confirm card. */\n.msi__confirm {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 32px 28px;\n text-align: center;\n color: var(--muted);\n}\n.msi__confirm-title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13.5px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.msi__confirm-sub {\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--muted);\n}\n.msi__btn {\n margin-top: 4px;\n font-family: inherit;\n font-size: 13px;\n color: #fff;\n background: var(--accent);\n border: none;\n border-radius: 8px;\n padding: 8px 18px;\n cursor: pointer;\n}\n.msi__btn:hover {\n filter: brightness(1.06);\n}\n\n/* Summary bar. */\n.msi__summary {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px 12px;\n padding: 9px 16px;\n border-bottom: 1px solid var(--border);\n font-size: 11.5px;\n letter-spacing: 0.02em;\n color: var(--faint);\n}\n.msi__fmt {\n font-weight: 700;\n color: var(--accent);\n}\n\n/* Identity / summary cards. */\n.msi__card {\n flex-shrink: 0;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n}\n.msi__h {\n margin: 0 0 8px;\n font-size: 10.5px;\n font-weight: 700;\n letter-spacing: 0.08em;\n color: var(--faint);\n text-transform: uppercase;\n}\n.msi__dl {\n margin: 0;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 4px 12px;\n align-content: start;\n}\n.msi__dt {\n color: var(--faint);\n font-size: 12px;\n white-space: nowrap;\n}\n.msi__dd {\n margin: 0;\n min-width: 0;\n font-size: 12.5px;\n color: var(--text);\n}\n.msi__dd--plain {\n overflow-wrap: anywhere;\n}\n.msi__copyable {\n display: inline-flex;\n align-items: baseline;\n gap: 8px;\n max-width: 100%;\n padding: 0;\n background: transparent;\n border: none;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n text-align: left;\n cursor: pointer;\n overflow-wrap: anywhere;\n}\n.msi__copyable:hover {\n color: var(--accent);\n}\n.msi__copied {\n flex-shrink: 0;\n font-size: 10.5px;\n color: var(--accent);\n}\n\n/* Collapsible sections (properties / tables). */\n.msi__section {\n flex-shrink: 0;\n border-bottom: 1px solid var(--border);\n}\n.msi__toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 16px;\n background: transparent;\n border: none;\n font-family: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--muted);\n text-align: left;\n cursor: pointer;\n}\n.msi__toggle:hover {\n background: var(--surface-2);\n}\n.msi__caret {\n flex-shrink: 0;\n width: 9px;\n font-size: 9px;\n color: var(--faint);\n transition: transform 0.12s;\n}\n.msi__caret--open {\n transform: rotate(90deg);\n}\n\n.msi__props {\n padding: 2px 0 8px;\n}\n.msi__prow {\n display: flex;\n align-items: baseline;\n gap: 12px;\n width: 100%;\n padding: 4px 16px;\n background: transparent;\n border: none;\n font-family: var(--font-mono);\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n.msi__prow:hover {\n background: var(--surface-2);\n}\n.msi__pname {\n flex-shrink: 0;\n min-width: 38%;\n color: var(--accent);\n overflow-wrap: anywhere;\n}\n.msi__pval {\n flex: 1 1 auto;\n min-width: 0;\n color: var(--text);\n overflow-wrap: anywhere;\n}\n\n.msi__tables {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n padding: 2px 16px 12px;\n}\n.msi__chip {\n padding: 2px 9px;\n border-radius: 999px;\n background: var(--surface-2);\n border: 1px solid var(--border);\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--muted);\n}\n\n.msi__note {\n flex-shrink: 0;\n margin: 0;\n padding: 10px 16px;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface-2);\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./NotebookViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-3419a6e8"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=NotebookViewer.js.map
@@ -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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"}
@@ -0,0 +1,9 @@
1
+ import e from "../_virtual/_plugin-vue_export-helper.js";
2
+ import t from "./PdfNavigationPane.vue_vue_type_script_setup_true_lang.js";
3
+ /* empty css */
4
+ //#region src/components/PdfNavigationPane.vue
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-68918933"]]);
6
+ //#endregion
7
+ export { n as default };
8
+
9
+ //# sourceMappingURL=PdfNavigationPane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdfNavigationPane.js","names":[],"sources":["../../src/components/PdfNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PdfNavigationItem } from '../lib/pdfNavigation'\n\nconst props = defineProps<{\n outlineItems: readonly PdfNavigationItem[]\n pageCount: number\n pageLabels: readonly string[]\n pageThumbnails: readonly (string | null)[]\n activePage: number\n}>()\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\nconst emit = defineEmits<{\n navigate: [pageNumber: number]\n}>()\n\nconst listEl = useTemplateRef<HTMLElement>('listEl')\nconst autoScrollPending = shallowRef(false)\nconst hasOutline = computed(() => props.outlineItems.length > 0)\nconst hasThumbnails = computed(() => props.pageThumbnails.some(Boolean))\nconst panelClasses = computed(() => ({\n 'pn--collapsed': collapsed.value,\n 'pn--outline': hasOutline.value && view.value === 'outline',\n 'pn--compact': view.value === 'pages' && !hasThumbnails.value,\n}))\n\nconst activeOutlineId = computed(() => {\n let active: PdfNavigationItem | undefined\n for (const item of props.outlineItems) {\n if (item.pageNumber === null || item.pageNumber > props.activePage) continue\n const activePageNumber = active?.pageNumber ?? 0\n if (\n !active ||\n item.pageNumber > activePageNumber ||\n (item.pageNumber === activePageNumber && item.level > active.level)\n )\n active = item\n }\n return active?.id ?? ''\n})\n\nfunction pageLabel(pageNumber: number): string {\n return props.pageLabels[pageNumber - 1] || `第 ${pageNumber} 页`\n}\n\nfunction navigate(pageNumber: number | null): void {\n if (pageNumber !== null) emit('navigate', pageNumber)\n}\n\nasync function keepActiveItemVisible(): Promise<void> {\n if (collapsed.value || autoScrollPending.value) return\n autoScrollPending.value = true\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>('.pn__item--active')\n ?.scrollIntoView({ block: 'nearest' })\n autoScrollPending.value = false\n}\n\nwatch(\n () => props.outlineItems.length,\n (count) => {\n if (!count && view.value === 'outline') view.value = 'pages'\n },\n { immediate: true },\n)\nwatch(() => [props.activePage, view.value, collapsed.value], keepActiveItemVisible)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"panelClasses\" aria-label=\"PDF 导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开 PDF 导航\"\n title=\"展开 PDF 导航\"\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 v-if=\"hasOutline\" class=\"pn__tabs\" role=\"tablist\" aria-label=\"导航类型\">\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'outline' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'pages' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n <div v-else class=\"pn__meta\">\n <strong class=\"pn__title\">页面</strong>\n <span class=\"pn__count\">{{ pageCount }} 页</span>\n </div>\n <button\n class=\"pn__collapse\"\n type=\"button\"\n title=\"收起 PDF 导航\"\n aria-label=\"收起 PDF 导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"listEl\" class=\"pn__list\" aria-label=\"PDF 导航列表\">\n <template v-if=\"view === 'outline' && hasOutline\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n class=\"pn__item pn__outline\"\n :class=\"{\n 'pn__item--active': item.id === activeOutlineId,\n 'pn__item--disabled': item.pageNumber === null,\n }\"\n :style=\"{ '--pn-level': item.level }\"\n type=\"button\"\n :disabled=\"item.pageNumber === null\"\n :title=\"item.title\"\n :aria-current=\"item.id === activeOutlineId ? 'location' : undefined\"\n @click=\"navigate(item.pageNumber)\"\n >\n <span class=\"pn__outline-title\">{{ item.title }}</span>\n <span v-if=\"item.pageNumber !== null\" class=\"pn__outline-page\">{{\n item.pageNumber\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <button\n v-for=\"pageNumber in pageCount\"\n :key=\"pageNumber\"\n class=\"pn__item pn__page\"\n :class=\"{\n 'pn__item--active': pageNumber === activePage,\n 'pn__page--fallback': !pageThumbnails[pageNumber - 1],\n }\"\n type=\"button\"\n :data-nav-page-number=\"pageNumber\"\n :aria-current=\"pageNumber === activePage ? 'page' : undefined\"\n :title=\"`跳转到第 ${pageNumber} 页`\"\n @click=\"navigate(pageNumber)\"\n >\n <template v-if=\"pageThumbnails[pageNumber - 1]\">\n <span class=\"pn__preview-wrap\">\n <img\n class=\"pn__preview\"\n :src=\"pageThumbnails[pageNumber - 1] || ''\"\n alt=\"\"\n loading=\"lazy\"\n draggable=\"false\"\n />\n </span>\n <span class=\"pn__number\">{{ pageNumber }}</span>\n </template>\n <span v-else class=\"pn__sheet\">{{ pageNumber }}</span>\n <span class=\"pn__page-label\">{{ pageLabel(pageNumber) }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n width: 142px;\n min-width: 0;\n display: flex;\n flex: 0 0 142px;\n flex-direction: column;\n color: var(--muted);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n border-right: 1px solid var(--border);\n transition:\n flex-basis 160ms ease,\n width 160ms ease;\n}\n\n.pn--outline {\n width: 190px;\n flex-basis: 190px;\n}\n\n.pn--compact {\n width: 96px;\n flex-basis: 96px;\n}\n\n.pn--collapsed {\n width: 0;\n flex-basis: 0;\n border-right: 0;\n}\n\n.pn__header {\n min-height: 45px;\n padding: 7px 7px 7px 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n\n.pn__tabs {\n min-width: 0;\n display: flex;\n flex: 1;\n gap: 3px;\n}\n\n.pn__tab,\n.pn__collapse,\n.pn__reopen,\n.pn__item {\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n\n.pn__tab {\n position: relative;\n min-width: 0;\n padding: 7px 5px;\n flex: 1;\n color: var(--faint);\n font-size: 11.5px;\n border-radius: 6px;\n}\n\n.pn__tab:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__tab--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n\n.pn__tab--active::after {\n position: absolute;\n right: 10px;\n bottom: 2px;\n left: 10px;\n height: 2px;\n content: '';\n background: var(--accent);\n border-radius: 2px;\n}\n\n.pn__meta {\n min-width: 0;\n}\n\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n flex: none;\n place-items: center;\n border-radius: 7px;\n}\n\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\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 color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__list {\n min-height: 0;\n padding: 8px 7px 22px;\n flex: 1 1 auto;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.pn__item {\n width: 100%;\n min-width: 0;\n color: var(--muted);\n text-align: left;\n border: 1px solid transparent;\n border-radius: 8px;\n}\n\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n}\n\n.pn__item--disabled {\n cursor: default;\n opacity: 0.52;\n}\n\n.pn__item--disabled:hover {\n color: var(--muted);\n background: transparent;\n}\n\n.pn__outline {\n gap: 8px;\n padding: 7px 8px 7px calc(9px + min(var(--pn-level), 5) * 14px);\n display: flex;\n align-items: center;\n margin: 1px 0;\n font-size: 12px;\n line-height: 1.35;\n}\n\n.pn__outline-title {\n min-width: 0;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.pn__outline-page {\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n\n.pn__page {\n position: relative;\n padding: 5px;\n display: block;\n margin: 0 0 8px;\n}\n\n.pn__preview-wrap {\n min-width: 0;\n min-height: 62px;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 4px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\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\n.pn__preview {\n width: 104px;\n max-width: 100%;\n max-height: 142px;\n display: block;\n object-fit: contain;\n pointer-events: none;\n}\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 color: #fff;\n font-size: 9.5px;\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n background: rgba(20, 20, 20, 0.68);\n border-radius: 5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n\n.pn__item--active .pn__number {\n background: var(--accent);\n}\n\n.pn__page-label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n color: inherit;\n font-size: 10.5px;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.pn__page--fallback {\n padding: 5px;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 5px;\n}\n\n.pn__sheet {\n width: 28px;\n height: 38px;\n display: grid;\n flex: none;\n place-items: center;\n color: #526072;\n font-size: 9.5px;\n font-variant-numeric: tabular-nums;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 3px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__sheet {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.pn__page--fallback .pn__page-label {\n margin-top: 0;\n flex: 1;\n}\n\n@container (max-width: 500px) {\n .pn {\n width: 124px;\n flex-basis: 124px;\n }\n\n .pn--outline {\n width: 158px;\n flex-basis: 158px;\n }\n\n .pn--compact {\n width: 84px;\n flex-basis: 84px;\n }\n\n .pn--collapsed {\n width: 0;\n flex-basis: 0;\n }\n\n .pn__preview {\n max-height: 126px;\n }\n}\n</style>\n"],"mappings":""}
@@ -0,0 +1,24 @@
1
+ import type { PdfNavigationItem } from '../lib/pdfNavigation';
2
+ type __VLS_Props = {
3
+ outlineItems: readonly PdfNavigationItem[];
4
+ pageCount: number;
5
+ pageLabels: readonly string[];
6
+ pageThumbnails: readonly (string | null)[];
7
+ activePage: number;
8
+ };
9
+ type __VLS_ModelProps = {
10
+ 'collapsed'?: boolean;
11
+ 'view'?: 'outline' | 'pages';
12
+ };
13
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
14
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ navigate: (pageNumber: number) => any;
16
+ "update:collapsed": (value: boolean) => any;
17
+ "update:view": (value: "outline" | "pages") => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
19
+ onNavigate?: ((pageNumber: number) => any) | undefined;
20
+ "onUpdate:collapsed"?: ((value: boolean) => any) | undefined;
21
+ "onUpdate:view"?: ((value: "outline" | "pages") => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,149 @@
1
+ import e from "./icons/Icon.js";
2
+ import { Fragment as t, computed as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createVNode as o, defineComponent as s, mergeModels as c, nextTick as l, normalizeClass as u, normalizeStyle as d, openBlock as f, renderList as p, shallowRef as m, toDisplayString as h, useModel as g, useTemplateRef as _, watch as v } from "vue";
3
+ //#region src/components/PdfNavigationPane.vue?vue&type=script&setup=true&lang.ts
4
+ var y = { class: "pn__header" }, b = {
5
+ key: 0,
6
+ class: "pn__tabs",
7
+ role: "tablist",
8
+ "aria-label": "导航类型"
9
+ }, x = ["aria-selected"], S = ["aria-selected"], C = {
10
+ key: 1,
11
+ class: "pn__meta"
12
+ }, w = { class: "pn__count" }, T = [
13
+ "disabled",
14
+ "title",
15
+ "aria-current",
16
+ "onClick"
17
+ ], E = { class: "pn__outline-title" }, D = {
18
+ key: 0,
19
+ class: "pn__outline-page"
20
+ }, O = [
21
+ "data-nav-page-number",
22
+ "aria-current",
23
+ "title",
24
+ "onClick"
25
+ ], k = { class: "pn__preview-wrap" }, A = ["src"], j = { class: "pn__number" }, M = {
26
+ key: 1,
27
+ class: "pn__sheet"
28
+ }, N = { class: "pn__page-label" }, P = /*@__PURE__*/ s({
29
+ __name: "PdfNavigationPane",
30
+ props: /*@__PURE__*/ c({
31
+ outlineItems: {},
32
+ pageCount: {},
33
+ pageLabels: {},
34
+ pageThumbnails: {},
35
+ activePage: {}
36
+ }, {
37
+ collapsed: {
38
+ type: Boolean,
39
+ default: !1
40
+ },
41
+ collapsedModifiers: {},
42
+ view: { default: "outline" },
43
+ viewModifiers: {}
44
+ }),
45
+ emits: /*@__PURE__*/ c(["navigate"], ["update:collapsed", "update:view"]),
46
+ setup(s, { emit: c }) {
47
+ let P = s, F = g(s, "collapsed"), I = g(s, "view"), L = c, R = _("listEl"), z = m(!1), B = n(() => P.outlineItems.length > 0), V = n(() => P.pageThumbnails.some(Boolean)), H = n(() => ({
48
+ "pn--collapsed": F.value,
49
+ "pn--outline": B.value && I.value === "outline",
50
+ "pn--compact": I.value === "pages" && !V.value
51
+ })), U = n(() => {
52
+ let e;
53
+ for (let t of P.outlineItems) {
54
+ if (t.pageNumber === null || t.pageNumber > P.activePage) continue;
55
+ let n = e?.pageNumber ?? 0;
56
+ (!e || t.pageNumber > n || t.pageNumber === n && t.level > e.level) && (e = t);
57
+ }
58
+ return e?.id ?? "";
59
+ });
60
+ function W(e) {
61
+ return P.pageLabels[e - 1] || `第 ${e} 页`;
62
+ }
63
+ function G(e) {
64
+ e !== null && L("navigate", e);
65
+ }
66
+ async function K() {
67
+ F.value || z.value || (z.value = !0, await l(), R.value?.querySelector(".pn__item--active")?.scrollIntoView({ block: "nearest" }), z.value = !1);
68
+ }
69
+ return v(() => P.outlineItems.length, (e) => {
70
+ !e && I.value === "outline" && (I.value = "pages");
71
+ }, { immediate: !0 }), v(() => [
72
+ P.activePage,
73
+ I.value,
74
+ F.value
75
+ ], K), (n, c) => (f(), i("aside", {
76
+ class: u(["pn", H.value]),
77
+ "aria-label": "PDF 导航"
78
+ }, [F.value ? (f(), i("button", {
79
+ key: 0,
80
+ type: "button",
81
+ class: "pn__reopen",
82
+ "aria-label": "展开 PDF 导航",
83
+ title: "展开 PDF 导航",
84
+ onClick: c[0] ||= (e) => F.value = !1
85
+ }, [o(e, {
86
+ name: "chevronRight",
87
+ size: 15
88
+ })])) : (f(), i(t, { key: 1 }, [a("header", y, [B.value ? (f(), i("div", b, [a("button", {
89
+ class: u(["pn__tab", { "pn__tab--active": I.value === "outline" }]),
90
+ type: "button",
91
+ role: "tab",
92
+ "aria-selected": I.value === "outline",
93
+ onClick: c[1] ||= (e) => I.value = "outline"
94
+ }, " 目录 ", 10, x), a("button", {
95
+ class: u(["pn__tab", { "pn__tab--active": I.value === "pages" }]),
96
+ type: "button",
97
+ role: "tab",
98
+ "aria-selected": I.value === "pages",
99
+ onClick: c[2] ||= (e) => I.value = "pages"
100
+ }, " 页面 ", 10, S)])) : (f(), i("div", C, [c[4] ||= a("strong", { class: "pn__title" }, "页面", -1), a("span", w, h(s.pageCount) + " 页", 1)])), a("button", {
101
+ class: "pn__collapse",
102
+ type: "button",
103
+ title: "收起 PDF 导航",
104
+ "aria-label": "收起 PDF 导航",
105
+ onClick: c[3] ||= (e) => F.value = !0
106
+ }, [o(e, {
107
+ name: "chevronLeft",
108
+ size: 15
109
+ })])]), a("nav", {
110
+ ref_key: "listEl",
111
+ ref: R,
112
+ class: "pn__list",
113
+ "aria-label": "PDF 导航列表"
114
+ }, [I.value === "outline" && B.value ? (f(!0), i(t, { key: 0 }, p(s.outlineItems, (e) => (f(), i("button", {
115
+ key: e.id,
116
+ class: u(["pn__item pn__outline", {
117
+ "pn__item--active": e.id === U.value,
118
+ "pn__item--disabled": e.pageNumber === null
119
+ }]),
120
+ style: d({ "--pn-level": e.level }),
121
+ type: "button",
122
+ disabled: e.pageNumber === null,
123
+ title: e.title,
124
+ "aria-current": e.id === U.value ? "location" : void 0,
125
+ onClick: (t) => G(e.pageNumber)
126
+ }, [a("span", E, h(e.title), 1), e.pageNumber === null ? r("", !0) : (f(), i("span", D, h(e.pageNumber), 1))], 14, T))), 128)) : (f(!0), i(t, { key: 1 }, p(s.pageCount, (e) => (f(), i("button", {
127
+ key: e,
128
+ class: u(["pn__item pn__page", {
129
+ "pn__item--active": e === s.activePage,
130
+ "pn__page--fallback": !s.pageThumbnails[e - 1]
131
+ }]),
132
+ type: "button",
133
+ "data-nav-page-number": e,
134
+ "aria-current": e === s.activePage ? "page" : void 0,
135
+ title: `跳转到第 ${e} 页`,
136
+ onClick: (t) => G(e)
137
+ }, [s.pageThumbnails[e - 1] ? (f(), i(t, { key: 0 }, [a("span", k, [a("img", {
138
+ class: "pn__preview",
139
+ src: s.pageThumbnails[e - 1] || "",
140
+ alt: "",
141
+ loading: "lazy",
142
+ draggable: "false"
143
+ }, null, 8, A)]), a("span", j, h(e), 1)], 64)) : (f(), i("span", M, h(e), 1)), a("span", N, h(W(e)), 1)], 10, O))), 128))], 512)], 64))], 2));
144
+ }
145
+ });
146
+ //#endregion
147
+ export { P as default };
148
+
149
+ //# sourceMappingURL=PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map