@l1yp/file-viewer 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +2 -0
  2. package/dist/FileViewer.js +2 -0
  3. package/dist/FileViewer.js.map +1 -0
  4. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  5. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  6. package/dist/components/AppxViewer.js +2 -0
  7. package/dist/components/AppxViewer.js.map +1 -0
  8. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  9. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  10. package/dist/components/ArchiveBrowser.js +2 -0
  11. package/dist/components/ArchiveBrowser.js.map +1 -0
  12. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  13. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/AudioViewer.js +2 -0
  15. package/dist/components/AudioViewer.js.map +1 -0
  16. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  17. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  18. package/dist/components/BinObjBrowser.js +2 -0
  19. package/dist/components/BinObjBrowser.js.map +1 -0
  20. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  21. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  22. package/dist/components/CertViewer.js +2 -0
  23. package/dist/components/CertViewer.js.map +1 -0
  24. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  25. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  26. package/dist/components/ClassViewer.js +2 -0
  27. package/dist/components/ClassViewer.js.map +1 -0
  28. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  29. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  30. package/dist/components/CodeBlock.js +2 -0
  31. package/dist/components/CodeBlock.js.map +1 -0
  32. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  33. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  34. package/dist/components/CodeView.js +2 -0
  35. package/dist/components/CodeView.js.map +1 -0
  36. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +2 -0
  37. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  38. package/dist/components/CsvViewer.js +2 -0
  39. package/dist/components/CsvViewer.js.map +1 -0
  40. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  41. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  42. package/dist/components/DicomViewer.js +2 -0
  43. package/dist/components/DicomViewer.js.map +1 -0
  44. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  45. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  46. package/dist/components/DocxNavigationPane.js +9 -0
  47. package/dist/components/DocxNavigationPane.js.map +1 -0
  48. package/dist/components/DocxNavigationPane.vue.d.ts +23 -0
  49. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +98 -0
  50. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/DocxViewer.js +3 -1
  52. package/dist/components/DocxViewer.js.map +1 -0
  53. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +72 -37
  54. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/DrawioViewer.js +2 -0
  56. package/dist/components/DrawioViewer.js.map +1 -0
  57. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  58. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/DxfViewer.js +2 -0
  60. package/dist/components/DxfViewer.js.map +1 -0
  61. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  62. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/EbookViewer.js +2 -0
  64. package/dist/components/EbookViewer.js.map +1 -0
  65. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  66. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/EmlViewer.js +2 -0
  68. package/dist/components/EmlViewer.js.map +1 -0
  69. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  70. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/EplViewer.js +2 -0
  72. package/dist/components/EplViewer.js.map +1 -0
  73. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  74. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/FileViewerBody.js +3 -1
  76. package/dist/components/FileViewerBody.js.map +1 -0
  77. package/dist/components/FileViewerBody.vue.d.ts +5 -0
  78. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +250 -247
  79. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -0
  80. package/dist/components/FileViewerPanel.js +2 -0
  81. package/dist/components/FileViewerPanel.js.map +1 -0
  82. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +2 -0
  83. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  84. package/dist/components/FontViewer.js +2 -0
  85. package/dist/components/FontViewer.js.map +1 -0
  86. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  87. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  88. package/dist/components/FrontmatterCard.js +2 -0
  89. package/dist/components/FrontmatterCard.js.map +1 -0
  90. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js +2 -0
  91. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  92. package/dist/components/GeoViewer.js +2 -0
  93. package/dist/components/GeoViewer.js.map +1 -0
  94. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  95. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  96. package/dist/components/HarViewer.js +2 -0
  97. package/dist/components/HarViewer.js.map +1 -0
  98. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  99. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  100. package/dist/components/HexViewer.js +2 -0
  101. package/dist/components/HexViewer.js.map +1 -0
  102. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  103. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  104. package/dist/components/HprofViewer.js +2 -0
  105. package/dist/components/HprofViewer.js.map +1 -0
  106. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  107. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  108. package/dist/components/ImageLightbox.js +2 -0
  109. package/dist/components/ImageLightbox.js.map +1 -0
  110. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +2 -0
  111. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -0
  112. package/dist/components/InlineText.js +2 -0
  113. package/dist/components/InlineText.js.map +1 -0
  114. package/dist/components/JksViewer.js +2 -0
  115. package/dist/components/JksViewer.js.map +1 -0
  116. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  117. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  118. package/dist/components/JsonlViewer.js +2 -0
  119. package/dist/components/JsonlViewer.js.map +1 -0
  120. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  121. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  122. package/dist/components/LogViewer.js +2 -0
  123. package/dist/components/LogViewer.js.map +1 -0
  124. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  125. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  126. package/dist/components/LrcViewer.js +2 -0
  127. package/dist/components/LrcViewer.js.map +1 -0
  128. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  129. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  130. package/dist/components/MarkdownTable.js +2 -0
  131. package/dist/components/MarkdownTable.js.map +1 -0
  132. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js +2 -0
  133. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  134. package/dist/components/MarkdownText.js +2 -0
  135. package/dist/components/MarkdownText.js.map +1 -0
  136. package/dist/components/MhtViewer.js +2 -0
  137. package/dist/components/MhtViewer.js.map +1 -0
  138. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  139. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  140. package/dist/components/MindMapView.js +2 -0
  141. package/dist/components/MindMapView.js.map +1 -0
  142. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js +2 -0
  143. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  144. package/dist/components/MissingDependency.js +2 -0
  145. package/dist/components/MissingDependency.js.map +1 -0
  146. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js +2 -0
  147. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js.map +1 -0
  148. package/dist/components/ModelViewer.js +2 -0
  149. package/dist/components/ModelViewer.js.map +1 -0
  150. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  151. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  152. package/dist/components/MsiViewer.js +2 -0
  153. package/dist/components/MsiViewer.js.map +1 -0
  154. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  155. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  156. package/dist/components/NotebookViewer.js +2 -0
  157. package/dist/components/NotebookViewer.js.map +1 -0
  158. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  159. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  160. package/dist/components/PdfViewer.js +2 -0
  161. package/dist/components/PdfViewer.js.map +1 -0
  162. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  163. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  164. package/dist/components/PptxNavigationPane.js +9 -0
  165. package/dist/components/PptxNavigationPane.js.map +1 -0
  166. package/dist/components/PptxNavigationPane.vue.d.ts +20 -0
  167. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +120 -0
  168. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  169. package/dist/components/PptxViewer.js +3 -1
  170. package/dist/components/PptxViewer.js.map +1 -0
  171. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +71 -39
  172. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  173. package/dist/components/PsdViewer.js +2 -0
  174. package/dist/components/PsdViewer.js.map +1 -0
  175. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  176. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  177. package/dist/components/QmcAudioViewer.js +2 -0
  178. package/dist/components/QmcAudioViewer.js.map +1 -0
  179. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  180. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/components/RasterViewer.js +2 -0
  182. package/dist/components/RasterViewer.js.map +1 -0
  183. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  184. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  185. package/dist/components/RtfViewer.js +2 -0
  186. package/dist/components/RtfViewer.js.map +1 -0
  187. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  188. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  189. package/dist/components/SevenZipBrowser.js +2 -0
  190. package/dist/components/SevenZipBrowser.js.map +1 -0
  191. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  192. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  193. package/dist/components/SqliteViewer.js +2 -0
  194. package/dist/components/SqliteViewer.js.map +1 -0
  195. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  196. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  197. package/dist/components/SubtitleViewer.js +2 -0
  198. package/dist/components/SubtitleViewer.js.map +1 -0
  199. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  200. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  201. package/dist/components/XlsxChart.js +2 -0
  202. package/dist/components/XlsxChart.js.map +1 -0
  203. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +2 -0
  204. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  205. package/dist/components/XlsxViewer.js +3 -1
  206. package/dist/components/XlsxViewer.js.map +1 -0
  207. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +301 -304
  208. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  209. package/dist/components/XmindViewer.js +2 -0
  210. package/dist/components/XmindViewer.js.map +1 -0
  211. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  212. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  213. package/dist/components/ZipBrowser.js +3 -1
  214. package/dist/components/ZipBrowser.js.map +1 -0
  215. package/dist/components/ZipBrowser.vue.d.ts +3 -1
  216. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +154 -122
  217. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  218. package/dist/components/ZoomImage.js +2 -0
  219. package/dist/components/ZoomImage.js.map +1 -0
  220. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +2 -0
  221. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -0
  222. package/dist/components/epl/EplCodeBlock.js +2 -0
  223. package/dist/components/epl/EplCodeBlock.js.map +1 -0
  224. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  225. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  226. package/dist/components/epl/EplDoc.js +2 -0
  227. package/dist/components/epl/EplDoc.js.map +1 -0
  228. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js +2 -0
  229. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js.map +1 -0
  230. package/dist/components/epl/EplMethod.js +2 -0
  231. package/dist/components/epl/EplMethod.js.map +1 -0
  232. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js +2 -0
  233. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js.map +1 -0
  234. package/dist/components/epl/EplResource.js +2 -0
  235. package/dist/components/epl/EplResource.js.map +1 -0
  236. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js +2 -0
  237. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -0
  238. package/dist/components/epl/EplTree.js +2 -0
  239. package/dist/components/epl/EplTree.js.map +1 -0
  240. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js +2 -0
  241. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js.map +1 -0
  242. package/dist/components/epl/EplVarTable.js +2 -0
  243. package/dist/components/epl/EplVarTable.js.map +1 -0
  244. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js +2 -0
  245. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  246. package/dist/components/epl/numbering.js +2 -0
  247. package/dist/components/epl/numbering.js.map +1 -0
  248. package/dist/components/icons/Icon.js +2 -0
  249. package/dist/components/icons/Icon.js.map +1 -0
  250. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js +2 -0
  251. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js.map +1 -0
  252. package/dist/components/icons/iconPaths.js +2 -0
  253. package/dist/components/icons/iconPaths.js.map +1 -0
  254. package/dist/components/optionalViewer.js +2 -0
  255. package/dist/components/optionalViewer.js.map +1 -0
  256. package/dist/components/ui/ClampBlock.js +2 -0
  257. package/dist/components/ui/ClampBlock.js.map +1 -0
  258. package/dist/composables/useElementNavigation.d.ts +21 -0
  259. package/dist/composables/useElementNavigation.js +63 -0
  260. package/dist/composables/useElementNavigation.js.map +1 -0
  261. package/dist/composables/useFileViewer.js +2 -0
  262. package/dist/composables/useFileViewer.js.map +1 -0
  263. package/dist/composables/useLightbox.js +2 -0
  264. package/dist/composables/useLightbox.js.map +1 -0
  265. package/dist/composables/useLogRules.js +2 -0
  266. package/dist/composables/useLogRules.js.map +1 -0
  267. package/dist/lib/aes.js +2 -0
  268. package/dist/lib/aes.js.map +1 -0
  269. package/dist/lib/appx.js +2 -0
  270. package/dist/lib/appx.js.map +1 -0
  271. package/dist/lib/archiveHtml.d.ts +49 -0
  272. package/dist/lib/archiveHtml.js +377 -0
  273. package/dist/lib/archiveHtml.js.map +1 -0
  274. package/dist/lib/asn1.js +2 -0
  275. package/dist/lib/asn1.js.map +1 -0
  276. package/dist/lib/binobj.js +2 -0
  277. package/dist/lib/binobj.js.map +1 -0
  278. package/dist/lib/brotli.js +2 -0
  279. package/dist/lib/brotli.js.map +1 -0
  280. package/dist/lib/bzip2.js +2 -0
  281. package/dist/lib/bzip2.js.map +1 -0
  282. package/dist/lib/classfile.js +2 -0
  283. package/dist/lib/classfile.js.map +1 -0
  284. package/dist/lib/codePreview.js +2 -0
  285. package/dist/lib/codePreview.js.map +1 -0
  286. package/dist/lib/codefold.js +2 -0
  287. package/dist/lib/codefold.js.map +1 -0
  288. package/dist/lib/codeformat.js +2 -0
  289. package/dist/lib/codeformat.js.map +1 -0
  290. package/dist/lib/csv.js +2 -0
  291. package/dist/lib/csv.js.map +1 -0
  292. package/dist/lib/des.js +2 -0
  293. package/dist/lib/des.js.map +1 -0
  294. package/dist/lib/diagram.js +2 -0
  295. package/dist/lib/diagram.js.map +1 -0
  296. package/dist/lib/dicom.js +2 -0
  297. package/dist/lib/dicom.js.map +1 -0
  298. package/dist/lib/dicomCodecs.js +2 -0
  299. package/dist/lib/dicomCodecs.js.map +1 -0
  300. package/dist/lib/dicomRender.js +2 -0
  301. package/dist/lib/dicomRender.js.map +1 -0
  302. package/dist/lib/docxNavigation.d.ts +31 -0
  303. package/dist/lib/docxNavigation.js +65 -0
  304. package/dist/lib/docxNavigation.js.map +1 -0
  305. package/dist/lib/dxf.js +2 -0
  306. package/dist/lib/dxf.js.map +1 -0
  307. package/dist/lib/eml.js +2 -0
  308. package/dist/lib/eml.js.map +1 -0
  309. package/dist/lib/encoding.js +2 -0
  310. package/dist/lib/encoding.js.map +1 -0
  311. package/dist/lib/epl/code/codeDataParser.js +2 -0
  312. package/dist/lib/epl/code/codeDataParser.js.map +1 -0
  313. package/dist/lib/epl/code/codeLines.js +2 -0
  314. package/dist/lib/epl/code/codeLines.js.map +1 -0
  315. package/dist/lib/epl/code/textCode.js +2 -0
  316. package/dist/lib/epl/code/textCode.js.map +1 -0
  317. package/dist/lib/epl/eplDocument.js +2 -0
  318. package/dist/lib/epl/eplDocument.js.map +1 -0
  319. package/dist/lib/epl/imageList.js +2 -0
  320. package/dist/lib/epl/imageList.js.map +1 -0
  321. package/dist/lib/epl/index.js +2 -0
  322. package/dist/lib/epl/index.js.map +1 -0
  323. package/dist/lib/epl/io/binaryReader.js +2 -0
  324. package/dist/lib/epl/io/binaryReader.js.map +1 -0
  325. package/dist/lib/epl/io/encoding.js +2 -0
  326. package/dist/lib/epl/io/encoding.js.map +1 -0
  327. package/dist/lib/epl/model/eplSystemId.js +2 -0
  328. package/dist/lib/epl/model/eplSystemId.js.map +1 -0
  329. package/dist/lib/epl/names/eplLibData.js +2 -0
  330. package/dist/lib/epl/names/eplLibData.js.map +1 -0
  331. package/dist/lib/epl/names/idToNameMap.js +2 -0
  332. package/dist/lib/epl/names/idToNameMap.js.map +1 -0
  333. package/dist/lib/epl/names/libNameProvider.js +2 -0
  334. package/dist/lib/epl/names/libNameProvider.js.map +1 -0
  335. package/dist/lib/epl/program.js +2 -0
  336. package/dist/lib/epl/program.js.map +1 -0
  337. package/dist/lib/epl/projectFileReader.js +2 -0
  338. package/dist/lib/epl/projectFileReader.js.map +1 -0
  339. package/dist/lib/fb2.js +2 -0
  340. package/dist/lib/fb2.js.map +1 -0
  341. package/dist/lib/fileType.js +2 -0
  342. package/dist/lib/fileType.js.map +1 -0
  343. package/dist/lib/fonts.js +2 -0
  344. package/dist/lib/fonts.js.map +1 -0
  345. package/dist/lib/format.js +2 -0
  346. package/dist/lib/format.js.map +1 -0
  347. package/dist/lib/frontmatter.js +2 -0
  348. package/dist/lib/frontmatter.js.map +1 -0
  349. package/dist/lib/geo.js +2 -0
  350. package/dist/lib/geo.js.map +1 -0
  351. package/dist/lib/har.js +2 -0
  352. package/dist/lib/har.js.map +1 -0
  353. package/dist/lib/highlight.js +2 -0
  354. package/dist/lib/highlight.js.map +1 -0
  355. package/dist/lib/highlightLangs.js +2 -0
  356. package/dist/lib/highlightLangs.js.map +1 -0
  357. package/dist/lib/hprof.js +2 -0
  358. package/dist/lib/hprof.js.map +1 -0
  359. package/dist/lib/ipynb.js +2 -0
  360. package/dist/lib/ipynb.js.map +1 -0
  361. package/dist/lib/jks.js +2 -0
  362. package/dist/lib/jks.js.map +1 -0
  363. package/dist/lib/jsonl.js +2 -0
  364. package/dist/lib/jsonl.js.map +1 -0
  365. package/dist/lib/lrc.js +2 -0
  366. package/dist/lib/lrc.js.map +1 -0
  367. package/dist/lib/lzma.js +2 -0
  368. package/dist/lib/lzma.js.map +1 -0
  369. package/dist/lib/markdown/blocks.js +2 -0
  370. package/dist/lib/markdown/blocks.js.map +1 -0
  371. package/dist/lib/markdown/inline.js +2 -0
  372. package/dist/lib/markdown/inline.js.map +1 -0
  373. package/dist/lib/markdown/math.js +2 -0
  374. package/dist/lib/markdown/math.js.map +1 -0
  375. package/dist/lib/markdown/segmentize.js +2 -0
  376. package/dist/lib/markdown/segmentize.js.map +1 -0
  377. package/dist/lib/markdown/styles.js +2 -0
  378. package/dist/lib/markdown/styles.js.map +1 -0
  379. package/dist/lib/mht.js +2 -0
  380. package/dist/lib/mht.js.map +1 -0
  381. package/dist/lib/midi.js +2 -0
  382. package/dist/lib/midi.js.map +1 -0
  383. package/dist/lib/mime.js +2 -0
  384. package/dist/lib/mime.js.map +1 -0
  385. package/dist/lib/mindmap.js +2 -0
  386. package/dist/lib/mindmap.js.map +1 -0
  387. package/dist/lib/mobi.js +2 -0
  388. package/dist/lib/mobi.js.map +1 -0
  389. package/dist/lib/msi.js +2 -0
  390. package/dist/lib/msi.js.map +1 -0
  391. package/dist/lib/optionalDep.js +2 -0
  392. package/dist/lib/optionalDep.js.map +1 -0
  393. package/dist/lib/pkcs12.js +2 -0
  394. package/dist/lib/pkcs12.js.map +1 -0
  395. package/dist/lib/pptxNavigation.d.ts +7 -0
  396. package/dist/lib/pptxNavigation.js +29 -0
  397. package/dist/lib/pptxNavigation.js.map +1 -0
  398. package/dist/lib/qmc.js +2 -0
  399. package/dist/lib/qmc.js.map +1 -0
  400. package/dist/lib/rar.js +2 -0
  401. package/dist/lib/rar.js.map +1 -0
  402. package/dist/lib/rawPreview.js +2 -0
  403. package/dist/lib/rawPreview.js.map +1 -0
  404. package/dist/lib/rc2.js +2 -0
  405. package/dist/lib/rc2.js.map +1 -0
  406. package/dist/lib/resolveInput.js +2 -0
  407. package/dist/lib/resolveInput.js.map +1 -0
  408. package/dist/lib/rtf.js +2 -0
  409. package/dist/lib/rtf.js.map +1 -0
  410. package/dist/lib/sevenZip.js +2 -0
  411. package/dist/lib/sevenZip.js.map +1 -0
  412. package/dist/lib/sha1.js +2 -0
  413. package/dist/lib/sha1.js.map +1 -0
  414. package/dist/lib/sha256.js +2 -0
  415. package/dist/lib/sha256.js.map +1 -0
  416. package/dist/lib/speechAudio.js +2 -0
  417. package/dist/lib/speechAudio.js.map +1 -0
  418. package/dist/lib/sqlite.js +2 -0
  419. package/dist/lib/sqlite.js.map +1 -0
  420. package/dist/lib/subtitle.js +2 -0
  421. package/dist/lib/subtitle.js.map +1 -0
  422. package/dist/lib/tarball.js +2 -0
  423. package/dist/lib/tarball.js.map +1 -0
  424. package/dist/lib/viewerFormats.js +2 -0
  425. package/dist/lib/viewerFormats.js.map +1 -0
  426. package/dist/lib/viewerKind.js +2 -0
  427. package/dist/lib/viewerKind.js.map +1 -0
  428. package/dist/lib/x509.js +2 -0
  429. package/dist/lib/x509.js.map +1 -0
  430. package/dist/lib/xlsx.js +2 -0
  431. package/dist/lib/xlsx.js.map +1 -0
  432. package/dist/lib/xlsxCharts.js +2 -0
  433. package/dist/lib/xlsxCharts.js.map +1 -0
  434. package/dist/lib/xmind.js +2 -0
  435. package/dist/lib/xmind.js.map +1 -0
  436. package/dist/lib/zip.js +2 -0
  437. package/dist/lib/zip.js.map +1 -0
  438. package/dist/style.css +1 -1
  439. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LrcViewer.js","names":[],"sources":["../../src/components/LrcViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { parseLrc } from '@/lib/lrc'\nimport { decodeTextSniff } from '@/lib/encoding'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * Side-viewer LRC lyrics renderer. Unlike the text-backed viewers, this one self-fetches the BYTES and sniffs\n * the encoding (UTF-8 → GBK → lenient, see lib/encoding.ts) — QQ Music's `.lrc` files are almost always GBK\n * and would be mojibake if read as UTF-8 text. The bytes are parsed in-browser (see lib/lrc.ts) into a synced\n * lyric list (timestamp + text); HTML numeric entities common in those files are decoded too.\n *\n * Self-contained 歌词/源码 toggle (the panel's own toggle is for text-backed kinds, which this isn't): render\n * shows the parsed lines, source shows the decoded raw text.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst raw = ref('')\nconst mode = ref<'render' | 'source'>('render')\n\nconst parsed = computed(() => parseLrc(raw.value))\n\n// ms → \"mm:ss\" for the time gutter.\nfunction fmtTime(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000))\n const m = Math.floor(total / 60)\n const s = total % 60\n return `${m}:${String(s).padStart(2, '0')}`\n}\n\nasync function load(): 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 = new Uint8Array(await res.arrayBuffer())\n raw.value = decodeTextSniff(buf)\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(() => {})\n</script>\n\n<template>\n <div class=\"lrc\">\n <div class=\"lrc__head\">\n <span class=\"lrc__badge\">LRC</span>\n <span v-if=\"parsed.hasTimings\" class=\"lrc__count\">{{ parsed.lines.length }} 行</span>\n <span v-else class=\"lrc__count\">纯文本歌词</span>\n <span v-if=\"parsed.offsetMs\" class=\"lrc__dim\">offset {{ parsed.offsetMs }}ms</span>\n <div class=\"lrc__toggle\" role=\"group\" aria-label=\"渲染或源码\">\n <button\n type=\"button\"\n class=\"lrc__tbtn\"\n :class=\"{ 'lrc__tbtn--on': mode === 'render' }\"\n title=\"歌词\"\n @click=\"mode = 'render'\"\n >\n <Icon name=\"eye\" :size=\"14\" />\n </button>\n <button\n type=\"button\"\n class=\"lrc__tbtn\"\n :class=\"{ 'lrc__tbtn--on': mode === 'source' }\"\n title=\"源码\"\n @click=\"mode = 'source'\"\n >\n <Icon name=\"code\" :size=\"14\" />\n </button>\n </div>\n </div>\n\n <p v-if=\"status === 'loading'\" class=\"lrc__msg\">正在加载…</p>\n <div v-else-if=\"status === 'error'\" class=\"lrc__msg lrc__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"lrc__retry\" @click=\"load\">重试</button>\n </div>\n\n <!-- source: the decoded raw .lrc text -->\n <pre v-else-if=\"mode === 'source'\" class=\"lrc__source\">{{ raw }}</pre>\n\n <!-- render: title/artist meta + the synced lyric list -->\n <div v-else class=\"lrc__body\">\n <div v-if=\"parsed.title || parsed.artist || parsed.album || parsed.by\" class=\"lrc__info\">\n <div v-if=\"parsed.title\" class=\"lrc__title\">{{ parsed.title }}</div>\n <div class=\"lrc__sub\">\n <span v-if=\"parsed.artist\">{{ parsed.artist }}</span>\n <span v-if=\"parsed.album\" class=\"lrc__al\">{{ parsed.album }}</span>\n </div>\n </div>\n\n <div v-if=\"parsed.lines.length\" class=\"lrc__list\">\n <div v-for=\"(line, i) in parsed.lines\" :key=\"i\" class=\"lrc__line\">\n <span v-if=\"line.timeMs != null\" class=\"lrc__time\">{{ fmtTime(line.timeMs) }}</span>\n <span class=\"lrc__text\" :class=\"{ 'lrc__text--empty': !line.text }\">{{\n line.text || '♪'\n }}</span>\n </div>\n </div>\n <p v-else class=\"lrc__msg\">未解析到歌词内容(可切换到「源码」查看原始内容)。</p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.lrc {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--code-bg);\n}\n\n.lrc__head {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n padding: 10px 16px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n font-size: 12px;\n color: var(--muted);\n}\n.lrc__badge {\n padding: 2px 9px;\n border-radius: 999px;\n background: var(--surface-2);\n color: var(--accent);\n font-weight: 600;\n letter-spacing: 0.02em;\n}\n.lrc__dim {\n color: var(--faint);\n}\n/* render/source toggle pinned right */\n.lrc__toggle {\n margin-left: auto;\n display: inline-flex;\n gap: 2px;\n padding: 2px;\n border-radius: 8px;\n background: var(--surface-2);\n}\n.lrc__tbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 24px;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--faint);\n cursor: pointer;\n transition:\n background 0.15s,\n color 0.15s;\n}\n.lrc__tbtn:hover {\n color: var(--muted);\n}\n.lrc__tbtn--on {\n background: var(--surface);\n color: var(--accent);\n box-shadow: var(--shadow);\n}\n\n.lrc__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.lrc__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.lrc__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\n.lrc__source {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n margin: 0;\n padding: 14px 16px 60px;\n font-family: var(--font-mono);\n font-size: 12.5px;\n line-height: 1.7;\n color: var(--text);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.lrc__body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 0 0 60px;\n}\n.lrc__info {\n padding: 16px 18px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.lrc__title {\n font-size: 15px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.lrc__sub {\n margin-top: 3px;\n display: flex;\n gap: 12px;\n font-size: 12.5px;\n color: var(--muted);\n}\n.lrc__al {\n color: var(--faint);\n}\n.lrc__list {\n padding: 8px 0;\n}\n.lrc__line {\n display: flex;\n gap: 14px;\n padding: 6px 18px;\n}\n.lrc__line:hover {\n background: var(--surface);\n}\n.lrc__time {\n flex-shrink: 0;\n width: 46px;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--accent);\n white-space: nowrap;\n padding-top: 1px;\n}\n.lrc__text {\n flex: 1 1 auto;\n min-width: 0;\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--text);\n word-break: break-word;\n}\n.lrc__text--empty {\n color: var(--faint);\n}\n</style>\n"],"mappings":""}
@@ -104,3 +104,5 @@ var _ = { class: "lrc" }, v = { class: "lrc__head" }, y = {
104
104
  });
105
105
  //#endregion
106
106
  export { F as default };
107
+
108
+ //# sourceMappingURL=LrcViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LrcViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/LrcViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { parseLrc } from '@/lib/lrc'\nimport { decodeTextSniff } from '@/lib/encoding'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * Side-viewer LRC lyrics renderer. Unlike the text-backed viewers, this one self-fetches the BYTES and sniffs\n * the encoding (UTF-8 → GBK → lenient, see lib/encoding.ts) — QQ Music's `.lrc` files are almost always GBK\n * and would be mojibake if read as UTF-8 text. The bytes are parsed in-browser (see lib/lrc.ts) into a synced\n * lyric list (timestamp + text); HTML numeric entities common in those files are decoded too.\n *\n * Self-contained 歌词/源码 toggle (the panel's own toggle is for text-backed kinds, which this isn't): render\n * shows the parsed lines, source shows the decoded raw text.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst raw = ref('')\nconst mode = ref<'render' | 'source'>('render')\n\nconst parsed = computed(() => parseLrc(raw.value))\n\n// ms → \"mm:ss\" for the time gutter.\nfunction fmtTime(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000))\n const m = Math.floor(total / 60)\n const s = total % 60\n return `${m}:${String(s).padStart(2, '0')}`\n}\n\nasync function load(): 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 = new Uint8Array(await res.arrayBuffer())\n raw.value = decodeTextSniff(buf)\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(() => {})\n</script>\n\n<template>\n <div class=\"lrc\">\n <div class=\"lrc__head\">\n <span class=\"lrc__badge\">LRC</span>\n <span v-if=\"parsed.hasTimings\" class=\"lrc__count\">{{ parsed.lines.length }} 行</span>\n <span v-else class=\"lrc__count\">纯文本歌词</span>\n <span v-if=\"parsed.offsetMs\" class=\"lrc__dim\">offset {{ parsed.offsetMs }}ms</span>\n <div class=\"lrc__toggle\" role=\"group\" aria-label=\"渲染或源码\">\n <button\n type=\"button\"\n class=\"lrc__tbtn\"\n :class=\"{ 'lrc__tbtn--on': mode === 'render' }\"\n title=\"歌词\"\n @click=\"mode = 'render'\"\n >\n <Icon name=\"eye\" :size=\"14\" />\n </button>\n <button\n type=\"button\"\n class=\"lrc__tbtn\"\n :class=\"{ 'lrc__tbtn--on': mode === 'source' }\"\n title=\"源码\"\n @click=\"mode = 'source'\"\n >\n <Icon name=\"code\" :size=\"14\" />\n </button>\n </div>\n </div>\n\n <p v-if=\"status === 'loading'\" class=\"lrc__msg\">正在加载…</p>\n <div v-else-if=\"status === 'error'\" class=\"lrc__msg lrc__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"lrc__retry\" @click=\"load\">重试</button>\n </div>\n\n <!-- source: the decoded raw .lrc text -->\n <pre v-else-if=\"mode === 'source'\" class=\"lrc__source\">{{ raw }}</pre>\n\n <!-- render: title/artist meta + the synced lyric list -->\n <div v-else class=\"lrc__body\">\n <div v-if=\"parsed.title || parsed.artist || parsed.album || parsed.by\" class=\"lrc__info\">\n <div v-if=\"parsed.title\" class=\"lrc__title\">{{ parsed.title }}</div>\n <div class=\"lrc__sub\">\n <span v-if=\"parsed.artist\">{{ parsed.artist }}</span>\n <span v-if=\"parsed.album\" class=\"lrc__al\">{{ parsed.album }}</span>\n </div>\n </div>\n\n <div v-if=\"parsed.lines.length\" class=\"lrc__list\">\n <div v-for=\"(line, i) in parsed.lines\" :key=\"i\" class=\"lrc__line\">\n <span v-if=\"line.timeMs != null\" class=\"lrc__time\">{{ fmtTime(line.timeMs) }}</span>\n <span class=\"lrc__text\" :class=\"{ 'lrc__text--empty': !line.text }\">{{\n line.text || '♪'\n }}</span>\n </div>\n </div>\n <p v-else class=\"lrc__msg\">未解析到歌词内容(可切换到「源码」查看原始内容)。</p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.lrc {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--code-bg);\n}\n\n.lrc__head {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n padding: 10px 16px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n font-size: 12px;\n color: var(--muted);\n}\n.lrc__badge {\n padding: 2px 9px;\n border-radius: 999px;\n background: var(--surface-2);\n color: var(--accent);\n font-weight: 600;\n letter-spacing: 0.02em;\n}\n.lrc__dim {\n color: var(--faint);\n}\n/* render/source toggle pinned right */\n.lrc__toggle {\n margin-left: auto;\n display: inline-flex;\n gap: 2px;\n padding: 2px;\n border-radius: 8px;\n background: var(--surface-2);\n}\n.lrc__tbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 24px;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--faint);\n cursor: pointer;\n transition:\n background 0.15s,\n color 0.15s;\n}\n.lrc__tbtn:hover {\n color: var(--muted);\n}\n.lrc__tbtn--on {\n background: var(--surface);\n color: var(--accent);\n box-shadow: var(--shadow);\n}\n\n.lrc__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.lrc__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.lrc__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\n.lrc__source {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n margin: 0;\n padding: 14px 16px 60px;\n font-family: var(--font-mono);\n font-size: 12.5px;\n line-height: 1.7;\n color: var(--text);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.lrc__body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 0 0 60px;\n}\n.lrc__info {\n padding: 16px 18px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.lrc__title {\n font-size: 15px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.lrc__sub {\n margin-top: 3px;\n display: flex;\n gap: 12px;\n font-size: 12.5px;\n color: var(--muted);\n}\n.lrc__al {\n color: var(--faint);\n}\n.lrc__list {\n padding: 8px 0;\n}\n.lrc__line {\n display: flex;\n gap: 14px;\n padding: 6px 18px;\n}\n.lrc__line:hover {\n background: var(--surface);\n}\n.lrc__time {\n flex-shrink: 0;\n width: 46px;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--accent);\n white-space: nowrap;\n padding-top: 1px;\n}\n.lrc__text {\n flex: 1 1 auto;\n min-width: 0;\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--text);\n word-break: break-word;\n}\n.lrc__text--empty {\n color: var(--faint);\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./MarkdownTable.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-49d97fb7"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=MarkdownTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownTable.js","names":[],"sources":["../../src/components/MarkdownTable.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport InlineText from '@/components/InlineText'\n\n/**\n * GFM pipe table rendered as a horizontally-scrollable card, with drag-to-resize columns.\n *\n * Column resize is opt-in and lazy: until you grab a header's right-edge handle the table renders exactly as\n * before — `width:100%`, auto layout, content-sized adaptive columns (also the untouched SSR output). On the\n * FIRST drag we snapshot the current rendered column widths ({@link colWidths}), switch to a fixed layout keyed\n * off a `<colgroup>`, and from then on each column is an explicit px width (table width = their sum, so the card\n * scrolls horizontally once you widen past it). Dragging changes only the grabbed column — same mechanics as the\n * CSV viewer. A change in column count (e.g. a table re-parsing while a message streams) drops back to adaptive.\n */\nconst props = defineProps<{\n header: string[]\n aligns: (string | null)[]\n rows: string[][]\n keyPrefix?: string\n}>()\n\nfunction al(ci: number): 'left' | 'center' | 'right' {\n return (props.aligns[ci] || 'left') as 'left' | 'center' | 'right'\n}\n\nconst MIN_COL_W = 40\n\n// null → adaptive default (no explicit widths); an array → user has taken manual control (fixed layout).\nconst colWidths = ref<number[] | null>(null)\nconst totalWidth = computed(() => colWidths.value?.reduce((a, w) => a + w, 0) ?? 0)\n\n// A structural change (column count differs) invalidates measured widths — revert to the adaptive default.\nwatch(\n () => props.header.length,\n () => {\n colWidths.value = null\n },\n)\n\nlet resizeIdx = -1\nlet startX = 0\nlet startW = 0\n\nfunction startResize(ci: number, e: MouseEvent): void {\n // First drag: freeze the columns at their current rendered widths, then switch to fixed layout so the drag is\n // pixel-exact. Measured from the header row's cells (one <tr>), which always has header.length columns.\n if (!colWidths.value) {\n const th = (e.currentTarget as HTMLElement).closest('th')\n const rowEl = th?.parentElement\n if (!rowEl) return\n const raw = Array.from(rowEl.children).map(\n (el) => (el as HTMLElement).getBoundingClientRect().width,\n )\n // Scale the per-cell widths to the table's actual width: with collapsed borders the cell widths overcount\n // the shared internal borders, so their raw sum slightly exceeds the table — normalizing avoids a spurious\n // scrollbar the moment the table locks to a fixed width.\n const tableW = th?.closest('table')?.getBoundingClientRect().width ?? 0\n const rawSum = raw.reduce((a, w) => a + w, 0) || 1\n const scale = tableW > 0 ? tableW / rawSum : 1\n colWidths.value = raw.map((w) => Math.max(MIN_COL_W, Math.round(w * scale)))\n }\n const widths = colWidths.value\n if (!widths) return\n resizeIdx = ci\n startX = e.clientX\n startW = widths[ci]\n window.addEventListener('mousemove', onResizeMove)\n window.addEventListener('mouseup', endResize)\n document.body.style.userSelect = 'none'\n document.body.style.cursor = 'col-resize'\n}\nfunction onResizeMove(e: MouseEvent): void {\n const widths = colWidths.value\n if (resizeIdx < 0 || !widths) return\n const next = widths.slice()\n next[resizeIdx] = Math.max(MIN_COL_W, Math.round(startW + (e.clientX - startX)))\n colWidths.value = next\n}\nfunction endResize(): void {\n resizeIdx = -1\n window.removeEventListener('mousemove', onResizeMove)\n window.removeEventListener('mouseup', endResize)\n document.body.style.userSelect = ''\n document.body.style.cursor = ''\n}\n\nonBeforeUnmount(endResize)\n</script>\n\n<template>\n <div class=\"md-table\">\n <div class=\"md-table__scroll\">\n <table\n class=\"md-table__table\"\n :class=\"{ 'md-table__table--fixed': colWidths }\"\n :style=\"colWidths ? { width: totalWidth + 'px' } : undefined\"\n >\n <colgroup v-if=\"colWidths\">\n <col v-for=\"(w, ci) in colWidths\" :key=\"ci\" :style=\"{ width: w + 'px' }\" />\n </colgroup>\n <thead>\n <tr>\n <th v-for=\"(c, ci) in header\" :key=\"ci\" :style=\"{ textAlign: al(ci) }\">\n <InlineText :text=\"c\" :kp=\"`${keyPrefix}-th${ci}`\" />\n <span\n class=\"md-table__resize\"\n title=\"拖动调整列宽\"\n @mousedown.prevent.stop=\"startResize(ci, $event)\"\n />\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, ri) in rows\" :key=\"ri\">\n <td v-for=\"(_, ci) in header\" :key=\"ci\" :style=\"{ textAlign: al(ci) }\">\n <InlineText :text=\"row[ci] || ''\" :kp=\"`${keyPrefix}-r${ri}c${ci}`\" />\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</template>\n\n<style scoped>\n.md-table {\n margin: 4px 0 22px;\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n box-shadow: var(--shadow);\n background: var(--surface);\n}\n.md-table__scroll {\n overflow-x: auto;\n}\n.md-table__table {\n border-collapse: collapse;\n width: 100%;\n}\n/* Fixed layout kicks in only after the first resize — column widths then come straight from <colgroup>, and\n cells wrap / break so a narrowed column never spills over its neighbour. */\n.md-table__table--fixed {\n table-layout: fixed;\n}\n.md-table__table--fixed th,\n.md-table__table--fixed td {\n white-space: normal;\n overflow-wrap: anywhere;\n}\n.md-table__table th {\n position: relative;\n font-family: var(--font-sans);\n font-size: 14px;\n font-weight: 600;\n color: var(--text-strong);\n padding: 10px 14px;\n background: var(--code-head);\n border-bottom: 1px solid var(--border-2);\n white-space: nowrap;\n}\n.md-table__table td {\n font-family: var(--font-sans);\n font-size: 14px;\n line-height: 1.55;\n color: var(--text);\n padding: 9px 14px;\n border-top: 1px solid var(--border);\n vertical-align: top;\n}\n/* Column-resize handle over each header cell's right edge; the grip line fades in on hover. */\n.md-table__resize {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 9px;\n cursor: col-resize;\n user-select: none;\n touch-action: none;\n}\n.md-table__resize::after {\n content: '';\n position: absolute;\n top: 20%;\n bottom: 20%;\n right: 3px;\n width: 2px;\n border-radius: 1px;\n background: var(--border-2);\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n.md-table__table th:hover .md-table__resize::after {\n opacity: 1;\n}\n.md-table__resize:hover::after {\n opacity: 1;\n background: var(--accent);\n}\n</style>\n"],"mappings":""}
@@ -69,3 +69,5 @@ var _ = { class: "md-table" }, v = { class: "md-table__scroll" }, y = { key: 0 }
69
69
  });
70
70
  //#endregion
71
71
  export { S as default };
72
+
73
+ //# sourceMappingURL=MarkdownTable.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownTable.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/MarkdownTable.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport InlineText from '@/components/InlineText'\n\n/**\n * GFM pipe table rendered as a horizontally-scrollable card, with drag-to-resize columns.\n *\n * Column resize is opt-in and lazy: until you grab a header's right-edge handle the table renders exactly as\n * before — `width:100%`, auto layout, content-sized adaptive columns (also the untouched SSR output). On the\n * FIRST drag we snapshot the current rendered column widths ({@link colWidths}), switch to a fixed layout keyed\n * off a `<colgroup>`, and from then on each column is an explicit px width (table width = their sum, so the card\n * scrolls horizontally once you widen past it). Dragging changes only the grabbed column — same mechanics as the\n * CSV viewer. A change in column count (e.g. a table re-parsing while a message streams) drops back to adaptive.\n */\nconst props = defineProps<{\n header: string[]\n aligns: (string | null)[]\n rows: string[][]\n keyPrefix?: string\n}>()\n\nfunction al(ci: number): 'left' | 'center' | 'right' {\n return (props.aligns[ci] || 'left') as 'left' | 'center' | 'right'\n}\n\nconst MIN_COL_W = 40\n\n// null → adaptive default (no explicit widths); an array → user has taken manual control (fixed layout).\nconst colWidths = ref<number[] | null>(null)\nconst totalWidth = computed(() => colWidths.value?.reduce((a, w) => a + w, 0) ?? 0)\n\n// A structural change (column count differs) invalidates measured widths — revert to the adaptive default.\nwatch(\n () => props.header.length,\n () => {\n colWidths.value = null\n },\n)\n\nlet resizeIdx = -1\nlet startX = 0\nlet startW = 0\n\nfunction startResize(ci: number, e: MouseEvent): void {\n // First drag: freeze the columns at their current rendered widths, then switch to fixed layout so the drag is\n // pixel-exact. Measured from the header row's cells (one <tr>), which always has header.length columns.\n if (!colWidths.value) {\n const th = (e.currentTarget as HTMLElement).closest('th')\n const rowEl = th?.parentElement\n if (!rowEl) return\n const raw = Array.from(rowEl.children).map(\n (el) => (el as HTMLElement).getBoundingClientRect().width,\n )\n // Scale the per-cell widths to the table's actual width: with collapsed borders the cell widths overcount\n // the shared internal borders, so their raw sum slightly exceeds the table — normalizing avoids a spurious\n // scrollbar the moment the table locks to a fixed width.\n const tableW = th?.closest('table')?.getBoundingClientRect().width ?? 0\n const rawSum = raw.reduce((a, w) => a + w, 0) || 1\n const scale = tableW > 0 ? tableW / rawSum : 1\n colWidths.value = raw.map((w) => Math.max(MIN_COL_W, Math.round(w * scale)))\n }\n const widths = colWidths.value\n if (!widths) return\n resizeIdx = ci\n startX = e.clientX\n startW = widths[ci]\n window.addEventListener('mousemove', onResizeMove)\n window.addEventListener('mouseup', endResize)\n document.body.style.userSelect = 'none'\n document.body.style.cursor = 'col-resize'\n}\nfunction onResizeMove(e: MouseEvent): void {\n const widths = colWidths.value\n if (resizeIdx < 0 || !widths) return\n const next = widths.slice()\n next[resizeIdx] = Math.max(MIN_COL_W, Math.round(startW + (e.clientX - startX)))\n colWidths.value = next\n}\nfunction endResize(): void {\n resizeIdx = -1\n window.removeEventListener('mousemove', onResizeMove)\n window.removeEventListener('mouseup', endResize)\n document.body.style.userSelect = ''\n document.body.style.cursor = ''\n}\n\nonBeforeUnmount(endResize)\n</script>\n\n<template>\n <div class=\"md-table\">\n <div class=\"md-table__scroll\">\n <table\n class=\"md-table__table\"\n :class=\"{ 'md-table__table--fixed': colWidths }\"\n :style=\"colWidths ? { width: totalWidth + 'px' } : undefined\"\n >\n <colgroup v-if=\"colWidths\">\n <col v-for=\"(w, ci) in colWidths\" :key=\"ci\" :style=\"{ width: w + 'px' }\" />\n </colgroup>\n <thead>\n <tr>\n <th v-for=\"(c, ci) in header\" :key=\"ci\" :style=\"{ textAlign: al(ci) }\">\n <InlineText :text=\"c\" :kp=\"`${keyPrefix}-th${ci}`\" />\n <span\n class=\"md-table__resize\"\n title=\"拖动调整列宽\"\n @mousedown.prevent.stop=\"startResize(ci, $event)\"\n />\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, ri) in rows\" :key=\"ri\">\n <td v-for=\"(_, ci) in header\" :key=\"ci\" :style=\"{ textAlign: al(ci) }\">\n <InlineText :text=\"row[ci] || ''\" :kp=\"`${keyPrefix}-r${ri}c${ci}`\" />\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</template>\n\n<style scoped>\n.md-table {\n margin: 4px 0 22px;\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n box-shadow: var(--shadow);\n background: var(--surface);\n}\n.md-table__scroll {\n overflow-x: auto;\n}\n.md-table__table {\n border-collapse: collapse;\n width: 100%;\n}\n/* Fixed layout kicks in only after the first resize — column widths then come straight from <colgroup>, and\n cells wrap / break so a narrowed column never spills over its neighbour. */\n.md-table__table--fixed {\n table-layout: fixed;\n}\n.md-table__table--fixed th,\n.md-table__table--fixed td {\n white-space: normal;\n overflow-wrap: anywhere;\n}\n.md-table__table th {\n position: relative;\n font-family: var(--font-sans);\n font-size: 14px;\n font-weight: 600;\n color: var(--text-strong);\n padding: 10px 14px;\n background: var(--code-head);\n border-bottom: 1px solid var(--border-2);\n white-space: nowrap;\n}\n.md-table__table td {\n font-family: var(--font-sans);\n font-size: 14px;\n line-height: 1.55;\n color: var(--text);\n padding: 9px 14px;\n border-top: 1px solid var(--border);\n vertical-align: top;\n}\n/* Column-resize handle over each header cell's right edge; the grip line fades in on hover. */\n.md-table__resize {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 9px;\n cursor: col-resize;\n user-select: none;\n touch-action: none;\n}\n.md-table__resize::after {\n content: '';\n position: absolute;\n top: 20%;\n bottom: 20%;\n right: 3px;\n width: 2px;\n border-radius: 1px;\n background: var(--border-2);\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n.md-table__table th:hover .md-table__resize::after {\n opacity: 1;\n}\n.md-table__resize:hover::after {\n opacity: 1;\n background: var(--accent);\n}\n</style>\n"],"mappings":"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"}
@@ -19,3 +19,5 @@ var n = t({
19
19
  });
20
20
  //#endregion
21
21
  export { n as default };
22
+
23
+ //# sourceMappingURL=MarkdownText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownText.js","names":[],"sources":["../../src/components/MarkdownText.ts"],"sourcesContent":["import { defineComponent } from 'vue'\nimport { md } from '@/lib/markdown/blocks'\n\n/**\n * Renders block-level markdown (paragraphs, headings, lists, tables, code) as a\n * fragment — the nodes flatten into the parent so they sit as siblings of other\n * message blocks, exactly like the original `this.md(...)` spread.\n */\nexport default defineComponent({\n name: 'MarkdownText',\n props: {\n text: { type: String, default: '' },\n kp: { type: String, default: 'md' },\n },\n setup(props) {\n return () => md(props.text, props.kp)\n },\n})\n"],"mappings":";;;AAQA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CACN,OAAO;EACL,MAAM;GAAE,MAAM;GAAQ,SAAS;EAAG;EAClC,IAAI;GAAE,MAAM;GAAQ,SAAS;EAAK;CACpC;CACA,MAAM,GAAO;EACX,aAAa,EAAG,EAAM,MAAM,EAAM,EAAE;CACtC;AACF,CAAC"}
@@ -5,3 +5,5 @@ import t from "./MhtViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-d878d4ca"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=MhtViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MhtViewer.js","names":[],"sources":["../../src/components/MhtViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { parseMht, rewriteHtml } from '@/lib/mht'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer web-archive (`.mht`/`.mhtml`) renderer. Fetches the archive bytes and parses the MIME container\n * entirely in-browser (see lib/mht.ts) — picks the root HTML, indexes the page resources, and rewrites the\n * HTML's resource references to blob URLs before handing it to a sandboxed iframe.\n *\n * Safety: a saved page is hostile markup. It renders in a sandboxed iframe that NEVER gets `allow-scripts`,\n * and the rewritten HTML carries an injected CSP (`script-src 'none'` + `default-src 'none'`, see MHT_CSP) —\n * so NO upstream script runs (two independent gates) and the page can't phone home (only blob:/data:\n * resources are allowed; external requests are blocked). Unlike the panel's HTML preview / EmlViewer (which\n * use the strictest opaque-origin `sandbox=\"\"` + data URLs), this frame keeps `allow-same-origin` so the\n * page's many resources can load as same-origin blob URLs instead of being inlined as memory-heavy data URLs\n * — the deliberate trade for opening large archives. With scripts hard-disabled, `allow-same-origin` is inert.\n */\nconst props = defineProps<{\n /** Download URL of the .mht/.mhtml bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\n\ninterface MhtMeta {\n title: string\n sourceUrl: string\n date: string\n resourceCount: number\n}\nconst meta = ref<MhtMeta | null>(null)\nconst docUrl = ref('')\nlet objectUrls: string[] = []\n\n// A web archive bundles every page resource, so it can be large; gate very big ones behind a confirm. The\n// blob-URL strategy keeps memory ≈ file size, so the bar is higher than EmlViewer's in-memory data-URL gate.\nconst SIZE_LIMIT = 30 * 1024 * 1024\n\nfunction revokeAll(): void {\n for (const u of objectUrls) URL.revokeObjectURL(u)\n objectUrls = []\n docUrl.value = ''\n}\n\n/** Blob-URL factory handed to rewriteHtml (and reused for the document itself); tracks URLs for revocation. */\nfunction makeBlobUrl(bytes: Uint8Array, contentType: string): string {\n const u = URL.createObjectURL(new Blob([bytes], { type: contentType }))\n objectUrls.push(u)\n return u\n}\n\n// Human-friendly Date header (falls back to the raw value when it doesn't parse).\nconst dateDisplay = computed(() => {\n const d = meta.value?.date\n if (!d) return ''\n const t = Date.parse(d)\n if (Number.isNaN(t)) return d\n try {\n return new Date(t).toLocaleString('zh-CN', { dateStyle: 'medium', timeStyle: 'short' })\n } catch {\n return d\n }\n})\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n revokeAll()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n const parsed = parseMht(new Uint8Array(buf))\n\n meta.value = {\n title: parsed.title,\n sourceUrl: parsed.sourceUrl,\n date: parsed.date,\n resourceCount: new Set([...parsed.byLocation.values(), ...parsed.byCid.values()]).size,\n }\n\n if (parsed.html !== null) {\n // rewriteHtml mints blob URLs for the page resources via makeBlobUrl as it walks; then blob the doc too.\n const html = rewriteHtml(parsed, makeBlobUrl)\n docUrl.value = makeBlobUrl(new TextEncoder().encode(html), 'text/html')\n }\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 // Gate oversized files behind a confirm; otherwise fetch + parse straight away.\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 })\nonBeforeUnmount(revokeAll)\n</script>\n\n<template>\n <div class=\"mht\">\n <p v-if=\"status === 'loading'\" class=\"mht__msg\">正在解析网页存档…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"mht__msg mht__gate\">\n <p>该存档较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"mht__btn\" @click=\"doLoad\">解析并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"mht__msg mht__msg--error\">\n <p>解析失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"mht__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready' && meta\">\n <!-- header card: page title + original URL + date -->\n <div class=\"mht__head\">\n <div class=\"mht__title\">{{ meta.title || props.name || '(无标题)' }}</div>\n <dl class=\"mht__fields\">\n <div v-if=\"meta.sourceUrl\" class=\"mht__field\">\n <dt>来源</dt>\n <dd class=\"mht__src\">{{ meta.sourceUrl }}</dd>\n </div>\n <div v-if=\"dateDisplay\" class=\"mht__field\">\n <dt>保存</dt>\n <dd>{{ dateDisplay }}</dd>\n </div>\n <div v-if=\"meta.resourceCount\" class=\"mht__field\">\n <dt>资源</dt>\n <dd>{{ meta.resourceCount }} 项</dd>\n </div>\n </dl>\n </div>\n\n <!-- body: the rewritten page in a sandboxed iframe (allow-same-origin, NO allow-scripts; CSP blocks JS) -->\n <div v-if=\"docUrl\" class=\"mht__body\">\n <iframe\n class=\"mht__frame\"\n :src=\"docUrl\"\n title=\"网页存档正文\"\n sandbox=\"allow-same-origin\"\n loading=\"lazy\"\n referrerpolicy=\"no-referrer\"\n />\n </div>\n <p v-else class=\"mht__msg\">(此存档没有可显示的网页正文)</p>\n </template>\n </div>\n</template>\n\n<style scoped>\n.mht {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.mht__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mht__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.mht__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.mht__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.mht__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Header card: title + source + date, fixed above the scrolling page. */\n.mht__head {\n flex-shrink: 0;\n padding: 14px 16px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.mht__title {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--text-strong);\n word-break: break-word;\n}\n.mht__fields {\n margin: 10px 0 0;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.mht__field {\n display: flex;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.5;\n}\n.mht__field dt {\n flex-shrink: 0;\n width: 36px;\n color: var(--faint);\n}\n.mht__field dd {\n margin: 0;\n min-width: 0;\n color: var(--text);\n word-break: break-word;\n}\n.mht__src {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--muted);\n}\n\n/* Page body: the iframe fills the remaining height on a white canvas (like the HTML file preview). */\n.mht__body {\n flex: 1 1 auto;\n min-height: 0;\n background: #fff;\n}\n.mht__frame {\n display: block;\n width: 100%;\n height: 100%;\n border: none;\n background: #fff;\n}\n</style>\n"],"mappings":""}
@@ -110,3 +110,5 @@ var h = { class: "mht" }, g = {
110
110
  });
111
111
  //#endregion
112
112
  export { A as default };
113
+
114
+ //# sourceMappingURL=MhtViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MhtViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/MhtViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { parseMht, rewriteHtml } from '@/lib/mht'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer web-archive (`.mht`/`.mhtml`) renderer. Fetches the archive bytes and parses the MIME container\n * entirely in-browser (see lib/mht.ts) — picks the root HTML, indexes the page resources, and rewrites the\n * HTML's resource references to blob URLs before handing it to a sandboxed iframe.\n *\n * Safety: a saved page is hostile markup. It renders in a sandboxed iframe that NEVER gets `allow-scripts`,\n * and the rewritten HTML carries an injected CSP (`script-src 'none'` + `default-src 'none'`, see MHT_CSP) —\n * so NO upstream script runs (two independent gates) and the page can't phone home (only blob:/data:\n * resources are allowed; external requests are blocked). Unlike the panel's HTML preview / EmlViewer (which\n * use the strictest opaque-origin `sandbox=\"\"` + data URLs), this frame keeps `allow-same-origin` so the\n * page's many resources can load as same-origin blob URLs instead of being inlined as memory-heavy data URLs\n * — the deliberate trade for opening large archives. With scripts hard-disabled, `allow-same-origin` is inert.\n */\nconst props = defineProps<{\n /** Download URL of the .mht/.mhtml bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\n\ninterface MhtMeta {\n title: string\n sourceUrl: string\n date: string\n resourceCount: number\n}\nconst meta = ref<MhtMeta | null>(null)\nconst docUrl = ref('')\nlet objectUrls: string[] = []\n\n// A web archive bundles every page resource, so it can be large; gate very big ones behind a confirm. The\n// blob-URL strategy keeps memory ≈ file size, so the bar is higher than EmlViewer's in-memory data-URL gate.\nconst SIZE_LIMIT = 30 * 1024 * 1024\n\nfunction revokeAll(): void {\n for (const u of objectUrls) URL.revokeObjectURL(u)\n objectUrls = []\n docUrl.value = ''\n}\n\n/** Blob-URL factory handed to rewriteHtml (and reused for the document itself); tracks URLs for revocation. */\nfunction makeBlobUrl(bytes: Uint8Array, contentType: string): string {\n const u = URL.createObjectURL(new Blob([bytes], { type: contentType }))\n objectUrls.push(u)\n return u\n}\n\n// Human-friendly Date header (falls back to the raw value when it doesn't parse).\nconst dateDisplay = computed(() => {\n const d = meta.value?.date\n if (!d) return ''\n const t = Date.parse(d)\n if (Number.isNaN(t)) return d\n try {\n return new Date(t).toLocaleString('zh-CN', { dateStyle: 'medium', timeStyle: 'short' })\n } catch {\n return d\n }\n})\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n revokeAll()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n const parsed = parseMht(new Uint8Array(buf))\n\n meta.value = {\n title: parsed.title,\n sourceUrl: parsed.sourceUrl,\n date: parsed.date,\n resourceCount: new Set([...parsed.byLocation.values(), ...parsed.byCid.values()]).size,\n }\n\n if (parsed.html !== null) {\n // rewriteHtml mints blob URLs for the page resources via makeBlobUrl as it walks; then blob the doc too.\n const html = rewriteHtml(parsed, makeBlobUrl)\n docUrl.value = makeBlobUrl(new TextEncoder().encode(html), 'text/html')\n }\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 // Gate oversized files behind a confirm; otherwise fetch + parse straight away.\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 })\nonBeforeUnmount(revokeAll)\n</script>\n\n<template>\n <div class=\"mht\">\n <p v-if=\"status === 'loading'\" class=\"mht__msg\">正在解析网页存档…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"mht__msg mht__gate\">\n <p>该存档较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"mht__btn\" @click=\"doLoad\">解析并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"mht__msg mht__msg--error\">\n <p>解析失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"mht__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready' && meta\">\n <!-- header card: page title + original URL + date -->\n <div class=\"mht__head\">\n <div class=\"mht__title\">{{ meta.title || props.name || '(无标题)' }}</div>\n <dl class=\"mht__fields\">\n <div v-if=\"meta.sourceUrl\" class=\"mht__field\">\n <dt>来源</dt>\n <dd class=\"mht__src\">{{ meta.sourceUrl }}</dd>\n </div>\n <div v-if=\"dateDisplay\" class=\"mht__field\">\n <dt>保存</dt>\n <dd>{{ dateDisplay }}</dd>\n </div>\n <div v-if=\"meta.resourceCount\" class=\"mht__field\">\n <dt>资源</dt>\n <dd>{{ meta.resourceCount }} 项</dd>\n </div>\n </dl>\n </div>\n\n <!-- body: the rewritten page in a sandboxed iframe (allow-same-origin, NO allow-scripts; CSP blocks JS) -->\n <div v-if=\"docUrl\" class=\"mht__body\">\n <iframe\n class=\"mht__frame\"\n :src=\"docUrl\"\n title=\"网页存档正文\"\n sandbox=\"allow-same-origin\"\n loading=\"lazy\"\n referrerpolicy=\"no-referrer\"\n />\n </div>\n <p v-else class=\"mht__msg\">(此存档没有可显示的网页正文)</p>\n </template>\n </div>\n</template>\n\n<style scoped>\n.mht {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.mht__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mht__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.mht__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.mht__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.mht__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Header card: title + source + date, fixed above the scrolling page. */\n.mht__head {\n flex-shrink: 0;\n padding: 14px 16px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.mht__title {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--text-strong);\n word-break: break-word;\n}\n.mht__fields {\n margin: 10px 0 0;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.mht__field {\n display: flex;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.5;\n}\n.mht__field dt {\n flex-shrink: 0;\n width: 36px;\n color: var(--faint);\n}\n.mht__field dd {\n margin: 0;\n min-width: 0;\n color: var(--text);\n word-break: break-word;\n}\n.mht__src {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--muted);\n}\n\n/* Page body: the iframe fills the remaining height on a white canvas (like the HTML file preview). */\n.mht__body {\n flex: 1 1 auto;\n min-height: 0;\n background: #fff;\n}\n.mht__frame {\n display: block;\n width: 100%;\n height: 100%;\n border: none;\n background: #fff;\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./MindMapView.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-93d0df23"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=MindMapView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MindMapView.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":""}
@@ -335,3 +335,5 @@ var ne = { class: "mm" }, re = {
335
335
  });
336
336
  //#endregion
337
337
  export { k as default };
338
+
339
+ //# sourceMappingURL=MindMapView.vue_vue_type_script_setup_true_lang.js.map
@@ -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