@l1yp/file-viewer 0.1.1 → 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 (431) hide show
  1. package/README.md +1 -0
  2. package/dist/FileViewer.js +2 -0
  3. package/dist/FileViewer.js.map +1 -0
  4. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  5. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  6. package/dist/components/AppxViewer.js +2 -0
  7. package/dist/components/AppxViewer.js.map +1 -0
  8. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  9. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  10. package/dist/components/ArchiveBrowser.js +2 -0
  11. package/dist/components/ArchiveBrowser.js.map +1 -0
  12. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  13. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/AudioViewer.js +2 -0
  15. package/dist/components/AudioViewer.js.map +1 -0
  16. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  17. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  18. package/dist/components/BinObjBrowser.js +2 -0
  19. package/dist/components/BinObjBrowser.js.map +1 -0
  20. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  21. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  22. package/dist/components/CertViewer.js +2 -0
  23. package/dist/components/CertViewer.js.map +1 -0
  24. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  25. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  26. package/dist/components/ClassViewer.js +2 -0
  27. package/dist/components/ClassViewer.js.map +1 -0
  28. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  29. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  30. package/dist/components/CodeBlock.js +2 -0
  31. package/dist/components/CodeBlock.js.map +1 -0
  32. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  33. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  34. package/dist/components/CodeView.js +2 -0
  35. package/dist/components/CodeView.js.map +1 -0
  36. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +2 -0
  37. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  38. package/dist/components/CsvViewer.js +2 -0
  39. package/dist/components/CsvViewer.js.map +1 -0
  40. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  41. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  42. package/dist/components/DicomViewer.js +2 -0
  43. package/dist/components/DicomViewer.js.map +1 -0
  44. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  45. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  46. package/dist/components/DocxNavigationPane.js +2 -0
  47. package/dist/components/DocxNavigationPane.js.map +1 -0
  48. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +2 -0
  49. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  50. package/dist/components/DocxViewer.js +2 -0
  51. package/dist/components/DocxViewer.js.map +1 -0
  52. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  53. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  54. package/dist/components/DrawioViewer.js +2 -0
  55. package/dist/components/DrawioViewer.js.map +1 -0
  56. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  57. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  58. package/dist/components/DxfViewer.js +2 -0
  59. package/dist/components/DxfViewer.js.map +1 -0
  60. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  61. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  62. package/dist/components/EbookViewer.js +2 -0
  63. package/dist/components/EbookViewer.js.map +1 -0
  64. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  65. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  66. package/dist/components/EmlViewer.js +2 -0
  67. package/dist/components/EmlViewer.js.map +1 -0
  68. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  69. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  70. package/dist/components/EplViewer.js +2 -0
  71. package/dist/components/EplViewer.js.map +1 -0
  72. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  73. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  74. package/dist/components/FileViewerBody.js +2 -0
  75. package/dist/components/FileViewerBody.js.map +1 -0
  76. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +2 -0
  77. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -0
  78. package/dist/components/FileViewerPanel.js +2 -0
  79. package/dist/components/FileViewerPanel.js.map +1 -0
  80. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +2 -0
  81. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  82. package/dist/components/FontViewer.js +2 -0
  83. package/dist/components/FontViewer.js.map +1 -0
  84. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  85. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  86. package/dist/components/FrontmatterCard.js +2 -0
  87. package/dist/components/FrontmatterCard.js.map +1 -0
  88. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js +2 -0
  89. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  90. package/dist/components/GeoViewer.js +2 -0
  91. package/dist/components/GeoViewer.js.map +1 -0
  92. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  93. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  94. package/dist/components/HarViewer.js +2 -0
  95. package/dist/components/HarViewer.js.map +1 -0
  96. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  97. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  98. package/dist/components/HexViewer.js +2 -0
  99. package/dist/components/HexViewer.js.map +1 -0
  100. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  101. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  102. package/dist/components/HprofViewer.js +2 -0
  103. package/dist/components/HprofViewer.js.map +1 -0
  104. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  105. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  106. package/dist/components/ImageLightbox.js +2 -0
  107. package/dist/components/ImageLightbox.js.map +1 -0
  108. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +2 -0
  109. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -0
  110. package/dist/components/InlineText.js +2 -0
  111. package/dist/components/InlineText.js.map +1 -0
  112. package/dist/components/JksViewer.js +2 -0
  113. package/dist/components/JksViewer.js.map +1 -0
  114. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  115. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  116. package/dist/components/JsonlViewer.js +2 -0
  117. package/dist/components/JsonlViewer.js.map +1 -0
  118. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  119. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  120. package/dist/components/LogViewer.js +2 -0
  121. package/dist/components/LogViewer.js.map +1 -0
  122. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  123. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  124. package/dist/components/LrcViewer.js +2 -0
  125. package/dist/components/LrcViewer.js.map +1 -0
  126. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  127. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  128. package/dist/components/MarkdownTable.js +2 -0
  129. package/dist/components/MarkdownTable.js.map +1 -0
  130. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js +2 -0
  131. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  132. package/dist/components/MarkdownText.js +2 -0
  133. package/dist/components/MarkdownText.js.map +1 -0
  134. package/dist/components/MhtViewer.js +2 -0
  135. package/dist/components/MhtViewer.js.map +1 -0
  136. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  137. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  138. package/dist/components/MindMapView.js +2 -0
  139. package/dist/components/MindMapView.js.map +1 -0
  140. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js +2 -0
  141. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  142. package/dist/components/MissingDependency.js +2 -0
  143. package/dist/components/MissingDependency.js.map +1 -0
  144. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js +2 -0
  145. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js.map +1 -0
  146. package/dist/components/ModelViewer.js +2 -0
  147. package/dist/components/ModelViewer.js.map +1 -0
  148. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  149. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  150. package/dist/components/MsiViewer.js +2 -0
  151. package/dist/components/MsiViewer.js.map +1 -0
  152. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  153. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  154. package/dist/components/NotebookViewer.js +2 -0
  155. package/dist/components/NotebookViewer.js.map +1 -0
  156. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  157. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  158. package/dist/components/PdfViewer.js +2 -0
  159. package/dist/components/PdfViewer.js.map +1 -0
  160. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  161. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  162. package/dist/components/PptxNavigationPane.js +2 -0
  163. package/dist/components/PptxNavigationPane.js.map +1 -0
  164. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +2 -0
  165. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  166. package/dist/components/PptxViewer.js +3 -1
  167. package/dist/components/PptxViewer.js.map +1 -0
  168. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +55 -49
  169. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  170. package/dist/components/PsdViewer.js +2 -0
  171. package/dist/components/PsdViewer.js.map +1 -0
  172. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  173. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  174. package/dist/components/QmcAudioViewer.js +2 -0
  175. package/dist/components/QmcAudioViewer.js.map +1 -0
  176. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  177. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  178. package/dist/components/RasterViewer.js +2 -0
  179. package/dist/components/RasterViewer.js.map +1 -0
  180. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  181. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  182. package/dist/components/RtfViewer.js +2 -0
  183. package/dist/components/RtfViewer.js.map +1 -0
  184. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  185. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  186. package/dist/components/SevenZipBrowser.js +2 -0
  187. package/dist/components/SevenZipBrowser.js.map +1 -0
  188. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  189. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  190. package/dist/components/SqliteViewer.js +2 -0
  191. package/dist/components/SqliteViewer.js.map +1 -0
  192. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  193. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  194. package/dist/components/SubtitleViewer.js +2 -0
  195. package/dist/components/SubtitleViewer.js.map +1 -0
  196. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  197. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  198. package/dist/components/XlsxChart.js +2 -0
  199. package/dist/components/XlsxChart.js.map +1 -0
  200. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +2 -0
  201. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  202. package/dist/components/XlsxViewer.js +2 -0
  203. package/dist/components/XlsxViewer.js.map +1 -0
  204. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  205. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  206. package/dist/components/XmindViewer.js +2 -0
  207. package/dist/components/XmindViewer.js.map +1 -0
  208. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  209. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  210. package/dist/components/ZipBrowser.js +2 -0
  211. package/dist/components/ZipBrowser.js.map +1 -0
  212. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  213. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  214. package/dist/components/ZoomImage.js +2 -0
  215. package/dist/components/ZoomImage.js.map +1 -0
  216. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +2 -0
  217. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -0
  218. package/dist/components/epl/EplCodeBlock.js +2 -0
  219. package/dist/components/epl/EplCodeBlock.js.map +1 -0
  220. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  221. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  222. package/dist/components/epl/EplDoc.js +2 -0
  223. package/dist/components/epl/EplDoc.js.map +1 -0
  224. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js +2 -0
  225. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js.map +1 -0
  226. package/dist/components/epl/EplMethod.js +2 -0
  227. package/dist/components/epl/EplMethod.js.map +1 -0
  228. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js +2 -0
  229. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js.map +1 -0
  230. package/dist/components/epl/EplResource.js +2 -0
  231. package/dist/components/epl/EplResource.js.map +1 -0
  232. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js +2 -0
  233. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -0
  234. package/dist/components/epl/EplTree.js +2 -0
  235. package/dist/components/epl/EplTree.js.map +1 -0
  236. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js +2 -0
  237. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js.map +1 -0
  238. package/dist/components/epl/EplVarTable.js +2 -0
  239. package/dist/components/epl/EplVarTable.js.map +1 -0
  240. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js +2 -0
  241. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  242. package/dist/components/epl/numbering.js +2 -0
  243. package/dist/components/epl/numbering.js.map +1 -0
  244. package/dist/components/icons/Icon.js +2 -0
  245. package/dist/components/icons/Icon.js.map +1 -0
  246. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js +2 -0
  247. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js.map +1 -0
  248. package/dist/components/icons/iconPaths.js +2 -0
  249. package/dist/components/icons/iconPaths.js.map +1 -0
  250. package/dist/components/optionalViewer.js +2 -0
  251. package/dist/components/optionalViewer.js.map +1 -0
  252. package/dist/components/ui/ClampBlock.js +2 -0
  253. package/dist/components/ui/ClampBlock.js.map +1 -0
  254. package/dist/composables/useElementNavigation.js +2 -0
  255. package/dist/composables/useElementNavigation.js.map +1 -0
  256. package/dist/composables/useFileViewer.js +2 -0
  257. package/dist/composables/useFileViewer.js.map +1 -0
  258. package/dist/composables/useLightbox.js +2 -0
  259. package/dist/composables/useLightbox.js.map +1 -0
  260. package/dist/composables/useLogRules.js +2 -0
  261. package/dist/composables/useLogRules.js.map +1 -0
  262. package/dist/lib/aes.js +2 -0
  263. package/dist/lib/aes.js.map +1 -0
  264. package/dist/lib/appx.js +2 -0
  265. package/dist/lib/appx.js.map +1 -0
  266. package/dist/lib/archiveHtml.js +2 -0
  267. package/dist/lib/archiveHtml.js.map +1 -0
  268. package/dist/lib/asn1.js +2 -0
  269. package/dist/lib/asn1.js.map +1 -0
  270. package/dist/lib/binobj.js +2 -0
  271. package/dist/lib/binobj.js.map +1 -0
  272. package/dist/lib/brotli.js +2 -0
  273. package/dist/lib/brotli.js.map +1 -0
  274. package/dist/lib/bzip2.js +2 -0
  275. package/dist/lib/bzip2.js.map +1 -0
  276. package/dist/lib/classfile.js +2 -0
  277. package/dist/lib/classfile.js.map +1 -0
  278. package/dist/lib/codePreview.js +2 -0
  279. package/dist/lib/codePreview.js.map +1 -0
  280. package/dist/lib/codefold.js +2 -0
  281. package/dist/lib/codefold.js.map +1 -0
  282. package/dist/lib/codeformat.js +2 -0
  283. package/dist/lib/codeformat.js.map +1 -0
  284. package/dist/lib/csv.js +2 -0
  285. package/dist/lib/csv.js.map +1 -0
  286. package/dist/lib/des.js +2 -0
  287. package/dist/lib/des.js.map +1 -0
  288. package/dist/lib/diagram.js +2 -0
  289. package/dist/lib/diagram.js.map +1 -0
  290. package/dist/lib/dicom.js +2 -0
  291. package/dist/lib/dicom.js.map +1 -0
  292. package/dist/lib/dicomCodecs.js +2 -0
  293. package/dist/lib/dicomCodecs.js.map +1 -0
  294. package/dist/lib/dicomRender.js +2 -0
  295. package/dist/lib/dicomRender.js.map +1 -0
  296. package/dist/lib/docxNavigation.js +2 -0
  297. package/dist/lib/docxNavigation.js.map +1 -0
  298. package/dist/lib/dxf.js +2 -0
  299. package/dist/lib/dxf.js.map +1 -0
  300. package/dist/lib/eml.js +2 -0
  301. package/dist/lib/eml.js.map +1 -0
  302. package/dist/lib/encoding.js +2 -0
  303. package/dist/lib/encoding.js.map +1 -0
  304. package/dist/lib/epl/code/codeDataParser.js +2 -0
  305. package/dist/lib/epl/code/codeDataParser.js.map +1 -0
  306. package/dist/lib/epl/code/codeLines.js +2 -0
  307. package/dist/lib/epl/code/codeLines.js.map +1 -0
  308. package/dist/lib/epl/code/textCode.js +2 -0
  309. package/dist/lib/epl/code/textCode.js.map +1 -0
  310. package/dist/lib/epl/eplDocument.js +2 -0
  311. package/dist/lib/epl/eplDocument.js.map +1 -0
  312. package/dist/lib/epl/imageList.js +2 -0
  313. package/dist/lib/epl/imageList.js.map +1 -0
  314. package/dist/lib/epl/index.js +2 -0
  315. package/dist/lib/epl/index.js.map +1 -0
  316. package/dist/lib/epl/io/binaryReader.js +2 -0
  317. package/dist/lib/epl/io/binaryReader.js.map +1 -0
  318. package/dist/lib/epl/io/encoding.js +2 -0
  319. package/dist/lib/epl/io/encoding.js.map +1 -0
  320. package/dist/lib/epl/model/eplSystemId.js +2 -0
  321. package/dist/lib/epl/model/eplSystemId.js.map +1 -0
  322. package/dist/lib/epl/names/eplLibData.js +2 -0
  323. package/dist/lib/epl/names/eplLibData.js.map +1 -0
  324. package/dist/lib/epl/names/idToNameMap.js +2 -0
  325. package/dist/lib/epl/names/idToNameMap.js.map +1 -0
  326. package/dist/lib/epl/names/libNameProvider.js +2 -0
  327. package/dist/lib/epl/names/libNameProvider.js.map +1 -0
  328. package/dist/lib/epl/program.js +2 -0
  329. package/dist/lib/epl/program.js.map +1 -0
  330. package/dist/lib/epl/projectFileReader.js +2 -0
  331. package/dist/lib/epl/projectFileReader.js.map +1 -0
  332. package/dist/lib/fb2.js +2 -0
  333. package/dist/lib/fb2.js.map +1 -0
  334. package/dist/lib/fileType.js +2 -0
  335. package/dist/lib/fileType.js.map +1 -0
  336. package/dist/lib/fonts.js +2 -0
  337. package/dist/lib/fonts.js.map +1 -0
  338. package/dist/lib/format.js +2 -0
  339. package/dist/lib/format.js.map +1 -0
  340. package/dist/lib/frontmatter.js +2 -0
  341. package/dist/lib/frontmatter.js.map +1 -0
  342. package/dist/lib/geo.js +2 -0
  343. package/dist/lib/geo.js.map +1 -0
  344. package/dist/lib/har.js +2 -0
  345. package/dist/lib/har.js.map +1 -0
  346. package/dist/lib/highlight.js +2 -0
  347. package/dist/lib/highlight.js.map +1 -0
  348. package/dist/lib/highlightLangs.js +2 -0
  349. package/dist/lib/highlightLangs.js.map +1 -0
  350. package/dist/lib/hprof.js +2 -0
  351. package/dist/lib/hprof.js.map +1 -0
  352. package/dist/lib/ipynb.js +2 -0
  353. package/dist/lib/ipynb.js.map +1 -0
  354. package/dist/lib/jks.js +2 -0
  355. package/dist/lib/jks.js.map +1 -0
  356. package/dist/lib/jsonl.js +2 -0
  357. package/dist/lib/jsonl.js.map +1 -0
  358. package/dist/lib/lrc.js +2 -0
  359. package/dist/lib/lrc.js.map +1 -0
  360. package/dist/lib/lzma.js +2 -0
  361. package/dist/lib/lzma.js.map +1 -0
  362. package/dist/lib/markdown/blocks.js +2 -0
  363. package/dist/lib/markdown/blocks.js.map +1 -0
  364. package/dist/lib/markdown/inline.js +2 -0
  365. package/dist/lib/markdown/inline.js.map +1 -0
  366. package/dist/lib/markdown/math.js +2 -0
  367. package/dist/lib/markdown/math.js.map +1 -0
  368. package/dist/lib/markdown/segmentize.js +2 -0
  369. package/dist/lib/markdown/segmentize.js.map +1 -0
  370. package/dist/lib/markdown/styles.js +2 -0
  371. package/dist/lib/markdown/styles.js.map +1 -0
  372. package/dist/lib/mht.js +2 -0
  373. package/dist/lib/mht.js.map +1 -0
  374. package/dist/lib/midi.js +2 -0
  375. package/dist/lib/midi.js.map +1 -0
  376. package/dist/lib/mime.js +2 -0
  377. package/dist/lib/mime.js.map +1 -0
  378. package/dist/lib/mindmap.js +2 -0
  379. package/dist/lib/mindmap.js.map +1 -0
  380. package/dist/lib/mobi.js +2 -0
  381. package/dist/lib/mobi.js.map +1 -0
  382. package/dist/lib/msi.js +2 -0
  383. package/dist/lib/msi.js.map +1 -0
  384. package/dist/lib/optionalDep.js +2 -0
  385. package/dist/lib/optionalDep.js.map +1 -0
  386. package/dist/lib/pkcs12.js +2 -0
  387. package/dist/lib/pkcs12.js.map +1 -0
  388. package/dist/lib/pptxNavigation.js +2 -0
  389. package/dist/lib/pptxNavigation.js.map +1 -0
  390. package/dist/lib/qmc.js +2 -0
  391. package/dist/lib/qmc.js.map +1 -0
  392. package/dist/lib/rar.js +2 -0
  393. package/dist/lib/rar.js.map +1 -0
  394. package/dist/lib/rawPreview.js +2 -0
  395. package/dist/lib/rawPreview.js.map +1 -0
  396. package/dist/lib/rc2.js +2 -0
  397. package/dist/lib/rc2.js.map +1 -0
  398. package/dist/lib/resolveInput.js +2 -0
  399. package/dist/lib/resolveInput.js.map +1 -0
  400. package/dist/lib/rtf.js +2 -0
  401. package/dist/lib/rtf.js.map +1 -0
  402. package/dist/lib/sevenZip.js +2 -0
  403. package/dist/lib/sevenZip.js.map +1 -0
  404. package/dist/lib/sha1.js +2 -0
  405. package/dist/lib/sha1.js.map +1 -0
  406. package/dist/lib/sha256.js +2 -0
  407. package/dist/lib/sha256.js.map +1 -0
  408. package/dist/lib/speechAudio.js +2 -0
  409. package/dist/lib/speechAudio.js.map +1 -0
  410. package/dist/lib/sqlite.js +2 -0
  411. package/dist/lib/sqlite.js.map +1 -0
  412. package/dist/lib/subtitle.js +2 -0
  413. package/dist/lib/subtitle.js.map +1 -0
  414. package/dist/lib/tarball.js +2 -0
  415. package/dist/lib/tarball.js.map +1 -0
  416. package/dist/lib/viewerFormats.js +2 -0
  417. package/dist/lib/viewerFormats.js.map +1 -0
  418. package/dist/lib/viewerKind.js +2 -0
  419. package/dist/lib/viewerKind.js.map +1 -0
  420. package/dist/lib/x509.js +2 -0
  421. package/dist/lib/x509.js.map +1 -0
  422. package/dist/lib/xlsx.js +2 -0
  423. package/dist/lib/xlsx.js.map +1 -0
  424. package/dist/lib/xlsxCharts.js +2 -0
  425. package/dist/lib/xlsxCharts.js.map +1 -0
  426. package/dist/lib/xmind.js +2 -0
  427. package/dist/lib/xmind.js.map +1 -0
  428. package/dist/lib/zip.js +2 -0
  429. package/dist/lib/zip.js.map +1 -0
  430. package/dist/style.css +1 -1
  431. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HprofViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/HprofViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport {\n Hprof,\n HprofParseError,\n decodeStringValue,\n type CollectTarget,\n type HprofClassRow,\n type HprofInstance,\n type HprofObjectView,\n} from '@/lib/hprof'\nimport { formatBytes } from '@/lib/format'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * The side viewer's HPROF (JVM heap dump) mode: fetch a `.hprof`, stream-parse it in-browser (see lib/hprof.ts),\n * and show a class histogram (instances + shallow size) — then drill into ONE class (or array type) to page\n * through its objects with decoded fields / array content. Mirrors BinObjBrowser: the bytes come from the same\n * download endpoint and never leave the browser.\n *\n * Memory is the whole design constraint (a heap dump can be many GB):\n * - Parsing never loads the file into the JS heap — the Blob is streamed in 8 MB chunks, and only bounded\n * per-class state is kept (Pass A in lib/hprof.ts).\n * - Drilling into a target collects at most a fixed cap of object rows (MAX_COLLECT) via Pass B, then PAGES that\n * cap — so a class with millions of instances still shows instantly. When it overflows the cap, the on-screen\n * filter is local to the loaded rows; a \"search the whole file\" action re-streams with the filter pushed down.\n * - Following a reference (jumping to `#id`) or reading a String's real text both use `resolveObjects`: an\n * on-demand streaming sweep that resolves just the requested ids, bounded to what's on screen. No id→offset\n * index is built, so memory stays ∝ the visible batch, not the object count.\n *\n * Large-file gate (like BinObjBrowser): unless the manifest size is known-small we don't auto-download — a\n * confirm card explains that parsing pulls the whole file, and only fetches on click.\n */\nconst props = defineProps<{ url: string; name: string; size?: number }>()\n\nconst LARGE_THRESHOLD = 20 * 1024 * 1024 // auto-load ≤ 20 MB; larger (or unknown) asks first\nconst TABLE_CAP = 2000 // max histogram rows rendered at once (filter to narrow past this)\nconst PAGE_SIZE = 50 // objects per page in the drill-in\nconst STR_CLASS = 'java/lang/String'\n\ntype Status = 'confirm' | 'loading' | 'error' | 'ready'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst progress = ref(0) // 0..1, drives the parse/scan bar\nconst hprof = ref<Hprof | null>(null)\n\nconst view = ref<'overview' | 'class'>('overview')\n\n// ── load + parse ──────────────────────────────────────────────────────────────────────────────────\nfunction decideInitial(): void {\n hprof.value = null\n view.value = 'overview'\n overviewQuery.value = ''\n errorMsg.value = ''\n progress.value = 0\n objStack.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 progress.value = 0\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const blob = await res.blob()\n hprof.value = await Hprof.parse(blob, (pos, size) => {\n progress.value = size ? pos / size : 1\n })\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof HprofParseError || e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nconst pct = computed(() => Math.round(progress.value * 100))\n\n// ── overview: file meta + class histogram ───────────────────────────────────────────────────────────\nconst summary = computed(() => hprof.value?.summary ?? null)\n\nconst metaRows = computed<{ label: string; value: string }[]>(() => {\n const s = summary.value\n if (!s) return []\n const ts = Number(s.timestamp)\n return [\n { label: '对象总数', value: s.instanceCount.toLocaleString() },\n { label: '类数量', value: s.classCount.toLocaleString() },\n { label: 'Shallow 大小', value: formatBytes(s.heapBytes) },\n { label: 'GC Roots', value: s.gcRoots.toLocaleString() },\n { label: '字符串常量', value: s.stringCount.toLocaleString() },\n { label: 'ID 宽度', value: `${s.idSize} 字节` },\n { label: '转储时间', value: ts > 0 ? new Date(ts).toLocaleString() : '—' },\n { label: '文件大小', value: formatBytes(s.fileSize) },\n ]\n})\n\nconst overviewQuery = ref('')\ntype SortKey = 'bytes' | 'count' | 'name'\nconst sortKey = ref<SortKey>('bytes')\nconst sortAsc = ref(false)\n\nfunction toggleSort(key: SortKey): void {\n if (sortKey.value === key) sortAsc.value = !sortAsc.value\n else {\n sortKey.value = key\n sortAsc.value = key === 'name' // names default A→Z, sizes/counts default high→low\n }\n}\n\nconst sortedRows = computed<HprofClassRow[]>(() => {\n const rows = summary.value?.rows ?? []\n const q = overviewQuery.value.trim().toLowerCase()\n const filtered = q ? rows.filter((r) => r.name.toLowerCase().includes(q)) : rows\n const dir = sortAsc.value ? 1 : -1\n const key = sortKey.value\n // Copy before sort so we never mutate the parser's array.\n return [...filtered].sort((a, b) => {\n if (key === 'name') return dir * a.name.localeCompare(b.name)\n if (key === 'count') return dir * (a.count - b.count) || b.bytes - a.bytes\n return dir * (a.bytes - b.bytes) || b.count - a.count\n })\n})\nconst visibleRows = computed(() => sortedRows.value.slice(0, TABLE_CAP))\nconst tableTruncated = computed(() => sortedRows.value.length > TABLE_CAP)\n\n// ── drill-in (class OR array) ─────────────────────────────────────────────────────────────────────\nconst classSel = ref<HprofClassRow | null>(null)\nconst collecting = ref(false)\nconst collected = ref<HprofInstance[]>([])\nconst collectTotal = ref(0)\nconst collectTruncated = ref(false)\nconst collectQuery = ref('') // the class-view filter box\nconst searchedQuery = ref<string | null>(null) // the query a whole-file scan currently reflects\nconst page = ref(1)\nconst expanded = ref<Set<string>>(new Set())\n\nasync function openTarget(row: HprofClassRow): Promise<void> {\n if (!row.target || !hprof.value) return\n classSel.value = row\n view.value = 'class'\n collectQuery.value = ''\n searchedQuery.value = null\n page.value = 1\n expanded.value = new Set()\n objStack.value = []\n strCache.value = new Map()\n await runCollect(row.target, '')\n}\n\nasync function runCollect(target: CollectTarget, filter: string): Promise<void> {\n if (!hprof.value) return\n collecting.value = true\n progress.value = 0\n try {\n const res = await hprof.value.collect(target, {\n filter: filter || undefined,\n onProgress: (pos, size) => {\n progress.value = size ? pos / size : 1\n },\n })\n collected.value = res.rows\n collectTotal.value = res.total\n collectTruncated.value = res.truncated\n searchedQuery.value = filter ? filter : null\n page.value = 1\n expanded.value = new Set()\n strCache.value = new Map()\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n } finally {\n collecting.value = false\n }\n}\n\nfunction backToOverview(): void {\n view.value = 'overview'\n classSel.value = null\n collected.value = []\n objStack.value = []\n}\n\n// Local filter over the loaded rows (instant). When a whole-file scan is active for the SAME query, its rows\n// are already the matches, so this passes them through.\nconst localFiltered = computed<HprofInstance[]>(() => {\n const q = collectQuery.value.trim().toLowerCase()\n if (!q || q === searchedQuery.value?.toLowerCase()) return collected.value\n return collected.value.filter((o) => rowMatches(o, q))\n})\n\n// Same match as the parser's whole-file filter: id, field `name=value`, or array content (text/hex/elements).\nfunction rowMatches(o: HprofInstance, q: string): boolean {\n if (o.id.includes(q)) return true\n if (o.fields.some((f) => (f.name + '=' + f.value).toLowerCase().includes(q))) return true\n const a = o.array\n if (a) {\n if (a.text && a.text.toLowerCase().includes(q)) return true\n if (a.hex && a.hex.includes(q)) return true\n if (a.elements?.some((e) => e.value.toLowerCase().includes(q))) return true\n }\n return false\n}\n\n// Offer a whole-file scan only when the target overflowed the cap and the current query isn't already scanned.\nconst canWholeFileSearch = computed(\n () =>\n collectTruncated.value &&\n collectQuery.value.trim().length > 0 &&\n collectQuery.value.trim().toLowerCase() !== (searchedQuery.value ?? '').toLowerCase(),\n)\nfunction wholeFileSearch(): void {\n if (classSel.value?.target) void runCollect(classSel.value.target, collectQuery.value.trim())\n}\nfunction clearWholeFileSearch(): void {\n collectQuery.value = ''\n if (classSel.value?.target && searchedQuery.value != null)\n void runCollect(classSel.value.target, '')\n}\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(localFiltered.value.length / PAGE_SIZE)))\nconst pageRows = computed(() => {\n const start = (page.value - 1) * PAGE_SIZE\n return localFiltered.value.slice(start, start + PAGE_SIZE)\n})\n// Keep the page in range as the filtered set shrinks/grows.\nwatch(localFiltered, () => {\n if (page.value > pageCount.value) page.value = pageCount.value\n})\nfunction prevPage(): void {\n if (page.value > 1) page.value--\n}\nfunction nextPage(): void {\n if (page.value < pageCount.value) page.value++\n}\n\nfunction toggleExpand(id: string): void {\n const next = new Set(expanded.value)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n expanded.value = next\n}\n\n// ── String content: dereference each row's `value` byte[]/char[] and decode with its `coder` (lazy, per page) ──\n// A class is \"stringy\" when it's java/lang/String, or looks like it (a `value` object field + a `coder` field).\nconst isStringy = computed(() => {\n const r = collected.value[0]\n if (!r) return false\n const hasValue = r.fields.some(\n (f) => f.name === 'value' && (f.kind === 'ref' || f.kind === 'null'),\n )\n const hasCoder = r.fields.some((f) => f.name === 'coder')\n return hasValue && (classSel.value?.name === STR_CLASS || hasCoder)\n})\nconst strCache = ref<Map<string, string>>(new Map()) // instance id → decoded text\nconst derefBusy = ref(false)\nfunction strText(o: HprofInstance): string | undefined {\n return strCache.value.get(o.id)\n}\n\nasync function derefPage(): Promise<void> {\n if (!hprof.value || !isStringy.value) return\n const todo: { instId: string; refId: string; coder: number | null }[] = []\n for (const o of pageRows.value) {\n if (strCache.value.has(o.id)) continue\n const vf = o.fields.find((f) => f.name === 'value')\n if (!vf || vf.kind !== 'ref' || !vf.refId) {\n // null value ⇒ empty string; no layout ⇒ nothing to show\n strCache.value.set(o.id, vf?.kind === 'null' ? '' : '')\n continue\n }\n const cf = o.fields.find((f) => f.name === 'coder')\n const coder = cf ? parseInt(cf.value, 10) : null\n todo.push({ instId: o.id, refId: vf.refId, coder: Number.isNaN(coder) ? null : coder })\n }\n if (!todo.length) return\n derefBusy.value = true\n try {\n const m = await hprof.value.resolveObjects(new Set(todo.map((t) => t.refId)))\n const next = new Map(strCache.value)\n for (const t of todo) {\n const v = m.get(t.refId)\n next.set(\n t.instId,\n v && v.kind === 'primArray' ? decodeStringValue(v.rawBytes, v.elemType, t.coder) : '',\n )\n }\n strCache.value = next\n } catch {\n // deref is best-effort; leave the raw #id showing on failure\n } finally {\n derefBusy.value = false\n }\n}\nwatch([pageRows, isStringy], () => void derefPage())\n\n// Collapsed one-line preview per row: array content, decoded String, or the first few fields.\nfunction previewLine(o: HprofInstance): string {\n if (o.array) return arrayPreviewLine(o.array)\n if (isStringy.value) {\n const t = strText(o)\n if (t !== undefined) return t === '' ? '\"\"' : `\"${oneLine(clip(t, 160))}\"`\n }\n if (!o.fields.length) return ''\n const head = o.fields.slice(0, 4).map((f) => `${f.name}=${f.value}`)\n return head.join(', ') + (o.fields.length > 4 ? ', …' : '')\n}\nfunction arrayPreviewLine(a: NonNullable<HprofInstance['array']>): string {\n const tag = `(${a.length})`\n if (a.text != null) return `${tag} \"${oneLine(clip(a.text, 160))}\"`\n if (a.elements)\n return (\n `${tag} ` +\n a.elements\n .slice(0, 8)\n .map((e) => e.value)\n .join(', ')\n )\n if (a.hex) return `${tag} ${a.hex}`\n return tag\n}\n\n// ── reference jump: follow a `#id` to the object it points at, with a back stack ─────────────────────\nconst objStack = ref<HprofObjectView[]>([])\nconst objBusy = ref(false)\nconst jumpMsg = ref('')\nconst currentObj = computed<HprofObjectView | null>(() =>\n objStack.value.length ? objStack.value[objStack.value.length - 1] : null,\n)\n\nasync function jumpTo(refId: string | undefined): Promise<void> {\n if (!refId || !hprof.value) return\n jumpMsg.value = ''\n objBusy.value = true\n try {\n const m = await hprof.value.resolveObjects(new Set([refId]))\n const v = m.get(refId)\n if (v) objStack.value = [...objStack.value, v]\n else jumpMsg.value = `#${refId} 未找到(可能已被回收或不在本转储中)`\n } catch (e) {\n jumpMsg.value = e instanceof Error ? e.message : String(e)\n } finally {\n objBusy.value = false\n }\n}\nfunction popObj(): void {\n objStack.value = objStack.value.slice(0, -1)\n jumpMsg.value = ''\n}\nfunction closeObjStack(): void {\n objStack.value = []\n jumpMsg.value = ''\n}\n\n// One-hop annotation of the detail's references (className, or a String's/byte[]'s text) so a jump is informative.\nconst detailAnnots = ref<Map<string, string>>(new Map())\nwatch(currentObj, async (v) => {\n detailAnnots.value = new Map()\n if (!v || !hprof.value) return\n const ids = new Set<string>()\n if (v.kind === 'instance')\n for (const f of v.fields) if (f.kind === 'ref' && f.refId) ids.add(f.refId)\n if (v.kind === 'objArray' && v.array.elements)\n for (const e of v.array.elements) if (e.kind === 'ref' && e.refId) ids.add(e.refId)\n if (!ids.size) return\n const captured = v\n const m = await hprof.value.resolveObjects(ids)\n if (currentObj.value !== captured) return // navigated away while resolving\n const annots = new Map<string, string>()\n for (const [id, ov] of m) annots.set(id, viewSummary(ov))\n detailAnnots.value = annots\n})\nfunction annot(refId: string | undefined): string | undefined {\n return refId ? detailAnnots.value.get(refId) : undefined\n}\nfunction viewSummary(v: HprofObjectView): string {\n if (v.kind === 'class') return 'class ' + v.className\n if (v.kind === 'instance') return v.className\n if (v.kind === 'primArray')\n return v.array.text != null\n ? `${v.className} \"${oneLine(clip(v.array.text, 60))}\"`\n : `${v.className}[${v.length}]`\n return `${v.className.replace(/\\[\\]$/, '')}[${v.length}]` // objArray\n}\n\n// ── small text helpers ───────────────────────────────────────────────────────────────────────────\nfunction clip(s: string, n: number): string {\n return s.length > n ? s.slice(0, n) + '…' : s\n}\nfunction oneLine(s: string): string {\n return s.replace(/\\s+/g, ' ')\n}\n\nonMounted(decideInitial)\nwatch(() => props.url, decideInitial)\nonBeforeUnmount(() => {\n hprof.value = null\n})\n</script>\n\n<template>\n <div class=\"hp\">\n <!-- large-file gate -->\n <div v-if=\"status === 'confirm'\" class=\"hp__confirm\">\n <Icon name=\"code\" :size=\"32\" />\n <p class=\"hp__confirm-title\" :title=\"name\">{{ name }}</p>\n <p class=\"hp__confirm-sub\">\n {{\n size != null ? `文件较大(${formatBytes(size)})` : '文件大小未知'\n }},解析堆转储需将整个文件下载到浏览器(流式解析、内存可控)。\n </p>\n <button type=\"button\" class=\"hp__btn\" @click=\"load\">加载并解析</button>\n </div>\n\n <div v-else-if=\"status === 'loading'\" class=\"hp__progress\">\n <p class=\"hp__progress-label\">正在解析堆转储… {{ pct }}%</p>\n <div class=\"hp__bar\"><div class=\"hp__bar-fill\" :style=\"{ width: pct + '%' }\" /></div>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"hp__msg hp__msg--error\">\n <p>{{ errorMsg }}</p>\n <button type=\"button\" class=\"hp__retry\" @click=\"load\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready' && summary\">\n <!-- ── object detail (reference jump) — sits above whatever base view is active ── -->\n <template v-if=\"currentObj\">\n <div class=\"hp__crumb\">\n <button type=\"button\" class=\"hp__back\" @click=\"popObj\">\n <Icon name=\"chevronLeft\" :size=\"14\" /> 返回\n </button>\n <span class=\"hp__crumb-name\">\n <span class=\"hp__crumb-cls\">{{ currentObj.className }}</span>\n <span class=\"hp__objid\">#{{ currentObj.id }}</span>\n </span>\n <button\n type=\"button\"\n class=\"hp__ghostbtn hp__crumb-close\"\n title=\"回到列表\"\n @click=\"closeObjStack\"\n >\n 列表\n </button>\n </div>\n\n <div v-if=\"objBusy\" class=\"hp__progress hp__progress--inline\">\n <p class=\"hp__progress-label\">正在解析对象…</p>\n </div>\n <div v-else class=\"hp__list\">\n <!-- instance -->\n <dl v-if=\"currentObj.kind === 'instance'\" class=\"hp__fields hp__fields--detail\">\n <p v-if=\"!currentObj.fields.length\" class=\"hp__nofields\">(无字段或无法解析布局)</p>\n <template v-for=\"(f, fi) in currentObj.fields\" :key=\"fi\">\n <dt class=\"hp__fkey\">{{ f.name }}</dt>\n <dd class=\"hp__fval\" :class=\"{ 'hp__fval--null': f.kind === 'null' }\">\n <template v-if=\"f.kind === 'ref'\">\n <button type=\"button\" class=\"hp__reflink\" @click=\"jumpTo(f.refId)\">\n {{ f.value }}\n </button>\n <span v-if=\"annot(f.refId)\" class=\"hp__annot\">→ {{ annot(f.refId) }}</span>\n </template>\n <span v-else>{{ f.value }}</span>\n </dd>\n </template>\n </dl>\n\n <!-- primitive array -->\n <div v-else-if=\"currentObj.kind === 'primArray'\" class=\"hp__arr\">\n <div class=\"hp__arrmeta\">\n {{ currentObj.className }} · 长度 {{ currentObj.length.toLocaleString() }}\n </div>\n <pre v-if=\"currentObj.array.text != null\" class=\"hp__arrtext\">{{\n currentObj.array.text\n }}</pre>\n <div v-if=\"currentObj.array.hex\" class=\"hp__arrhex\">{{ currentObj.array.hex }}</div>\n <div v-if=\"currentObj.array.elements\" class=\"hp__arrels\">\n <span v-for=\"(e, ei) in currentObj.array.elements\" :key=\"ei\" class=\"hp__arrel\">\n {{ e.value }}\n </span>\n </div>\n <p v-if=\"currentObj.array.truncated\" class=\"hp__nofields\">\n (预览已截断,仅显示前若干项)\n </p>\n </div>\n\n <!-- object array -->\n <div v-else-if=\"currentObj.kind === 'objArray'\" class=\"hp__arr\">\n <div class=\"hp__arrmeta\">\n {{ currentObj.className }} · 长度 {{ currentObj.length.toLocaleString() }}\n </div>\n <div class=\"hp__arrels hp__arrels--obj\">\n <template v-for=\"(e, ei) in currentObj.array.elements ?? []\" :key=\"ei\">\n <button\n v-if=\"e.kind === 'ref'\"\n type=\"button\"\n class=\"hp__reflink hp__reflink--el\"\n @click=\"jumpTo(e.refId)\"\n >\n <span class=\"hp__eli\">[{{ e.name }}]</span> {{ e.value }}\n <span v-if=\"annot(e.refId)\" class=\"hp__annot\">→ {{ annot(e.refId) }}</span>\n </button>\n <span v-else class=\"hp__arrel\"\n ><span class=\"hp__eli\">[{{ e.name }}]</span> {{ e.value }}</span\n >\n </template>\n </div>\n <p v-if=\"currentObj.array.truncated\" class=\"hp__nofields\">\n (预览已截断,仅显示前若干项)\n </p>\n </div>\n\n <!-- class object -->\n <div v-else class=\"hp__arr\">\n <div class=\"hp__arrmeta\">类对象:{{ currentObj.className }}</div>\n <p class=\"hp__nofields\">这是一个 Class 对象。返回列表可在类直方图中查看它的实例。</p>\n </div>\n </div>\n </template>\n\n <!-- ── overview: file meta + class histogram ── -->\n <template v-else-if=\"view === 'overview'\">\n <dl class=\"hp__meta\">\n <template v-for=\"m in metaRows\" :key=\"m.label\">\n <dt class=\"hp__metakey\">{{ m.label }}</dt>\n <dd class=\"hp__metaval\">{{ m.value }}</dd>\n </template>\n </dl>\n\n <div class=\"hp__search\">\n <input v-model=\"overviewQuery\" type=\"search\" class=\"hp__input\" placeholder=\"搜索类名…\" />\n </div>\n\n <div class=\"hp__thead\">\n <button type=\"button\" class=\"hp__th hp__th--name\" @click=\"toggleSort('name')\">\n 类名<span v-if=\"sortKey === 'name'\" class=\"hp__arrow\">{{ sortAsc ? '▲' : '▼' }}</span>\n </button>\n <button type=\"button\" class=\"hp__th hp__th--num\" @click=\"toggleSort('count')\">\n 实例数<span v-if=\"sortKey === 'count'\" class=\"hp__arrow\">{{\n sortAsc ? '▲' : '▼'\n }}</span>\n </button>\n <button type=\"button\" class=\"hp__th hp__th--num\" @click=\"toggleSort('bytes')\">\n Shallow<span v-if=\"sortKey === 'bytes'\" class=\"hp__arrow\">{{\n sortAsc ? '▲' : '▼'\n }}</span>\n </button>\n </div>\n\n <div class=\"hp__list\">\n <p v-if=\"sortedRows.length === 0\" class=\"hp__msg\">无匹配类</p>\n <button\n v-for=\"(row, i) in visibleRows\"\n :key=\"i\"\n type=\"button\"\n class=\"hp__row\"\n :title=\"`查看 ${row.name} 的对象列表`\"\n @click=\"openTarget(row)\"\n >\n <span class=\"hp__name\">{{ row.name }}</span>\n <span class=\"hp__num\">{{ row.count.toLocaleString() }}</span>\n <span class=\"hp__num hp__num--bytes\">{{ formatBytes(row.bytes) }}</span>\n <Icon name=\"chevronRight\" :size=\"14\" class=\"hp__chev\" />\n </button>\n <p v-if=\"tableTruncated\" class=\"hp__more\">\n 仅显示前 {{ TABLE_CAP }} 个类,输入关键词缩小范围(共 {{ sortedRows.length }} 个)\n </p>\n </div>\n </template>\n\n <!-- ── drill-in: paginated object list ── -->\n <template v-else-if=\"classSel\">\n <div class=\"hp__crumb\">\n <button type=\"button\" class=\"hp__back\" @click=\"backToOverview\">\n <Icon name=\"chevronLeft\" :size=\"14\" /> 类列表\n </button>\n <span class=\"hp__crumb-name\" :title=\"classSel.name\">{{ classSel.name }}</span>\n </div>\n\n <div class=\"hp__classbar\">\n <span class=\"hp__classcount\">\n 共 {{ collectTotal.toLocaleString() }} 个实例<template v-if=\"searchedQuery\">\n 匹配「{{ searchedQuery }}」</template\n >\n </span>\n <span\n v-if=\"collectTruncated\"\n class=\"hp__tag\"\n title=\"为控制内存只加载了前若干个;用搜索缩小范围\"\n >\n 已加载前 {{ collected.length.toLocaleString() }} 个\n </span>\n <span v-if=\"derefBusy\" class=\"hp__tag\">解析字符串…</span>\n </div>\n\n <div class=\"hp__search hp__search--row\">\n <input\n v-model=\"collectQuery\"\n type=\"search\"\n class=\"hp__input\"\n placeholder=\"过滤 id / 字段值 / 内容…\"\n @keydown.enter=\"canWholeFileSearch && wholeFileSearch()\"\n />\n <button\n v-if=\"searchedQuery\"\n type=\"button\"\n class=\"hp__ghostbtn\"\n title=\"清除全文搜索\"\n @click=\"clearWholeFileSearch\"\n >\n 清除\n </button>\n </div>\n\n <p v-if=\"canWholeFileSearch\" class=\"hp__hint\">\n 过滤仅作用于已加载的 {{ collected.length.toLocaleString() }} 个。\n <button type=\"button\" class=\"hp__link\" @click=\"wholeFileSearch\">\n 在全部 {{ collectTotal.toLocaleString() }} 个实例中搜索「{{ collectQuery.trim() }}」\n </button>\n </p>\n\n <p v-if=\"jumpMsg\" class=\"hp__jumpmsg\">{{ jumpMsg }}</p>\n\n <div v-if=\"collecting\" class=\"hp__progress hp__progress--inline\">\n <p class=\"hp__progress-label\">正在扫描对象… {{ pct }}%</p>\n <div class=\"hp__bar\"><div class=\"hp__bar-fill\" :style=\"{ width: pct + '%' }\" /></div>\n </div>\n\n <template v-else>\n <div class=\"hp__list\">\n <p v-if=\"localFiltered.length === 0\" class=\"hp__msg\">无匹配对象</p>\n <div v-for=\"o in pageRows\" :key=\"o.id\" class=\"hp__obj\">\n <button type=\"button\" class=\"hp__objhead\" @click=\"toggleExpand(o.id)\">\n <Icon\n :name=\"expanded.has(o.id) ? 'chevronDown' : 'chevronRight'\"\n :size=\"13\"\n class=\"hp__objchev\"\n />\n <span class=\"hp__objid\">#{{ o.id }}</span>\n <span class=\"hp__objsize\">{{ formatBytes(o.size) }}</span>\n <span class=\"hp__objprev\">{{ previewLine(o) }}</span>\n </button>\n\n <div v-if=\"expanded.has(o.id)\" class=\"hp__detail\">\n <!-- array content -->\n <div v-if=\"o.array\" class=\"hp__arr\">\n <pre v-if=\"o.array.text != null\" class=\"hp__arrtext\">{{ o.array.text }}</pre>\n <div v-if=\"o.array.hex\" class=\"hp__arrhex\">{{ o.array.hex }}</div>\n <div\n v-if=\"o.array.elements\"\n class=\"hp__arrels\"\n :class=\"{ 'hp__arrels--obj': o.array.elemClassName }\"\n >\n <template v-for=\"(e, ei) in o.array.elements\" :key=\"ei\">\n <button\n v-if=\"e.kind === 'ref'\"\n type=\"button\"\n class=\"hp__reflink hp__reflink--el\"\n @click=\"jumpTo(e.refId)\"\n >\n <span class=\"hp__eli\">[{{ e.name }}]</span> {{ e.value }}\n </button>\n <span v-else class=\"hp__arrel\"\n ><span class=\"hp__eli\">[{{ e.name }}]</span> {{ e.value }}</span\n >\n </template>\n </div>\n <p v-if=\"o.array.truncated\" class=\"hp__nofields\">\n (预览已截断,完整长度 {{ o.array.length.toLocaleString() }})\n </p>\n </div>\n\n <!-- instance fields -->\n <dl v-else class=\"hp__fields\">\n <p v-if=\"!o.fields.length\" class=\"hp__nofields\">\n (无法解析字段布局,仅显示 id 与大小)\n </p>\n <template v-for=\"(f, fi) in o.fields\" :key=\"fi\">\n <dt class=\"hp__fkey\">{{ f.name }}</dt>\n <dd class=\"hp__fval\" :class=\"{ 'hp__fval--null': f.kind === 'null' }\">\n <template v-if=\"isStringy && f.name === 'value' && strText(o) !== undefined\">\n <span class=\"hp__str\">\"{{ strText(o) }}\"</span>\n <button\n v-if=\"f.kind === 'ref'\"\n type=\"button\"\n class=\"hp__reflink hp__reflink--sm\"\n @click=\"jumpTo(f.refId)\"\n >\n {{ f.value }}\n </button>\n </template>\n <template v-else-if=\"f.kind === 'ref'\">\n <button type=\"button\" class=\"hp__reflink\" @click=\"jumpTo(f.refId)\">\n {{ f.value }}\n </button>\n </template>\n <span v-else>{{ f.value }}</span>\n </dd>\n </template>\n </dl>\n </div>\n </div>\n </div>\n\n <div v-if=\"pageCount > 1\" class=\"hp__pager\">\n <button type=\"button\" class=\"hp__pagebtn\" :disabled=\"page <= 1\" @click=\"prevPage\">\n <Icon name=\"chevronLeft\" :size=\"14\" />\n </button>\n <span class=\"hp__pageinfo\">第 {{ page }} / {{ pageCount }} 页</span>\n <button\n type=\"button\"\n class=\"hp__pagebtn\"\n :disabled=\"page >= pageCount\"\n @click=\"nextPage\"\n >\n <Icon name=\"chevronRight\" :size=\"14\" />\n </button>\n </div>\n </template>\n </template>\n </template>\n </div>\n</template>\n\n<style scoped>\n.hp {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.hp__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.hp__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.hp__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.hp__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Large-file confirm card ------------------------------------------------------------------------- */\n.hp__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.hp__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.hp__confirm-sub {\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--muted);\n}\n.hp__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.hp__btn:hover {\n filter: brightness(1.06);\n}\n\n/* Progress bar (parse + scan) --------------------------------------------------------------------- */\n.hp__progress {\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;\n}\n.hp__progress--inline {\n flex: 0 0 auto;\n padding: 28px 24px;\n}\n.hp__progress-label {\n font-size: 12.5px;\n color: var(--muted);\n}\n.hp__bar {\n width: min(280px, 70%);\n height: 6px;\n border-radius: 999px;\n background: var(--surface-2);\n overflow: hidden;\n}\n.hp__bar-fill {\n height: 100%;\n background: var(--accent);\n transition: width 0.15s ease-out;\n}\n\n/* File meta grid ---------------------------------------------------------------------------------- */\n.hp__meta {\n flex-shrink: 0;\n margin: 0;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto minmax(0, 1fr);\n gap: 4px 12px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n}\n.hp__metakey {\n color: var(--faint);\n font-size: 11px;\n white-space: nowrap;\n}\n.hp__metaval {\n margin: 0;\n min-width: 0;\n color: var(--text);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n overflow-wrap: anywhere;\n}\n\n/* Search ------------------------------------------------------------------------------------------ */\n.hp__search {\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n}\n.hp__search--row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.hp__input {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 10px;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n}\n.hp__input:focus {\n outline: none;\n border-color: var(--accent);\n}\n.hp__ghostbtn {\n flex-shrink: 0;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.hp__ghostbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Histogram table --------------------------------------------------------------------------------- */\n.hp__thead {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 6px 14px;\n border-bottom: 1px solid var(--border);\n background: var(--surface-2);\n}\n.hp__th {\n font-family: inherit;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--faint);\n background: transparent;\n border: none;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n}\n.hp__th:hover {\n color: var(--accent);\n}\n.hp__th--name {\n flex: 1 1 auto;\n justify-content: flex-start;\n}\n.hp__th--num {\n flex-shrink: 0;\n width: 76px;\n justify-content: flex-end;\n}\n.hp__arrow {\n font-size: 8px;\n}\n\n.hp__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 2px 0;\n}\n.hp__row {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 5px 14px;\n background: transparent;\n border: none;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n}\n.hp__row:hover {\n background: var(--surface-2);\n}\n.hp__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 12.5px;\n color: var(--text);\n}\n.hp__num {\n flex-shrink: 0;\n width: 76px;\n text-align: right;\n font-size: 12px;\n color: var(--muted);\n font-variant-numeric: tabular-nums;\n}\n.hp__num--bytes {\n color: var(--faint);\n}\n.hp__chev {\n flex-shrink: 0;\n color: var(--faint);\n}\n.hp__more {\n padding: 10px 16px;\n font-size: 11.5px;\n color: var(--faint);\n}\n\n/* Breadcrumb + class bar -------------------------------------------------------------------------- */\n.hp__crumb {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n}\n.hp__back {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: 2px;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n padding: 4px 9px 4px 6px;\n cursor: pointer;\n}\n.hp__back:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.hp__crumb-name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 12.5px;\n color: var(--text-strong);\n}\n.hp__crumb-cls {\n color: var(--text-strong);\n}\n.hp__crumb-close {\n margin-left: auto;\n}\n.hp__classbar {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 14px;\n font-size: 11.5px;\n color: var(--faint);\n}\n.hp__classcount {\n font-variant-numeric: tabular-nums;\n}\n.hp__tag {\n padding: 1px 7px;\n border-radius: 999px;\n background: var(--surface-2);\n color: var(--muted);\n font-size: 10.5px;\n}\n.hp__hint {\n flex-shrink: 0;\n padding: 8px 14px;\n font-size: 11.5px;\n line-height: 1.6;\n color: var(--muted);\n background: var(--surface-2);\n}\n.hp__link {\n font-family: inherit;\n font-size: 11.5px;\n color: var(--accent);\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n text-decoration: underline;\n}\n.hp__jumpmsg {\n flex-shrink: 0;\n padding: 7px 14px;\n font-size: 11.5px;\n color: var(--danger);\n background: var(--surface-2);\n}\n\n/* Object list ------------------------------------------------------------------------------------- */\n.hp__obj {\n border-bottom: 1px solid var(--border);\n}\n.hp__objhead {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 6px 14px;\n background: transparent;\n border: none;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n}\n.hp__objhead:hover {\n background: var(--surface-2);\n}\n.hp__objchev {\n flex-shrink: 0;\n color: var(--faint);\n}\n.hp__objid {\n flex-shrink: 0;\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text);\n}\n.hp__objsize {\n flex-shrink: 0;\n font-size: 10.5px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n}\n.hp__objprev {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--muted);\n}\n.hp__detail {\n background: var(--surface-2);\n}\n.hp__fields {\n margin: 0;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 3px 14px;\n padding: 4px 16px 10px 34px;\n background: var(--surface-2);\n}\n.hp__fields--detail {\n padding: 10px 16px;\n}\n.hp__nofields {\n grid-column: 1 / -1;\n margin: 0;\n font-size: 11.5px;\n color: var(--faint);\n}\n.hp__fkey {\n color: var(--faint);\n font-family: var(--font-mono);\n font-size: 11.5px;\n white-space: nowrap;\n}\n.hp__fval {\n margin: 0;\n min-width: 0;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--text);\n overflow-wrap: anywhere;\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: 6px;\n}\n.hp__fval--null {\n color: var(--faint);\n font-style: italic;\n}\n.hp__str {\n color: var(--text-strong);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* Reference links --------------------------------------------------------------------------------- */\n.hp__reflink {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--accent);\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.hp__reflink:hover {\n filter: brightness(1.15);\n}\n.hp__reflink--sm {\n font-size: 10.5px;\n opacity: 0.75;\n}\n.hp__reflink--el {\n display: inline-block;\n text-decoration: none;\n padding: 2px 6px;\n margin: 2px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: var(--surface);\n}\n.hp__reflink--el:hover {\n border-color: var(--accent);\n}\n.hp__annot {\n color: var(--faint);\n font-size: 10.5px;\n font-style: italic;\n}\n\n/* Array content ----------------------------------------------------------------------------------- */\n.hp__arr {\n padding: 8px 16px 12px 34px;\n}\n.hp__fields--detail + .hp__arr,\n.hp__crumb ~ .hp__list .hp__arr {\n padding-left: 16px;\n}\n.hp__arrmeta {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--faint);\n margin-bottom: 6px;\n}\n.hp__arrtext {\n margin: 0 0 6px;\n max-height: 260px;\n overflow: auto;\n padding: 8px 10px;\n border-radius: 6px;\n background: var(--surface);\n border: 1px solid var(--border);\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--text);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.hp__arrhex {\n margin-bottom: 6px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--muted);\n overflow-wrap: anywhere;\n line-height: 1.6;\n}\n.hp__arrels {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 10px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text);\n}\n.hp__arrels--obj {\n gap: 2px;\n}\n.hp__arrel {\n color: var(--text);\n}\n.hp__eli {\n color: var(--faint);\n}\n\n/* Pager ------------------------------------------------------------------------------------------- */\n.hp__pager {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 14px;\n padding: 8px 14px;\n border-top: 1px solid var(--border);\n}\n.hp__pagebtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 26px;\n color: var(--muted);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n cursor: pointer;\n}\n.hp__pagebtn:hover:not(:disabled) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.hp__pagebtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.hp__pageinfo {\n font-size: 12px;\n color: var(--muted);\n font-variant-numeric: tabular-nums;\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./ImageLightbox.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-edff5d07"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=ImageLightbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageLightbox.js","names":[],"sources":["../../src/components/ImageLightbox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useLightbox } from '@/composables/useLightbox'\nimport { fileExt } from '@/lib/fileType'\nimport Icon from '@/components/icons/Icon.vue'\nimport ZoomImage from '@/components/ZoomImage.vue'\n\n/**\n * Full-screen media lightbox (the \"弹框预览\"). A single instance is mounted in App.vue and driven by the\n * `useLightbox` singleton — any thumbnail / file chip opens it. Teleported to <body> so it overlays everything\n * regardless of the message tree's stacking contexts. Close via the X button, the empty backdrop, or Esc.\n *\n * Images render in the shared {@link ZoomImage} stage (wheel-zoom, drag-pan, dbl-click fit↔2×, toolbar, and\n * long-image fill-width). +/-/0 keys drive it via the exposed methods; a no-drag click on empty area closes.\n * Videos are FETCHED and played from a blob URL: the download endpoint serves `octet-stream` + `nosniff`, so a\n * direct `<video src>` can't determine a codec — but a blob tagged with the right MIME does (no backend change;\n * the same trick ZipBrowser uses).\n */\nconst { current, close } = useLightbox()\n\n// Extension → a concrete video MIME so the blob decodes (the upstream bytes arrive as octet-stream).\nconst VIDEO_MIME: Record<string, string> = {\n mp4: 'video/mp4',\n m4v: 'video/mp4',\n webm: 'video/webm',\n ogv: 'video/ogg',\n mov: 'video/quicktime',\n mkv: 'video/x-matroska',\n avi: 'video/x-msvideo',\n}\n\nconst isVideo = computed(() => current.value?.kind === 'video')\nconst zoomImg = ref<InstanceType<typeof ZoomImage> | null>(null)\n\nconst videoUrl = ref('')\nconst videoLoading = ref(false)\nconst videoError = ref('')\n\nfunction onKey(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n close()\n } else if (!isVideo.value) {\n if (e.key === '+' || e.key === '=') zoomImg.value?.zoomIn()\n else if (e.key === '-' || e.key === '_') zoomImg.value?.zoomOut()\n else if (e.key === '0') zoomImg.value?.fit()\n }\n}\n\nfunction revokeVideo(): void {\n if (videoUrl.value) {\n URL.revokeObjectURL(videoUrl.value)\n videoUrl.value = ''\n }\n}\n\n// Fetch a video's bytes and wrap them in a typed blob the <video> element can actually decode.\nasync function loadVideo(media: NonNullable<typeof current.value>): Promise<void> {\n videoLoading.value = true\n videoError.value = ''\n try {\n const res = await fetch(media.src)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const raw = await res.blob()\n // Don't trust raw.type — the endpoint always serves octet-stream; derive from the filename extension.\n const type = VIDEO_MIME[fileExt(media.alt || '')] || 'video/mp4'\n const url = URL.createObjectURL(new Blob([raw], { type }))\n // Guard against the lightbox having closed / switched media while the fetch was in flight.\n if (current.value === media) videoUrl.value = url\n else URL.revokeObjectURL(url)\n } catch (e) {\n if (current.value === media) videoError.value = e instanceof Error ? e.message : String(e)\n } finally {\n if (current.value === media) videoLoading.value = false\n }\n}\n\n// Add the Esc listener and lock body scroll only while open; reset video state on every change and (re)load\n// when the new media is a video. Guarded for SSR.\nwatch(current, (media) => {\n if (typeof window === 'undefined') return\n if (media) {\n window.addEventListener('keydown', onKey)\n document.body.style.overflow = 'hidden'\n } else {\n window.removeEventListener('keydown', onKey)\n document.body.style.overflow = ''\n }\n revokeVideo()\n videoError.value = ''\n videoLoading.value = false\n if (media?.kind === 'video') void loadVideo(media)\n})\n\nonBeforeUnmount(() => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', onKey)\n document.body.style.overflow = ''\n }\n revokeVideo()\n})\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div v-if=\"current\" class=\"lightbox\">\n <button\n type=\"button\"\n class=\"lightbox__close\"\n title=\"关闭 (Esc)\"\n aria-label=\"关闭\"\n @click=\"close\"\n >\n <Icon name=\"close\" :size=\"22\" :stroke-width=\"2.2\" />\n </button>\n\n <template v-if=\"isVideo\">\n <p v-if=\"videoLoading\" class=\"lightbox__msg\">加载视频中…</p>\n <p v-else-if=\"videoError\" class=\"lightbox__msg\">视频加载失败:{{ videoError }}</p>\n <video\n v-else-if=\"videoUrl\"\n class=\"lightbox__video\"\n :src=\"videoUrl\"\n controls\n autoplay\n playsinline\n />\n </template>\n\n <ZoomImage\n v-else\n ref=\"zoomImg\"\n class=\"lightbox__zoom\"\n :src=\"current.src\"\n :alt=\"current.alt || ''\"\n :long=\"current.long\"\n @empty-click=\"close\"\n />\n </div>\n </Teleport>\n</template>\n\n<style scoped>\n.lightbox {\n position: fixed;\n inset: 0;\n z-index: 2000;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.82);\n animation: lightbox-in 0.12s ease-out;\n}\n@keyframes lightbox-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n/* The zoom/pan stage fills the overlay; its transparent background lets the dark backdrop show through. */\n.lightbox__zoom {\n position: absolute;\n inset: 0;\n}\n.lightbox__video {\n max-width: 94vw;\n max-height: 94vh;\n border-radius: 6px;\n background: #000;\n box-shadow: 0 16px 56px rgba(0, 0, 0, 0.55);\n}\n.lightbox__msg {\n color: #fff;\n font-size: 14px;\n}\n.lightbox__close {\n position: fixed;\n top: 16px;\n right: 16px;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.12);\n color: #fff;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.lightbox__close:hover {\n background: rgba(255, 255, 255, 0.24);\n}\n</style>\n"],"mappings":""}
@@ -84,3 +84,5 @@ var y = {
84
84
  });
85
85
  //#endregion
86
86
  export { C as default };
87
+
88
+ //# sourceMappingURL=ImageLightbox.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageLightbox.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ImageLightbox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useLightbox } from '@/composables/useLightbox'\nimport { fileExt } from '@/lib/fileType'\nimport Icon from '@/components/icons/Icon.vue'\nimport ZoomImage from '@/components/ZoomImage.vue'\n\n/**\n * Full-screen media lightbox (the \"弹框预览\"). A single instance is mounted in App.vue and driven by the\n * `useLightbox` singleton — any thumbnail / file chip opens it. Teleported to <body> so it overlays everything\n * regardless of the message tree's stacking contexts. Close via the X button, the empty backdrop, or Esc.\n *\n * Images render in the shared {@link ZoomImage} stage (wheel-zoom, drag-pan, dbl-click fit↔2×, toolbar, and\n * long-image fill-width). +/-/0 keys drive it via the exposed methods; a no-drag click on empty area closes.\n * Videos are FETCHED and played from a blob URL: the download endpoint serves `octet-stream` + `nosniff`, so a\n * direct `<video src>` can't determine a codec — but a blob tagged with the right MIME does (no backend change;\n * the same trick ZipBrowser uses).\n */\nconst { current, close } = useLightbox()\n\n// Extension → a concrete video MIME so the blob decodes (the upstream bytes arrive as octet-stream).\nconst VIDEO_MIME: Record<string, string> = {\n mp4: 'video/mp4',\n m4v: 'video/mp4',\n webm: 'video/webm',\n ogv: 'video/ogg',\n mov: 'video/quicktime',\n mkv: 'video/x-matroska',\n avi: 'video/x-msvideo',\n}\n\nconst isVideo = computed(() => current.value?.kind === 'video')\nconst zoomImg = ref<InstanceType<typeof ZoomImage> | null>(null)\n\nconst videoUrl = ref('')\nconst videoLoading = ref(false)\nconst videoError = ref('')\n\nfunction onKey(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n close()\n } else if (!isVideo.value) {\n if (e.key === '+' || e.key === '=') zoomImg.value?.zoomIn()\n else if (e.key === '-' || e.key === '_') zoomImg.value?.zoomOut()\n else if (e.key === '0') zoomImg.value?.fit()\n }\n}\n\nfunction revokeVideo(): void {\n if (videoUrl.value) {\n URL.revokeObjectURL(videoUrl.value)\n videoUrl.value = ''\n }\n}\n\n// Fetch a video's bytes and wrap them in a typed blob the <video> element can actually decode.\nasync function loadVideo(media: NonNullable<typeof current.value>): Promise<void> {\n videoLoading.value = true\n videoError.value = ''\n try {\n const res = await fetch(media.src)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const raw = await res.blob()\n // Don't trust raw.type — the endpoint always serves octet-stream; derive from the filename extension.\n const type = VIDEO_MIME[fileExt(media.alt || '')] || 'video/mp4'\n const url = URL.createObjectURL(new Blob([raw], { type }))\n // Guard against the lightbox having closed / switched media while the fetch was in flight.\n if (current.value === media) videoUrl.value = url\n else URL.revokeObjectURL(url)\n } catch (e) {\n if (current.value === media) videoError.value = e instanceof Error ? e.message : String(e)\n } finally {\n if (current.value === media) videoLoading.value = false\n }\n}\n\n// Add the Esc listener and lock body scroll only while open; reset video state on every change and (re)load\n// when the new media is a video. Guarded for SSR.\nwatch(current, (media) => {\n if (typeof window === 'undefined') return\n if (media) {\n window.addEventListener('keydown', onKey)\n document.body.style.overflow = 'hidden'\n } else {\n window.removeEventListener('keydown', onKey)\n document.body.style.overflow = ''\n }\n revokeVideo()\n videoError.value = ''\n videoLoading.value = false\n if (media?.kind === 'video') void loadVideo(media)\n})\n\nonBeforeUnmount(() => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', onKey)\n document.body.style.overflow = ''\n }\n revokeVideo()\n})\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div v-if=\"current\" class=\"lightbox\">\n <button\n type=\"button\"\n class=\"lightbox__close\"\n title=\"关闭 (Esc)\"\n aria-label=\"关闭\"\n @click=\"close\"\n >\n <Icon name=\"close\" :size=\"22\" :stroke-width=\"2.2\" />\n </button>\n\n <template v-if=\"isVideo\">\n <p v-if=\"videoLoading\" class=\"lightbox__msg\">加载视频中…</p>\n <p v-else-if=\"videoError\" class=\"lightbox__msg\">视频加载失败:{{ videoError }}</p>\n <video\n v-else-if=\"videoUrl\"\n class=\"lightbox__video\"\n :src=\"videoUrl\"\n controls\n autoplay\n playsinline\n />\n </template>\n\n <ZoomImage\n v-else\n ref=\"zoomImg\"\n class=\"lightbox__zoom\"\n :src=\"current.src\"\n :alt=\"current.alt || ''\"\n :long=\"current.long\"\n @empty-click=\"close\"\n />\n </div>\n </Teleport>\n</template>\n\n<style scoped>\n.lightbox {\n position: fixed;\n inset: 0;\n z-index: 2000;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.82);\n animation: lightbox-in 0.12s ease-out;\n}\n@keyframes lightbox-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n/* The zoom/pan stage fills the overlay; its transparent background lets the dark backdrop show through. */\n.lightbox__zoom {\n position: absolute;\n inset: 0;\n}\n.lightbox__video {\n max-width: 94vw;\n max-height: 94vh;\n border-radius: 6px;\n background: #000;\n box-shadow: 0 16px 56px rgba(0, 0, 0, 0.55);\n}\n.lightbox__msg {\n color: #fff;\n font-size: 14px;\n}\n.lightbox__close {\n position: fixed;\n top: 16px;\n right: 16px;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.12);\n color: #fff;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.lightbox__close:hover {\n background: rgba(255, 255, 255, 0.24);\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=InlineText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InlineText.js","names":[],"sources":["../../src/components/InlineText.ts"],"sourcesContent":["import { defineComponent, h } from 'vue'\nimport { inline } from '@/lib/markdown/inline'\n\n/**\n * Renders inline markdown (code/bold/italic/links/strikethrough) as a fragment,\n * for embedding inside SFC templates (table cells, recipe steps, …). The block\n * renderer in lib/markdown/blocks.ts calls `inline()` directly instead.\n */\nexport default defineComponent({\n name: 'InlineText',\n props: {\n text: { type: String, default: '' },\n kp: { type: String, default: 'i' },\n },\n setup(props) {\n return () => inline(props.text, props.kp) as ReturnType<typeof h>[]\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;EAAI;CACnC;CACA,MAAM,GAAO;EACX,aAAa,EAAO,EAAM,MAAM,EAAM,EAAE;CAC1C;AACF,CAAC"}
@@ -5,3 +5,5 @@ import t from "./JksViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-4e87fe75"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=JksViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JksViewer.js","names":[],"sources":["../../src/components/JksViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'\nimport {\n decryptPrivateKey,\n JCEKS_MAGIC,\n JKS_MAGIC,\n JksError,\n readJks,\n verifyStorePassword,\n type JksEntry,\n type JksStore,\n type KeyDecryptResult,\n} from '@/lib/jks'\nimport { looksLikePkcs12, readPkcs12, type Pkcs12Result } from '@/lib/pkcs12'\nimport { parseCertificate, sha256Hex, type CertSummary } from '@/lib/x509'\nimport { formatBytes } from '@/lib/format'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * Side-viewer for keystores. Fetches the BYTES itself (binary-safe) and sniffs the format:\n * - JKS / JCEKS (magic 0xFEEDFEED / 0xCECECECE): the Sun binary format (see lib/jks.ts). Certificates are\n * plaintext, so the whole inventory shows immediately; a password verifies the store MAC and decrypts JKS\n * private keys.\n * - PKCS#12 / PFX (`.p12`/`.pfx`, and the very common `.jks`-that-is-actually-PKCS#12 — keytool defaults to\n * PKCS12 since JDK 9): everything is encrypted (see lib/pkcs12.ts), so nothing shows until the password\n * unlocks it; then certs + private keys appear and the integrity MAC is verified.\n *\n * Both render the same cards: per entry the alias / kind / (JKS) creation date, the private-key algorithm once\n * decrypted, and each certificate's X.509 fields (subject/issuer, validity + expiry badge, key + signature\n * algorithm, SANs, SHA-256 fingerprint — reusing lib/x509.ts). All parsing/decryption is in-browser; the\n * password and key material never leave the page.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Format = 'jks' | 'jceks' | 'pkcs12'\n\ninterface CertView {\n summary: CertSummary\n id: string\n}\ninterface EntryView {\n entry: JksEntry\n index: number\n certs: CertView[]\n}\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst format = ref<Format | null>(null)\nconst rawBuf = shallowRef<ArrayBuffer | null>(null) // kept for PKCS#12 (decrypted on password submit)\nconst jksStore = shallowRef<JksStore | null>(null)\nconst entryViews = shallowRef<EntryView[]>([])\nconst fingerprints = reactive<Record<string, string>>({})\n\n// Password state: one field drives store-MAC verification and per-key decryption (keytool defaults the store\n// password equal to each key password — the common case).\nconst password = ref('')\nconst pwTried = ref(false)\nconst integrity = ref<'unknown' | 'ok' | 'bad'>('unknown')\nconst unlocked = ref(false) // PKCS#12: bags decrypted\nconst keyResults = reactive<Record<number, KeyDecryptResult>>({})\nconst p12meta = shallowRef<Pkcs12Result | null>(null)\nconst p12error = ref('') // unsupported / malformed (distinct from wrong-password)\n\nconst SIZE_LIMIT = 8 * 1024 * 1024\n\nconst isPkcs12 = computed(() => format.value === 'pkcs12')\nconst storeTypeLabel = computed(() =>\n format.value === 'pkcs12' ? 'PKCS#12' : format.value === 'jceks' ? 'JCEKS' : 'JKS',\n)\nconst hasPrivateKeys = computed(() => entryViews.value.some((e) => e.entry.type === 'privateKey'))\nconst storeMeta = computed(() => {\n const s = jksStore.value\n if (format.value === 'pkcs12') {\n const m = p12meta.value\n if (m) return [...m.encryptionAlgos, m.macAlgorithm].filter(Boolean).join(' · ')\n return '证书与私钥均加密,输入密码解密'\n }\n return s ? `版本 ${s.version} · ${s.entries.length} 个条目` : ''\n})\n\nfunction fingerprintAll(): void {\n for (const ev of entryViews.value) {\n for (const cv of ev.certs) {\n if (cv.summary.ok) {\n void sha256Hex(cv.summary.der)\n .then((hex) => {\n fingerprints[cv.id] = hex\n })\n .catch(() => {})\n }\n }\n }\n}\n\nfunction buildJksEntryViews(store: JksStore): void {\n entryViews.value = store.entries.map((entry, index) => ({\n entry,\n index,\n certs: entry.certs.map((c, ci) => ({ summary: parseCertificate(c.der), id: `${index}.${ci}` })),\n }))\n}\n\nfunction processBuffer(buf: ArrayBuffer): void {\n const bytes = new Uint8Array(buf)\n const magic =\n bytes.length >= 4 ? ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0 : 0\n if (magic === JKS_MAGIC || magic === JCEKS_MAGIC) {\n const store = readJks(buf)\n format.value = store.storeType\n jksStore.value = store\n buildJksEntryViews(store)\n fingerprintAll()\n } else if (looksLikePkcs12(bytes)) {\n format.value = 'pkcs12'\n rawBuf.value = buf // wait for the password before decrypting\n } else {\n throw new JksError(\n `文件头不匹配(magic=0x${magic.toString(16).padStart(8, '0')})—— 不是 JKS/JCEKS/PKCS#12 密钥库`,\n )\n }\n status.value = 'ready'\n}\n\nfunction resetState(): void {\n errorMsg.value = ''\n format.value = null\n rawBuf.value = null\n jksStore.value = null\n entryViews.value = []\n password.value = ''\n pwTried.value = false\n integrity.value = 'unknown'\n unlocked.value = false\n p12meta.value = null\n p12error.value = ''\n for (const k of Object.keys(fingerprints)) delete fingerprints[k]\n for (const k of Object.keys(keyResults)) delete keyResults[Number(k)]\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n resetState()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n processBuffer(await res.arrayBuffer())\n } catch (e) {\n errorMsg.value = e instanceof JksError || 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\nfunction submitJksPassword(store: JksStore): void {\n integrity.value = verifyStorePassword(store, password.value) ? 'ok' : 'bad'\n for (const ev of entryViews.value) {\n if (ev.entry.type === 'privateKey' && ev.entry.encryptedKey) {\n keyResults[ev.index] = decryptPrivateKey(ev.entry.encryptedKey, password.value)\n }\n }\n}\n\nfunction buildPkcs12EntryViews(result: Pkcs12Result): void {\n const views: EntryView[] = []\n const usedCerts = new Set<number>()\n let idx = 0\n for (const k of result.keys) {\n const chain = result.certs.filter((c, ci) => {\n const match = !!k.localKeyId && c.localKeyId === k.localKeyId\n if (match) usedCerts.add(ci)\n return match\n })\n const entry: JksEntry = {\n type: 'privateKey',\n alias: k.friendlyName || k.localKeyId || `私钥 ${idx + 1}`,\n createdMs: 0,\n certs: chain.map((c) => ({ type: 'X.509', der: c.der })),\n }\n views.push({\n entry,\n index: idx,\n certs: entry.certs.map((c, ci) => ({ summary: parseCertificate(c.der), id: `${idx}.${ci}` })),\n })\n keyResults[idx] = {\n ok: true,\n key: { pkcs8: k.pkcs8, algorithm: k.algorithm, bits: k.bits, curve: k.curve },\n }\n idx++\n }\n result.certs.forEach((c, ci) => {\n if (usedCerts.has(ci)) return\n const entry: JksEntry = {\n type: 'trustedCert',\n alias: c.friendlyName || `证书 ${idx + 1}`,\n createdMs: 0,\n certs: [{ type: 'X.509', der: c.der }],\n }\n views.push({ entry, index: idx, certs: [{ summary: parseCertificate(c.der), id: `${idx}.0` }] })\n idx++\n })\n entryViews.value = views\n}\n\nfunction submitPkcs12Password(buf: ArrayBuffer): void {\n const r = readPkcs12(buf, password.value)\n if (!r.ok) {\n unlocked.value = false\n entryViews.value = []\n for (const k of Object.keys(keyResults)) delete keyResults[Number(k)]\n if (r.reason === 'wrong-password') {\n integrity.value = 'bad'\n p12error.value = ''\n } else {\n integrity.value = 'unknown'\n p12error.value =\n (r.reason === 'unsupported' ? '暂不支持的加密方式:' : '解析失败:') + (r.detail ?? '')\n }\n return\n }\n p12error.value = ''\n p12meta.value = r.result\n buildPkcs12EntryViews(r.result)\n integrity.value = r.result.macVerified === true ? 'ok' : 'unknown'\n unlocked.value = true\n fingerprintAll()\n}\n\n// Apply the password to whichever format this keystore is.\nfunction submitPassword(): void {\n if (!password.value) return\n pwTried.value = true\n if (format.value === 'pkcs12') {\n if (rawBuf.value) submitPkcs12Password(rawBuf.value)\n } else if (jksStore.value) {\n submitJksPassword(jksStore.value)\n }\n}\n\n// ── friendly-field formatting (shared with the cert cards) ─────────────────────────────────────────\nfunction formatDn(rdns: CertSummary['subject']): string {\n return rdns.map((r) => `${r.name}=${r.value}`).join(', ')\n}\n\nconst ENTRY_LABEL: Record<JksEntry['type'], string> = {\n privateKey: '私钥条目',\n trustedCert: '受信任证书',\n secretKey: '密钥条目',\n}\nconst ENTRY_CLASS: Record<JksEntry['type'], string> = {\n privateKey: 'jks__badge--key',\n trustedCert: 'jks__badge--cert',\n secretKey: 'jks__badge--secret',\n}\n\nfunction formatDate(ms: number): string {\n if (!ms) return ''\n try {\n return new Date(ms).toLocaleString('zh-CN', { hour12: false })\n } catch {\n return ''\n }\n}\n\nfunction publicKeyText(s: CertSummary): string {\n const parts: string[] = []\n if (s.pubKeyAlg) parts.push(s.pubKeyAlg)\n if (s.pubKeyBits) parts.push(`${s.pubKeyBits} 位`)\n if (s.pubKeyCurve && s.pubKeyCurve !== s.pubKeyAlg) parts.push(s.pubKeyCurve)\n return parts.join(' · ')\n}\n\n// Validity badge: future / expired / soon (≤30 days) / valid, from notBefore/notAfter vs now.\nfunction expiry(s: CertSummary): { text: string; cls: string } | null {\n if (s.notAfterMs == null) return null\n const now = Date.now()\n if (s.notBeforeMs != null && now < s.notBeforeMs)\n return { text: '尚未生效', cls: 'jks__cbadge--warn' }\n if (now > s.notAfterMs) return { text: '已过期', cls: 'jks__cbadge--bad' }\n const days = Math.floor((s.notAfterMs - now) / 86_400_000)\n if (days <= 30) return { text: `${days} 天后过期`, cls: 'jks__cbadge--warn' }\n return { text: `有效(剩 ${days} 天)`, cls: 'jks__cbadge--ok' }\n}\n\n// The private-key row's decrypted/locked/error state for one entry.\ntype KeyState =\n | { kind: 'locked' }\n | { kind: 'ok'; text: string }\n | { kind: 'wrong' }\n | { kind: 'unsupported'; detail?: string }\n | { kind: 'malformed' }\n\nfunction keyState(index: number): KeyState {\n const r = keyResults[index]\n if (!r) return { kind: 'locked' }\n if (r.ok) {\n const parts = [r.key.algorithm]\n if (r.key.bits) parts.push(`${r.key.bits} 位`)\n if (r.key.curve && r.key.curve !== r.key.algorithm) parts.push(r.key.curve)\n return { kind: 'ok', text: parts.join(' · ') }\n }\n if (r.reason === 'wrong-password') return { kind: 'wrong' }\n if (r.reason === 'unsupported') return { kind: 'unsupported', detail: r.detail }\n return { kind: 'malformed' }\n}\n\nconst pwPlaceholder = computed(() => {\n if (isPkcs12.value)\n return unlocked.value ? '已解锁(可重新输入)' : '输入 PKCS#12 密码解密证书与私钥'\n return hasPrivateKeys.value ? '输入密码:解密私钥并校验完整性' : '输入库密码校验完整性(可选)'\n})\n\n// Integrity/unlock status line after a password attempt (format-aware).\nconst integrityText = computed(() => {\n if (integrity.value === 'bad') {\n return isPkcs12.value ? '密码错误 —— 无法解密' : '完整性校验未通过 —— 密码错误或文件已被修改'\n }\n if (integrity.value === 'ok') {\n if (isPkcs12.value) return `密码正确,完整性校验通过(${p12meta.value?.macAlgorithm ?? 'MAC'})`\n return '完整性校验通过 —— 密码正确,keystore 未被篡改'\n }\n // PKCS#12 unlocked but MAC absent/unsupported: still a successful decrypt.\n if (isPkcs12.value && unlocked.value) return '已解密(该文件无 MAC 或摘要不支持,密码经解密验证)'\n return ''\n})\nconst integrityOk = computed(() => integrity.value === 'ok' || (isPkcs12.value && unlocked.value))\n\nwatch(() => [props.url, props.name], load, { immediate: true })\nonBeforeUnmount(() => {\n // nothing to revoke; bytes are not retained as object URLs\n})\n</script>\n\n<template>\n <div class=\"jks\">\n <p v-if=\"status === 'loading'\" class=\"jks__msg\">正在解析 keystore…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"jks__msg jks__gate\">\n <p>该文件较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"jks__btn\" @click=\"doLoad\">解析并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"jks__msg jks__msg--error\">\n <p>无法解析:{{ errorMsg }}</p>\n <button type=\"button\" class=\"jks__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <div v-else-if=\"format\" class=\"jks__scroll\">\n <!-- store header + password -->\n <section class=\"jks__store\">\n <header class=\"jks__storehead\">\n <span class=\"jks__badge jks__badge--type\">{{ storeTypeLabel }}</span>\n <span class=\"jks__storetitle\">{{ isPkcs12 ? 'PKCS#12 密钥库' : 'Java KeyStore' }}</span>\n <span class=\"jks__storemeta\">{{ storeMeta }}</span>\n </header>\n\n <div class=\"jks__pw\" :class=\"{ 'jks__pw--ok': integrityOk }\">\n <Icon name=\"key\" :size=\"14\" />\n <input\n v-model=\"password\"\n type=\"password\"\n class=\"jks__pwinput\"\n :placeholder=\"pwPlaceholder\"\n autocomplete=\"off\"\n @keyup.enter=\"submitPassword\"\n />\n <button type=\"button\" class=\"jks__pwbtn\" @click=\"submitPassword\">\n {{ isPkcs12 && unlocked ? '重新解锁' : '解锁' }}\n </button>\n </div>\n\n <p\n v-if=\"pwTried && integrityText\"\n class=\"jks__integrity\"\n :class=\"`jks__integrity--${integrity}`\"\n >\n <Icon :name=\"integrityOk ? 'check' : 'close'\" :size=\"13\" />\n <span>{{ integrityText }}</span>\n </p>\n\n <p class=\"jks__hint\">全部解析与解密均在浏览器本地完成,密码与密钥不会上传。</p>\n <p v-if=\"jksStore?.truncatedNote\" class=\"jks__warn\">{{ jksStore.truncatedNote }}</p>\n <p v-if=\"p12error\" class=\"jks__warn\">{{ p12error }}</p>\n <p v-for=\"(w, i) in p12meta?.warnings ?? []\" :key=\"i\" class=\"jks__warn\">{{ w }}</p>\n </section>\n\n <!-- PKCS#12 pre-unlock: nothing is visible until decrypted -->\n <div v-if=\"isPkcs12 && !unlocked\" class=\"jks__locked\">\n <Icon name=\"key\" :size=\"26\" class=\"jks__lockicon\" />\n <p>PKCS#12 密钥库的证书与私钥均已加密,输入上方密码后在此显示内容。</p>\n </div>\n\n <!-- entries -->\n <section v-for=\"ev in entryViews\" :key=\"ev.index\" class=\"jks__entry\">\n <header class=\"jks__entryhead\">\n <span class=\"jks__badge\" :class=\"ENTRY_CLASS[ev.entry.type]\">{{\n ENTRY_LABEL[ev.entry.type]\n }}</span>\n <span class=\"jks__alias\" :title=\"ev.entry.alias\">{{ ev.entry.alias }}</span>\n <span v-if=\"ev.entry.createdMs\" class=\"jks__date\">{{\n formatDate(ev.entry.createdMs)\n }}</span>\n </header>\n\n <!-- private key material -->\n <div v-if=\"ev.entry.type === 'privateKey'\" class=\"jks__key\">\n <template v-if=\"keyState(ev.index).kind === 'locked'\">\n <Icon name=\"key\" :size=\"13\" class=\"jks__keyicon\" />\n <span class=\"jks__keytext\">私钥已加密 —— 输入密码后在此显示密钥算法</span>\n </template>\n <template v-else-if=\"keyState(ev.index).kind === 'ok'\">\n <Icon name=\"check\" :size=\"13\" class=\"jks__keyicon jks__keyicon--ok\" />\n <span class=\"jks__keytext jks__keytext--ok\"\n >私钥已解密 · {{ (keyState(ev.index) as { text: string }).text }}</span\n >\n </template>\n <template v-else-if=\"keyState(ev.index).kind === 'wrong'\">\n <Icon name=\"close\" :size=\"13\" class=\"jks__keyicon jks__keyicon--bad\" />\n <span class=\"jks__keytext jks__keytext--bad\">密码错误 —— 无法解密该私钥</span>\n </template>\n <template v-else-if=\"keyState(ev.index).kind === 'unsupported'\">\n <Icon name=\"key\" :size=\"13\" class=\"jks__keyicon\" />\n <span class=\"jks__keytext\"\n >该私钥加密方式暂不支持浏览器内解密({{\n (keyState(ev.index) as { detail?: string }).detail\n }})—— 证书链仍可查看</span\n >\n </template>\n <template v-else>\n <Icon name=\"close\" :size=\"13\" class=\"jks__keyicon jks__keyicon--bad\" />\n <span class=\"jks__keytext jks__keytext--bad\">私钥数据无法解析</span>\n </template>\n </div>\n\n <p v-else-if=\"ev.entry.type === 'secretKey'\" class=\"jks__note\">\n JCEKS 对称密钥条目(SecretKey)—— 以 Java 序列化对象存储,暂不解析。\n </p>\n\n <!-- certificate chain -->\n <div v-if=\"ev.certs.length\" class=\"jks__chain\">\n <div v-if=\"ev.entry.type === 'privateKey'\" class=\"jks__chainlabel\">\n 证书链({{ ev.certs.length }})\n </div>\n <article v-for=\"cv in ev.certs\" :key=\"cv.id\" class=\"jks__cert\">\n <div class=\"jks__certhead\">\n <span class=\"jks__certcn\">{{ cv.summary.subjectCN || cv.summary.label }}</span>\n <span v-if=\"expiry(cv.summary)\" class=\"jks__cbadge\" :class=\"expiry(cv.summary)?.cls\">\n {{ expiry(cv.summary)?.text }}\n </span>\n </div>\n <p v-if=\"cv.summary.note && !cv.summary.ok\" class=\"jks__note\">{{ cv.summary.note }}</p>\n <dl class=\"jks__dl\">\n <template v-if=\"cv.summary.subject.length\">\n <dt>主题</dt>\n <dd>{{ formatDn(cv.summary.subject) }}</dd>\n </template>\n <template v-if=\"cv.summary.issuer.length\">\n <dt>颁发者</dt>\n <dd>{{ formatDn(cv.summary.issuer) }}</dd>\n </template>\n <template v-if=\"cv.summary.notBefore || cv.summary.notAfter\">\n <dt>有效期</dt>\n <dd>{{ cv.summary.notBefore ?? '?' }} → {{ cv.summary.notAfter ?? '?' }}</dd>\n </template>\n <template v-if=\"publicKeyText(cv.summary)\">\n <dt>公钥</dt>\n <dd>{{ publicKeyText(cv.summary) }}</dd>\n </template>\n <template v-if=\"cv.summary.sigAlg\">\n <dt>签名算法</dt>\n <dd>{{ cv.summary.sigAlg }}</dd>\n </template>\n <template v-if=\"cv.summary.serialHex\">\n <dt>序列号</dt>\n <dd class=\"jks__mono\">{{ cv.summary.serialHex }}</dd>\n </template>\n <template v-if=\"cv.summary.sans.length\">\n <dt>备用名 (SAN)</dt>\n <dd>{{ cv.summary.sans.join(',') }}</dd>\n </template>\n <template v-if=\"fingerprints[cv.id]\">\n <dt>SHA-256</dt>\n <dd class=\"jks__mono jks__fp\">{{ fingerprints[cv.id] }}</dd>\n </template>\n </dl>\n </article>\n </div>\n </section>\n </div>\n </div>\n</template>\n\n<style scoped>\n.jks {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.jks__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.jks__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.jks__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.jks__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.jks__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.jks__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 14px 14px 60px;\n}\n\n/* Store header + password ---------------------------------------------------------------------- */\n.jks__store {\n margin-bottom: 16px;\n border: 1px solid var(--border);\n border-radius: 10px;\n background: var(--surface);\n overflow: hidden;\n}\n.jks__storehead {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n background: var(--surface-2);\n border-bottom: 1px solid var(--border);\n}\n.jks__storetitle {\n flex-shrink: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.jks__storemeta {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n font-size: 11.5px;\n color: var(--faint);\n}\n\n.jks__pw {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n color: var(--muted);\n}\n.jks__pw--ok {\n color: var(--accent);\n}\n.jks__pwinput {\n flex: 1 1 auto;\n min-width: 0;\n height: 28px;\n padding: 0 9px;\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: 7px;\n}\n.jks__pwinput:focus {\n outline: none;\n border-color: var(--accent);\n}\n.jks__pwbtn {\n flex-shrink: 0;\n height: 28px;\n padding: 0 14px;\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: 7px;\n cursor: pointer;\n}\n.jks__pwbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.jks__integrity {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n padding: 0 12px 8px;\n font-size: 12px;\n}\n.jks__integrity--ok {\n color: #15803d;\n}\n.jks__integrity--bad {\n color: #b91c1c;\n}\n.jks__integrity--unknown {\n color: var(--accent);\n}\n.jks__hint {\n margin: 0;\n padding: 0 12px 10px;\n font-size: 11.5px;\n line-height: 1.5;\n color: var(--faint);\n}\n.jks__warn {\n margin: 0;\n padding: 9px 12px;\n font-size: 12px;\n line-height: 1.5;\n color: #b45309;\n background: color-mix(in srgb, #f59e0b 12%, transparent);\n border-top: 1px solid var(--border);\n}\n\n.jks__locked {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n padding: 40px 24px;\n text-align: center;\n color: var(--muted);\n font-size: 12.5px;\n}\n.jks__lockicon {\n color: var(--faint);\n}\n\n/* Badges ------------------------------------------------------------------------------------- */\n.jks__badge {\n flex-shrink: 0;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n border-radius: 5px;\n padding: 2px 7px;\n}\n.jks__badge--type {\n color: var(--accent);\n background: color-mix(in srgb, var(--accent) 12%, transparent);\n}\n.jks__badge--key {\n color: #b45309;\n background: color-mix(in srgb, #f59e0b 16%, transparent);\n}\n.jks__badge--cert {\n color: #15803d;\n background: color-mix(in srgb, #22c55e 16%, transparent);\n}\n.jks__badge--secret {\n color: var(--muted);\n background: var(--surface-2);\n}\n\n/* Entry cards -------------------------------------------------------------------------------- */\n.jks__entry {\n margin-bottom: 14px;\n border: 1px solid var(--border);\n border-radius: 10px;\n background: var(--surface);\n overflow: hidden;\n}\n.jks__entryhead {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n}\n.jks__alias {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.jks__date {\n flex-shrink: 0;\n font-size: 11px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n}\n\n.jks__key {\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 9px 12px;\n font-size: 12px;\n background: color-mix(in srgb, var(--accent) 4%, transparent);\n border-bottom: 1px solid var(--border);\n}\n.jks__keyicon {\n flex-shrink: 0;\n color: var(--faint);\n}\n.jks__keyicon--ok {\n color: #15803d;\n}\n.jks__keyicon--bad {\n color: #b91c1c;\n}\n.jks__keytext {\n min-width: 0;\n color: var(--muted);\n}\n.jks__keytext--ok {\n color: #15803d;\n font-weight: 500;\n}\n.jks__keytext--bad {\n color: #b91c1c;\n}\n\n.jks__note {\n margin: 0;\n padding: 9px 12px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--muted);\n}\n\n/* Certificate chain -------------------------------------------------------------------------- */\n.jks__chain {\n padding: 10px 12px 12px;\n}\n.jks__chainlabel {\n margin-bottom: 8px;\n font-size: 11.5px;\n color: var(--faint);\n}\n.jks__cert {\n margin-bottom: 10px;\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n}\n.jks__cert:last-child {\n margin-bottom: 0;\n}\n.jks__certhead {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n background: var(--surface-2);\n border-bottom: 1px solid var(--border);\n}\n.jks__certcn {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.jks__cbadge {\n flex-shrink: 0;\n font-size: 11px;\n font-weight: 600;\n border-radius: 5px;\n padding: 2px 8px;\n}\n.jks__cbadge--ok {\n color: #15803d;\n background: color-mix(in srgb, #22c55e 16%, transparent);\n}\n.jks__cbadge--warn {\n color: #b45309;\n background: color-mix(in srgb, #f59e0b 18%, transparent);\n}\n.jks__cbadge--bad {\n color: #b91c1c;\n background: color-mix(in srgb, #ef4444 16%, transparent);\n}\n.jks__dl {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 6px 14px;\n margin: 0;\n padding: 10px;\n}\n.jks__dl dt {\n font-size: 12px;\n color: var(--faint);\n white-space: nowrap;\n}\n.jks__dl dd {\n margin: 0;\n font-size: 12.5px;\n color: var(--text);\n word-break: break-word;\n}\n.jks__mono {\n font-family: var(--font-mono);\n font-size: 11.5px;\n}\n.jks__fp {\n word-break: break-all;\n}\n</style>\n"],"mappings":""}
@@ -351,3 +351,5 @@ var ce = { class: "jks" }, le = {
351
351
  });
352
352
  //#endregion
353
353
  export { O as default };
354
+
355
+ //# sourceMappingURL=JksViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JksViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/JksViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'\nimport {\n decryptPrivateKey,\n JCEKS_MAGIC,\n JKS_MAGIC,\n JksError,\n readJks,\n verifyStorePassword,\n type JksEntry,\n type JksStore,\n type KeyDecryptResult,\n} from '@/lib/jks'\nimport { looksLikePkcs12, readPkcs12, type Pkcs12Result } from '@/lib/pkcs12'\nimport { parseCertificate, sha256Hex, type CertSummary } from '@/lib/x509'\nimport { formatBytes } from '@/lib/format'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * Side-viewer for keystores. Fetches the BYTES itself (binary-safe) and sniffs the format:\n * - JKS / JCEKS (magic 0xFEEDFEED / 0xCECECECE): the Sun binary format (see lib/jks.ts). Certificates are\n * plaintext, so the whole inventory shows immediately; a password verifies the store MAC and decrypts JKS\n * private keys.\n * - PKCS#12 / PFX (`.p12`/`.pfx`, and the very common `.jks`-that-is-actually-PKCS#12 — keytool defaults to\n * PKCS12 since JDK 9): everything is encrypted (see lib/pkcs12.ts), so nothing shows until the password\n * unlocks it; then certs + private keys appear and the integrity MAC is verified.\n *\n * Both render the same cards: per entry the alias / kind / (JKS) creation date, the private-key algorithm once\n * decrypted, and each certificate's X.509 fields (subject/issuer, validity + expiry badge, key + signature\n * algorithm, SANs, SHA-256 fingerprint — reusing lib/x509.ts). All parsing/decryption is in-browser; the\n * password and key material never leave the page.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Format = 'jks' | 'jceks' | 'pkcs12'\n\ninterface CertView {\n summary: CertSummary\n id: string\n}\ninterface EntryView {\n entry: JksEntry\n index: number\n certs: CertView[]\n}\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst format = ref<Format | null>(null)\nconst rawBuf = shallowRef<ArrayBuffer | null>(null) // kept for PKCS#12 (decrypted on password submit)\nconst jksStore = shallowRef<JksStore | null>(null)\nconst entryViews = shallowRef<EntryView[]>([])\nconst fingerprints = reactive<Record<string, string>>({})\n\n// Password state: one field drives store-MAC verification and per-key decryption (keytool defaults the store\n// password equal to each key password — the common case).\nconst password = ref('')\nconst pwTried = ref(false)\nconst integrity = ref<'unknown' | 'ok' | 'bad'>('unknown')\nconst unlocked = ref(false) // PKCS#12: bags decrypted\nconst keyResults = reactive<Record<number, KeyDecryptResult>>({})\nconst p12meta = shallowRef<Pkcs12Result | null>(null)\nconst p12error = ref('') // unsupported / malformed (distinct from wrong-password)\n\nconst SIZE_LIMIT = 8 * 1024 * 1024\n\nconst isPkcs12 = computed(() => format.value === 'pkcs12')\nconst storeTypeLabel = computed(() =>\n format.value === 'pkcs12' ? 'PKCS#12' : format.value === 'jceks' ? 'JCEKS' : 'JKS',\n)\nconst hasPrivateKeys = computed(() => entryViews.value.some((e) => e.entry.type === 'privateKey'))\nconst storeMeta = computed(() => {\n const s = jksStore.value\n if (format.value === 'pkcs12') {\n const m = p12meta.value\n if (m) return [...m.encryptionAlgos, m.macAlgorithm].filter(Boolean).join(' · ')\n return '证书与私钥均加密,输入密码解密'\n }\n return s ? `版本 ${s.version} · ${s.entries.length} 个条目` : ''\n})\n\nfunction fingerprintAll(): void {\n for (const ev of entryViews.value) {\n for (const cv of ev.certs) {\n if (cv.summary.ok) {\n void sha256Hex(cv.summary.der)\n .then((hex) => {\n fingerprints[cv.id] = hex\n })\n .catch(() => {})\n }\n }\n }\n}\n\nfunction buildJksEntryViews(store: JksStore): void {\n entryViews.value = store.entries.map((entry, index) => ({\n entry,\n index,\n certs: entry.certs.map((c, ci) => ({ summary: parseCertificate(c.der), id: `${index}.${ci}` })),\n }))\n}\n\nfunction processBuffer(buf: ArrayBuffer): void {\n const bytes = new Uint8Array(buf)\n const magic =\n bytes.length >= 4 ? ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0 : 0\n if (magic === JKS_MAGIC || magic === JCEKS_MAGIC) {\n const store = readJks(buf)\n format.value = store.storeType\n jksStore.value = store\n buildJksEntryViews(store)\n fingerprintAll()\n } else if (looksLikePkcs12(bytes)) {\n format.value = 'pkcs12'\n rawBuf.value = buf // wait for the password before decrypting\n } else {\n throw new JksError(\n `文件头不匹配(magic=0x${magic.toString(16).padStart(8, '0')})—— 不是 JKS/JCEKS/PKCS#12 密钥库`,\n )\n }\n status.value = 'ready'\n}\n\nfunction resetState(): void {\n errorMsg.value = ''\n format.value = null\n rawBuf.value = null\n jksStore.value = null\n entryViews.value = []\n password.value = ''\n pwTried.value = false\n integrity.value = 'unknown'\n unlocked.value = false\n p12meta.value = null\n p12error.value = ''\n for (const k of Object.keys(fingerprints)) delete fingerprints[k]\n for (const k of Object.keys(keyResults)) delete keyResults[Number(k)]\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n resetState()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n processBuffer(await res.arrayBuffer())\n } catch (e) {\n errorMsg.value = e instanceof JksError || 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\nfunction submitJksPassword(store: JksStore): void {\n integrity.value = verifyStorePassword(store, password.value) ? 'ok' : 'bad'\n for (const ev of entryViews.value) {\n if (ev.entry.type === 'privateKey' && ev.entry.encryptedKey) {\n keyResults[ev.index] = decryptPrivateKey(ev.entry.encryptedKey, password.value)\n }\n }\n}\n\nfunction buildPkcs12EntryViews(result: Pkcs12Result): void {\n const views: EntryView[] = []\n const usedCerts = new Set<number>()\n let idx = 0\n for (const k of result.keys) {\n const chain = result.certs.filter((c, ci) => {\n const match = !!k.localKeyId && c.localKeyId === k.localKeyId\n if (match) usedCerts.add(ci)\n return match\n })\n const entry: JksEntry = {\n type: 'privateKey',\n alias: k.friendlyName || k.localKeyId || `私钥 ${idx + 1}`,\n createdMs: 0,\n certs: chain.map((c) => ({ type: 'X.509', der: c.der })),\n }\n views.push({\n entry,\n index: idx,\n certs: entry.certs.map((c, ci) => ({ summary: parseCertificate(c.der), id: `${idx}.${ci}` })),\n })\n keyResults[idx] = {\n ok: true,\n key: { pkcs8: k.pkcs8, algorithm: k.algorithm, bits: k.bits, curve: k.curve },\n }\n idx++\n }\n result.certs.forEach((c, ci) => {\n if (usedCerts.has(ci)) return\n const entry: JksEntry = {\n type: 'trustedCert',\n alias: c.friendlyName || `证书 ${idx + 1}`,\n createdMs: 0,\n certs: [{ type: 'X.509', der: c.der }],\n }\n views.push({ entry, index: idx, certs: [{ summary: parseCertificate(c.der), id: `${idx}.0` }] })\n idx++\n })\n entryViews.value = views\n}\n\nfunction submitPkcs12Password(buf: ArrayBuffer): void {\n const r = readPkcs12(buf, password.value)\n if (!r.ok) {\n unlocked.value = false\n entryViews.value = []\n for (const k of Object.keys(keyResults)) delete keyResults[Number(k)]\n if (r.reason === 'wrong-password') {\n integrity.value = 'bad'\n p12error.value = ''\n } else {\n integrity.value = 'unknown'\n p12error.value =\n (r.reason === 'unsupported' ? '暂不支持的加密方式:' : '解析失败:') + (r.detail ?? '')\n }\n return\n }\n p12error.value = ''\n p12meta.value = r.result\n buildPkcs12EntryViews(r.result)\n integrity.value = r.result.macVerified === true ? 'ok' : 'unknown'\n unlocked.value = true\n fingerprintAll()\n}\n\n// Apply the password to whichever format this keystore is.\nfunction submitPassword(): void {\n if (!password.value) return\n pwTried.value = true\n if (format.value === 'pkcs12') {\n if (rawBuf.value) submitPkcs12Password(rawBuf.value)\n } else if (jksStore.value) {\n submitJksPassword(jksStore.value)\n }\n}\n\n// ── friendly-field formatting (shared with the cert cards) ─────────────────────────────────────────\nfunction formatDn(rdns: CertSummary['subject']): string {\n return rdns.map((r) => `${r.name}=${r.value}`).join(', ')\n}\n\nconst ENTRY_LABEL: Record<JksEntry['type'], string> = {\n privateKey: '私钥条目',\n trustedCert: '受信任证书',\n secretKey: '密钥条目',\n}\nconst ENTRY_CLASS: Record<JksEntry['type'], string> = {\n privateKey: 'jks__badge--key',\n trustedCert: 'jks__badge--cert',\n secretKey: 'jks__badge--secret',\n}\n\nfunction formatDate(ms: number): string {\n if (!ms) return ''\n try {\n return new Date(ms).toLocaleString('zh-CN', { hour12: false })\n } catch {\n return ''\n }\n}\n\nfunction publicKeyText(s: CertSummary): string {\n const parts: string[] = []\n if (s.pubKeyAlg) parts.push(s.pubKeyAlg)\n if (s.pubKeyBits) parts.push(`${s.pubKeyBits} 位`)\n if (s.pubKeyCurve && s.pubKeyCurve !== s.pubKeyAlg) parts.push(s.pubKeyCurve)\n return parts.join(' · ')\n}\n\n// Validity badge: future / expired / soon (≤30 days) / valid, from notBefore/notAfter vs now.\nfunction expiry(s: CertSummary): { text: string; cls: string } | null {\n if (s.notAfterMs == null) return null\n const now = Date.now()\n if (s.notBeforeMs != null && now < s.notBeforeMs)\n return { text: '尚未生效', cls: 'jks__cbadge--warn' }\n if (now > s.notAfterMs) return { text: '已过期', cls: 'jks__cbadge--bad' }\n const days = Math.floor((s.notAfterMs - now) / 86_400_000)\n if (days <= 30) return { text: `${days} 天后过期`, cls: 'jks__cbadge--warn' }\n return { text: `有效(剩 ${days} 天)`, cls: 'jks__cbadge--ok' }\n}\n\n// The private-key row's decrypted/locked/error state for one entry.\ntype KeyState =\n | { kind: 'locked' }\n | { kind: 'ok'; text: string }\n | { kind: 'wrong' }\n | { kind: 'unsupported'; detail?: string }\n | { kind: 'malformed' }\n\nfunction keyState(index: number): KeyState {\n const r = keyResults[index]\n if (!r) return { kind: 'locked' }\n if (r.ok) {\n const parts = [r.key.algorithm]\n if (r.key.bits) parts.push(`${r.key.bits} 位`)\n if (r.key.curve && r.key.curve !== r.key.algorithm) parts.push(r.key.curve)\n return { kind: 'ok', text: parts.join(' · ') }\n }\n if (r.reason === 'wrong-password') return { kind: 'wrong' }\n if (r.reason === 'unsupported') return { kind: 'unsupported', detail: r.detail }\n return { kind: 'malformed' }\n}\n\nconst pwPlaceholder = computed(() => {\n if (isPkcs12.value)\n return unlocked.value ? '已解锁(可重新输入)' : '输入 PKCS#12 密码解密证书与私钥'\n return hasPrivateKeys.value ? '输入密码:解密私钥并校验完整性' : '输入库密码校验完整性(可选)'\n})\n\n// Integrity/unlock status line after a password attempt (format-aware).\nconst integrityText = computed(() => {\n if (integrity.value === 'bad') {\n return isPkcs12.value ? '密码错误 —— 无法解密' : '完整性校验未通过 —— 密码错误或文件已被修改'\n }\n if (integrity.value === 'ok') {\n if (isPkcs12.value) return `密码正确,完整性校验通过(${p12meta.value?.macAlgorithm ?? 'MAC'})`\n return '完整性校验通过 —— 密码正确,keystore 未被篡改'\n }\n // PKCS#12 unlocked but MAC absent/unsupported: still a successful decrypt.\n if (isPkcs12.value && unlocked.value) return '已解密(该文件无 MAC 或摘要不支持,密码经解密验证)'\n return ''\n})\nconst integrityOk = computed(() => integrity.value === 'ok' || (isPkcs12.value && unlocked.value))\n\nwatch(() => [props.url, props.name], load, { immediate: true })\nonBeforeUnmount(() => {\n // nothing to revoke; bytes are not retained as object URLs\n})\n</script>\n\n<template>\n <div class=\"jks\">\n <p v-if=\"status === 'loading'\" class=\"jks__msg\">正在解析 keystore…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"jks__msg jks__gate\">\n <p>该文件较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"jks__btn\" @click=\"doLoad\">解析并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"jks__msg jks__msg--error\">\n <p>无法解析:{{ errorMsg }}</p>\n <button type=\"button\" class=\"jks__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <div v-else-if=\"format\" class=\"jks__scroll\">\n <!-- store header + password -->\n <section class=\"jks__store\">\n <header class=\"jks__storehead\">\n <span class=\"jks__badge jks__badge--type\">{{ storeTypeLabel }}</span>\n <span class=\"jks__storetitle\">{{ isPkcs12 ? 'PKCS#12 密钥库' : 'Java KeyStore' }}</span>\n <span class=\"jks__storemeta\">{{ storeMeta }}</span>\n </header>\n\n <div class=\"jks__pw\" :class=\"{ 'jks__pw--ok': integrityOk }\">\n <Icon name=\"key\" :size=\"14\" />\n <input\n v-model=\"password\"\n type=\"password\"\n class=\"jks__pwinput\"\n :placeholder=\"pwPlaceholder\"\n autocomplete=\"off\"\n @keyup.enter=\"submitPassword\"\n />\n <button type=\"button\" class=\"jks__pwbtn\" @click=\"submitPassword\">\n {{ isPkcs12 && unlocked ? '重新解锁' : '解锁' }}\n </button>\n </div>\n\n <p\n v-if=\"pwTried && integrityText\"\n class=\"jks__integrity\"\n :class=\"`jks__integrity--${integrity}`\"\n >\n <Icon :name=\"integrityOk ? 'check' : 'close'\" :size=\"13\" />\n <span>{{ integrityText }}</span>\n </p>\n\n <p class=\"jks__hint\">全部解析与解密均在浏览器本地完成,密码与密钥不会上传。</p>\n <p v-if=\"jksStore?.truncatedNote\" class=\"jks__warn\">{{ jksStore.truncatedNote }}</p>\n <p v-if=\"p12error\" class=\"jks__warn\">{{ p12error }}</p>\n <p v-for=\"(w, i) in p12meta?.warnings ?? []\" :key=\"i\" class=\"jks__warn\">{{ w }}</p>\n </section>\n\n <!-- PKCS#12 pre-unlock: nothing is visible until decrypted -->\n <div v-if=\"isPkcs12 && !unlocked\" class=\"jks__locked\">\n <Icon name=\"key\" :size=\"26\" class=\"jks__lockicon\" />\n <p>PKCS#12 密钥库的证书与私钥均已加密,输入上方密码后在此显示内容。</p>\n </div>\n\n <!-- entries -->\n <section v-for=\"ev in entryViews\" :key=\"ev.index\" class=\"jks__entry\">\n <header class=\"jks__entryhead\">\n <span class=\"jks__badge\" :class=\"ENTRY_CLASS[ev.entry.type]\">{{\n ENTRY_LABEL[ev.entry.type]\n }}</span>\n <span class=\"jks__alias\" :title=\"ev.entry.alias\">{{ ev.entry.alias }}</span>\n <span v-if=\"ev.entry.createdMs\" class=\"jks__date\">{{\n formatDate(ev.entry.createdMs)\n }}</span>\n </header>\n\n <!-- private key material -->\n <div v-if=\"ev.entry.type === 'privateKey'\" class=\"jks__key\">\n <template v-if=\"keyState(ev.index).kind === 'locked'\">\n <Icon name=\"key\" :size=\"13\" class=\"jks__keyicon\" />\n <span class=\"jks__keytext\">私钥已加密 —— 输入密码后在此显示密钥算法</span>\n </template>\n <template v-else-if=\"keyState(ev.index).kind === 'ok'\">\n <Icon name=\"check\" :size=\"13\" class=\"jks__keyicon jks__keyicon--ok\" />\n <span class=\"jks__keytext jks__keytext--ok\"\n >私钥已解密 · {{ (keyState(ev.index) as { text: string }).text }}</span\n >\n </template>\n <template v-else-if=\"keyState(ev.index).kind === 'wrong'\">\n <Icon name=\"close\" :size=\"13\" class=\"jks__keyicon jks__keyicon--bad\" />\n <span class=\"jks__keytext jks__keytext--bad\">密码错误 —— 无法解密该私钥</span>\n </template>\n <template v-else-if=\"keyState(ev.index).kind === 'unsupported'\">\n <Icon name=\"key\" :size=\"13\" class=\"jks__keyicon\" />\n <span class=\"jks__keytext\"\n >该私钥加密方式暂不支持浏览器内解密({{\n (keyState(ev.index) as { detail?: string }).detail\n }})—— 证书链仍可查看</span\n >\n </template>\n <template v-else>\n <Icon name=\"close\" :size=\"13\" class=\"jks__keyicon jks__keyicon--bad\" />\n <span class=\"jks__keytext jks__keytext--bad\">私钥数据无法解析</span>\n </template>\n </div>\n\n <p v-else-if=\"ev.entry.type === 'secretKey'\" class=\"jks__note\">\n JCEKS 对称密钥条目(SecretKey)—— 以 Java 序列化对象存储,暂不解析。\n </p>\n\n <!-- certificate chain -->\n <div v-if=\"ev.certs.length\" class=\"jks__chain\">\n <div v-if=\"ev.entry.type === 'privateKey'\" class=\"jks__chainlabel\">\n 证书链({{ ev.certs.length }})\n </div>\n <article v-for=\"cv in ev.certs\" :key=\"cv.id\" class=\"jks__cert\">\n <div class=\"jks__certhead\">\n <span class=\"jks__certcn\">{{ cv.summary.subjectCN || cv.summary.label }}</span>\n <span v-if=\"expiry(cv.summary)\" class=\"jks__cbadge\" :class=\"expiry(cv.summary)?.cls\">\n {{ expiry(cv.summary)?.text }}\n </span>\n </div>\n <p v-if=\"cv.summary.note && !cv.summary.ok\" class=\"jks__note\">{{ cv.summary.note }}</p>\n <dl class=\"jks__dl\">\n <template v-if=\"cv.summary.subject.length\">\n <dt>主题</dt>\n <dd>{{ formatDn(cv.summary.subject) }}</dd>\n </template>\n <template v-if=\"cv.summary.issuer.length\">\n <dt>颁发者</dt>\n <dd>{{ formatDn(cv.summary.issuer) }}</dd>\n </template>\n <template v-if=\"cv.summary.notBefore || cv.summary.notAfter\">\n <dt>有效期</dt>\n <dd>{{ cv.summary.notBefore ?? '?' }} → {{ cv.summary.notAfter ?? '?' }}</dd>\n </template>\n <template v-if=\"publicKeyText(cv.summary)\">\n <dt>公钥</dt>\n <dd>{{ publicKeyText(cv.summary) }}</dd>\n </template>\n <template v-if=\"cv.summary.sigAlg\">\n <dt>签名算法</dt>\n <dd>{{ cv.summary.sigAlg }}</dd>\n </template>\n <template v-if=\"cv.summary.serialHex\">\n <dt>序列号</dt>\n <dd class=\"jks__mono\">{{ cv.summary.serialHex }}</dd>\n </template>\n <template v-if=\"cv.summary.sans.length\">\n <dt>备用名 (SAN)</dt>\n <dd>{{ cv.summary.sans.join(',') }}</dd>\n </template>\n <template v-if=\"fingerprints[cv.id]\">\n <dt>SHA-256</dt>\n <dd class=\"jks__mono jks__fp\">{{ fingerprints[cv.id] }}</dd>\n </template>\n </dl>\n </article>\n </div>\n </section>\n </div>\n </div>\n</template>\n\n<style scoped>\n.jks {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.jks__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.jks__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.jks__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.jks__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.jks__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.jks__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 14px 14px 60px;\n}\n\n/* Store header + password ---------------------------------------------------------------------- */\n.jks__store {\n margin-bottom: 16px;\n border: 1px solid var(--border);\n border-radius: 10px;\n background: var(--surface);\n overflow: hidden;\n}\n.jks__storehead {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n background: var(--surface-2);\n border-bottom: 1px solid var(--border);\n}\n.jks__storetitle {\n flex-shrink: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.jks__storemeta {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n font-size: 11.5px;\n color: var(--faint);\n}\n\n.jks__pw {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n color: var(--muted);\n}\n.jks__pw--ok {\n color: var(--accent);\n}\n.jks__pwinput {\n flex: 1 1 auto;\n min-width: 0;\n height: 28px;\n padding: 0 9px;\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: 7px;\n}\n.jks__pwinput:focus {\n outline: none;\n border-color: var(--accent);\n}\n.jks__pwbtn {\n flex-shrink: 0;\n height: 28px;\n padding: 0 14px;\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: 7px;\n cursor: pointer;\n}\n.jks__pwbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.jks__integrity {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n padding: 0 12px 8px;\n font-size: 12px;\n}\n.jks__integrity--ok {\n color: #15803d;\n}\n.jks__integrity--bad {\n color: #b91c1c;\n}\n.jks__integrity--unknown {\n color: var(--accent);\n}\n.jks__hint {\n margin: 0;\n padding: 0 12px 10px;\n font-size: 11.5px;\n line-height: 1.5;\n color: var(--faint);\n}\n.jks__warn {\n margin: 0;\n padding: 9px 12px;\n font-size: 12px;\n line-height: 1.5;\n color: #b45309;\n background: color-mix(in srgb, #f59e0b 12%, transparent);\n border-top: 1px solid var(--border);\n}\n\n.jks__locked {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n padding: 40px 24px;\n text-align: center;\n color: var(--muted);\n font-size: 12.5px;\n}\n.jks__lockicon {\n color: var(--faint);\n}\n\n/* Badges ------------------------------------------------------------------------------------- */\n.jks__badge {\n flex-shrink: 0;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n border-radius: 5px;\n padding: 2px 7px;\n}\n.jks__badge--type {\n color: var(--accent);\n background: color-mix(in srgb, var(--accent) 12%, transparent);\n}\n.jks__badge--key {\n color: #b45309;\n background: color-mix(in srgb, #f59e0b 16%, transparent);\n}\n.jks__badge--cert {\n color: #15803d;\n background: color-mix(in srgb, #22c55e 16%, transparent);\n}\n.jks__badge--secret {\n color: var(--muted);\n background: var(--surface-2);\n}\n\n/* Entry cards -------------------------------------------------------------------------------- */\n.jks__entry {\n margin-bottom: 14px;\n border: 1px solid var(--border);\n border-radius: 10px;\n background: var(--surface);\n overflow: hidden;\n}\n.jks__entryhead {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n}\n.jks__alias {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.jks__date {\n flex-shrink: 0;\n font-size: 11px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n}\n\n.jks__key {\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 9px 12px;\n font-size: 12px;\n background: color-mix(in srgb, var(--accent) 4%, transparent);\n border-bottom: 1px solid var(--border);\n}\n.jks__keyicon {\n flex-shrink: 0;\n color: var(--faint);\n}\n.jks__keyicon--ok {\n color: #15803d;\n}\n.jks__keyicon--bad {\n color: #b91c1c;\n}\n.jks__keytext {\n min-width: 0;\n color: var(--muted);\n}\n.jks__keytext--ok {\n color: #15803d;\n font-weight: 500;\n}\n.jks__keytext--bad {\n color: #b91c1c;\n}\n\n.jks__note {\n margin: 0;\n padding: 9px 12px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--muted);\n}\n\n/* Certificate chain -------------------------------------------------------------------------- */\n.jks__chain {\n padding: 10px 12px 12px;\n}\n.jks__chainlabel {\n margin-bottom: 8px;\n font-size: 11.5px;\n color: var(--faint);\n}\n.jks__cert {\n margin-bottom: 10px;\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n}\n.jks__cert:last-child {\n margin-bottom: 0;\n}\n.jks__certhead {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n background: var(--surface-2);\n border-bottom: 1px solid var(--border);\n}\n.jks__certcn {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.jks__cbadge {\n flex-shrink: 0;\n font-size: 11px;\n font-weight: 600;\n border-radius: 5px;\n padding: 2px 8px;\n}\n.jks__cbadge--ok {\n color: #15803d;\n background: color-mix(in srgb, #22c55e 16%, transparent);\n}\n.jks__cbadge--warn {\n color: #b45309;\n background: color-mix(in srgb, #f59e0b 18%, transparent);\n}\n.jks__cbadge--bad {\n color: #b91c1c;\n background: color-mix(in srgb, #ef4444 16%, transparent);\n}\n.jks__dl {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 6px 14px;\n margin: 0;\n padding: 10px;\n}\n.jks__dl dt {\n font-size: 12px;\n color: var(--faint);\n white-space: nowrap;\n}\n.jks__dl dd {\n margin: 0;\n font-size: 12.5px;\n color: var(--text);\n word-break: break-word;\n}\n.jks__mono {\n font-family: var(--font-mono);\n font-size: 11.5px;\n}\n.jks__fp {\n word-break: break-all;\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./JsonlViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-326ae959"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=JsonlViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonlViewer.js","names":[],"sources":["../../src/components/JsonlViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { parseJsonl, type JsonlRecord } from '@/lib/jsonl'\nimport { highlightCode } from '@/lib/highlight'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * Side-viewer JSON Lines (`.jsonl` / `.ndjson`) renderer (render mode). Receives the fetched text (the panel\n * loads it, like CsvViewer / SubtitleViewer), parses it into per-line records (see lib/jsonl.ts), and shows a\n * compact LIST — one truncated single-line preview per record. Clicking a row expands it to the pretty-printed,\n * syntax-highlighted value. Big files stay responsive two ways: each row's preview is a short single line, and\n * only an initial window of rows is in the DOM (a \"load more\" button grows it). The source toggle (header) shows\n * the raw file highlighted as JSON.\n */\nconst props = defineProps<{ text: string; name: string }>()\n\nconst PREVIEW_CHARS = 200 // truncate each row's inline preview so even a huge record is one short line\nconst INITIAL_ROWS = 200 // rows mounted initially; \"load more\" grows the window\nconst ROW_STEP = 400\n\nconst doc = computed(() => parseJsonl(props.text))\nconst shown = ref(INITIAL_ROWS)\nconst expanded = ref<Set<number>>(new Set())\n\n// A new file resets the window + any open rows.\nwatch(\n () => props.text,\n () => {\n shown.value = INITIAL_ROWS\n expanded.value = new Set()\n },\n)\n\nconst visible = computed(() => doc.value.records.slice(0, shown.value))\nconst remaining = computed(() => Math.max(0, doc.value.records.length - shown.value))\n\nfunction loadMore(): void {\n shown.value += ROW_STEP\n}\n\nfunction toggle(line: number): void {\n const s = new Set(expanded.value)\n if (s.has(line)) s.delete(line)\n else s.add(line)\n expanded.value = s\n}\n\n// Per-row copy: write the record's RAW line (the faithful on-disk text, not the re-stringified value) to the\n// clipboard, and flash a ✓ on that row. Mirrors the CodeBlock copy pattern (best-effort, cleared on a timer).\nconst copiedLine = ref<number | null>(null)\nlet copyTimer: ReturnType<typeof setTimeout> | undefined\nasync function copyRow(r: JsonlRecord): Promise<void> {\n try {\n await navigator.clipboard?.writeText(r.raw)\n } catch {\n /* clipboard unavailable — ignore */\n }\n copiedLine.value = r.line\n clearTimeout(copyTimer)\n copyTimer = setTimeout(() => (copiedLine.value = null), 1400)\n}\nonBeforeUnmount(() => clearTimeout(copyTimer))\n\n// Compact one-line preview: stringify the parsed value (whitespace collapsed) or, for an error row, the raw\n// line — then hard-truncate. Building it from the value normalizes spacing so the list reads cleanly.\nfunction preview(r: JsonlRecord): string {\n let s: string\n if (r.error) s = r.raw\n else {\n try {\n s = JSON.stringify(r.value) ?? String(r.value)\n } catch {\n s = r.raw\n }\n }\n s = s.replace(/\\s+/g, ' ').trim()\n return s.length > PREVIEW_CHARS ? s.slice(0, PREVIEW_CHARS) + '…' : s\n}\n\n// Pretty value, highlighted as JSON — built lazily, only for an expanded row.\nfunction expandedHtml(r: JsonlRecord): string {\n if (r.error) return highlightCode(r.raw, 'json')\n try {\n return highlightCode(JSON.stringify(r.value, null, 2) ?? '', 'json')\n } catch {\n return highlightCode(r.raw, 'json')\n }\n}\n</script>\n\n<template>\n <div class=\"jl\">\n <div class=\"jl__summary\">\n <span class=\"jl__count\">{{ doc.records.length }} 条记录</span>\n <span v-if=\"doc.errorCount\" class=\"jl__errtag\">{{ doc.errorCount }} 行无法解析</span>\n </div>\n\n <div class=\"jl__list\">\n <p v-if=\"!doc.records.length\" class=\"jl__msg\">(空文件,无 JSON 记录)</p>\n\n <div v-for=\"r in visible\" :key=\"r.line\" class=\"jl__rowwrap\">\n <div\n class=\"jl__rowhead\"\n :class=\"{ 'jl__rowhead--open': expanded.has(r.line), 'jl__rowhead--err': r.error }\"\n >\n <button\n type=\"button\"\n class=\"jl__row\"\n :title=\"r.error ? `第 ${r.line} 行:JSON 无效` : `第 ${r.line} 行 —— 点击展开`\"\n @click=\"toggle(r.line)\"\n >\n <span class=\"jl__caret\" :class=\"{ 'jl__caret--open': expanded.has(r.line) }\">▶</span>\n <span class=\"jl__ln\">{{ r.line }}</span>\n <span v-if=\"r.error\" class=\"jl__badge\">无效</span>\n <span class=\"jl__preview\">{{ preview(r) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"jl__copy\"\n :class=\"{ 'jl__copy--done': copiedLine === r.line }\"\n :title=\"copiedLine === r.line ? '已复制' : '复制此行'\"\n @click=\"copyRow(r)\"\n >\n <Icon :name=\"copiedLine === r.line ? 'check' : 'copy'\" :size=\"13\" />\n </button>\n </div>\n <pre\n v-if=\"expanded.has(r.line)\"\n class=\"jl__full\"\n ><code class=\"hljs\" v-html=\"expandedHtml(r)\" /></pre>\n </div>\n\n <button v-if=\"remaining\" type=\"button\" class=\"jl__more\" @click=\"loadMore\">\n 显示更多(还有 {{ remaining }} 条)\n </button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.jl {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.jl__summary {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 6px 12px;\n flex-wrap: wrap;\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.jl__count {\n font-weight: 600;\n color: var(--accent);\n}\n.jl__errtag {\n padding: 1px 7px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--danger) 14%, var(--surface));\n color: var(--danger);\n font-size: 10.5px;\n}\n\n.jl__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n\n.jl__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: scroll; /* always reserve the gutter so the draggable bar is present, not wheel-only */\n overscroll-behavior: contain;\n padding: 4px 0;\n /* Always-visible, draggable scrollbar — the record list gets long and an auto-hidden overlay bar reads\n * as \"no scrollbar\". Thin styled bar in Firefox; a classic (gutter-reserving) bar in Chromium/WebKit. */\n scrollbar-width: thin;\n scrollbar-color: var(--border-2) transparent;\n}\n.jl__list::-webkit-scrollbar {\n width: 12px;\n}\n.jl__list::-webkit-scrollbar-thumb {\n background: var(--border-2);\n background-clip: padding-box;\n border: 3px solid transparent;\n border-radius: 999px;\n}\n.jl__list::-webkit-scrollbar-thumb:hover {\n background: var(--muted);\n background-clip: padding-box;\n border: 3px solid transparent;\n}\n.jl__rowwrap {\n border-bottom: 1px solid var(--border);\n}\n\n/* Row header = the clickable preview button + a trailing copy button (a button can't nest inside a button,\n * so they're siblings in this flex row; the hover/open/error tint lives here, spanning both). */\n.jl__rowhead {\n display: flex;\n align-items: stretch;\n}\n.jl__rowhead:hover,\n.jl__rowhead--open {\n background: var(--surface-2);\n}\n\n.jl__row {\n display: flex;\n align-items: baseline;\n gap: 9px;\n flex: 1 1 auto;\n min-width: 0;\n padding: 6px 14px;\n background: transparent;\n border: none;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n/* Trailing per-row copy button: faint by default, brightening on hover; accent flash when just copied. */\n.jl__copy {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n background: transparent;\n border: none;\n color: var(--faint);\n cursor: pointer;\n transition: color 0.15s;\n}\n.jl__copy:hover {\n color: var(--accent);\n}\n.jl__copy--done {\n color: var(--accent);\n}\n.jl__caret {\n flex-shrink: 0;\n width: 9px;\n font-size: 9px;\n color: var(--faint);\n transition: transform 0.12s;\n transform: rotate(0deg);\n align-self: center;\n}\n.jl__caret--open {\n transform: rotate(90deg);\n}\n.jl__ln {\n flex-shrink: 0;\n min-width: 30px;\n text-align: right;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n user-select: none;\n}\n.jl__badge {\n flex-shrink: 0;\n padding: 0 6px;\n border-radius: 5px;\n background: color-mix(in srgb, var(--danger) 16%, var(--surface));\n color: var(--danger);\n font-size: 9.5px;\n font-weight: 700;\n letter-spacing: 0.04em;\n align-self: center;\n}\n.jl__preview {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 12.5px;\n color: var(--text);\n}\n.jl__rowhead--err .jl__preview {\n color: var(--muted);\n}\n\n.jl__full {\n margin: 0;\n padding: 8px 14px 12px 32px;\n overflow-x: auto;\n background: var(--code-bg);\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.55;\n white-space: pre;\n tab-size: 2;\n}\n.jl__full code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n\n.jl__more {\n display: block;\n width: 100%;\n padding: 10px 16px;\n font-family: inherit;\n font-size: 12px;\n color: var(--accent);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.jl__more:hover {\n background: var(--surface-2);\n}\n</style>\n"],"mappings":""}
@@ -101,3 +101,5 @@ var _ = { class: "jl" }, v = { class: "jl__summary" }, y = { class: "jl__count"
101
101
  });
102
102
  //#endregion
103
103
  export { N as default };
104
+
105
+ //# sourceMappingURL=JsonlViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonlViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/JsonlViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { parseJsonl, type JsonlRecord } from '@/lib/jsonl'\nimport { highlightCode } from '@/lib/highlight'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * Side-viewer JSON Lines (`.jsonl` / `.ndjson`) renderer (render mode). Receives the fetched text (the panel\n * loads it, like CsvViewer / SubtitleViewer), parses it into per-line records (see lib/jsonl.ts), and shows a\n * compact LIST — one truncated single-line preview per record. Clicking a row expands it to the pretty-printed,\n * syntax-highlighted value. Big files stay responsive two ways: each row's preview is a short single line, and\n * only an initial window of rows is in the DOM (a \"load more\" button grows it). The source toggle (header) shows\n * the raw file highlighted as JSON.\n */\nconst props = defineProps<{ text: string; name: string }>()\n\nconst PREVIEW_CHARS = 200 // truncate each row's inline preview so even a huge record is one short line\nconst INITIAL_ROWS = 200 // rows mounted initially; \"load more\" grows the window\nconst ROW_STEP = 400\n\nconst doc = computed(() => parseJsonl(props.text))\nconst shown = ref(INITIAL_ROWS)\nconst expanded = ref<Set<number>>(new Set())\n\n// A new file resets the window + any open rows.\nwatch(\n () => props.text,\n () => {\n shown.value = INITIAL_ROWS\n expanded.value = new Set()\n },\n)\n\nconst visible = computed(() => doc.value.records.slice(0, shown.value))\nconst remaining = computed(() => Math.max(0, doc.value.records.length - shown.value))\n\nfunction loadMore(): void {\n shown.value += ROW_STEP\n}\n\nfunction toggle(line: number): void {\n const s = new Set(expanded.value)\n if (s.has(line)) s.delete(line)\n else s.add(line)\n expanded.value = s\n}\n\n// Per-row copy: write the record's RAW line (the faithful on-disk text, not the re-stringified value) to the\n// clipboard, and flash a ✓ on that row. Mirrors the CodeBlock copy pattern (best-effort, cleared on a timer).\nconst copiedLine = ref<number | null>(null)\nlet copyTimer: ReturnType<typeof setTimeout> | undefined\nasync function copyRow(r: JsonlRecord): Promise<void> {\n try {\n await navigator.clipboard?.writeText(r.raw)\n } catch {\n /* clipboard unavailable — ignore */\n }\n copiedLine.value = r.line\n clearTimeout(copyTimer)\n copyTimer = setTimeout(() => (copiedLine.value = null), 1400)\n}\nonBeforeUnmount(() => clearTimeout(copyTimer))\n\n// Compact one-line preview: stringify the parsed value (whitespace collapsed) or, for an error row, the raw\n// line — then hard-truncate. Building it from the value normalizes spacing so the list reads cleanly.\nfunction preview(r: JsonlRecord): string {\n let s: string\n if (r.error) s = r.raw\n else {\n try {\n s = JSON.stringify(r.value) ?? String(r.value)\n } catch {\n s = r.raw\n }\n }\n s = s.replace(/\\s+/g, ' ').trim()\n return s.length > PREVIEW_CHARS ? s.slice(0, PREVIEW_CHARS) + '…' : s\n}\n\n// Pretty value, highlighted as JSON — built lazily, only for an expanded row.\nfunction expandedHtml(r: JsonlRecord): string {\n if (r.error) return highlightCode(r.raw, 'json')\n try {\n return highlightCode(JSON.stringify(r.value, null, 2) ?? '', 'json')\n } catch {\n return highlightCode(r.raw, 'json')\n }\n}\n</script>\n\n<template>\n <div class=\"jl\">\n <div class=\"jl__summary\">\n <span class=\"jl__count\">{{ doc.records.length }} 条记录</span>\n <span v-if=\"doc.errorCount\" class=\"jl__errtag\">{{ doc.errorCount }} 行无法解析</span>\n </div>\n\n <div class=\"jl__list\">\n <p v-if=\"!doc.records.length\" class=\"jl__msg\">(空文件,无 JSON 记录)</p>\n\n <div v-for=\"r in visible\" :key=\"r.line\" class=\"jl__rowwrap\">\n <div\n class=\"jl__rowhead\"\n :class=\"{ 'jl__rowhead--open': expanded.has(r.line), 'jl__rowhead--err': r.error }\"\n >\n <button\n type=\"button\"\n class=\"jl__row\"\n :title=\"r.error ? `第 ${r.line} 行:JSON 无效` : `第 ${r.line} 行 —— 点击展开`\"\n @click=\"toggle(r.line)\"\n >\n <span class=\"jl__caret\" :class=\"{ 'jl__caret--open': expanded.has(r.line) }\">▶</span>\n <span class=\"jl__ln\">{{ r.line }}</span>\n <span v-if=\"r.error\" class=\"jl__badge\">无效</span>\n <span class=\"jl__preview\">{{ preview(r) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"jl__copy\"\n :class=\"{ 'jl__copy--done': copiedLine === r.line }\"\n :title=\"copiedLine === r.line ? '已复制' : '复制此行'\"\n @click=\"copyRow(r)\"\n >\n <Icon :name=\"copiedLine === r.line ? 'check' : 'copy'\" :size=\"13\" />\n </button>\n </div>\n <pre\n v-if=\"expanded.has(r.line)\"\n class=\"jl__full\"\n ><code class=\"hljs\" v-html=\"expandedHtml(r)\" /></pre>\n </div>\n\n <button v-if=\"remaining\" type=\"button\" class=\"jl__more\" @click=\"loadMore\">\n 显示更多(还有 {{ remaining }} 条)\n </button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.jl {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.jl__summary {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 6px 12px;\n flex-wrap: wrap;\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.jl__count {\n font-weight: 600;\n color: var(--accent);\n}\n.jl__errtag {\n padding: 1px 7px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--danger) 14%, var(--surface));\n color: var(--danger);\n font-size: 10.5px;\n}\n\n.jl__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n\n.jl__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: scroll; /* always reserve the gutter so the draggable bar is present, not wheel-only */\n overscroll-behavior: contain;\n padding: 4px 0;\n /* Always-visible, draggable scrollbar — the record list gets long and an auto-hidden overlay bar reads\n * as \"no scrollbar\". Thin styled bar in Firefox; a classic (gutter-reserving) bar in Chromium/WebKit. */\n scrollbar-width: thin;\n scrollbar-color: var(--border-2) transparent;\n}\n.jl__list::-webkit-scrollbar {\n width: 12px;\n}\n.jl__list::-webkit-scrollbar-thumb {\n background: var(--border-2);\n background-clip: padding-box;\n border: 3px solid transparent;\n border-radius: 999px;\n}\n.jl__list::-webkit-scrollbar-thumb:hover {\n background: var(--muted);\n background-clip: padding-box;\n border: 3px solid transparent;\n}\n.jl__rowwrap {\n border-bottom: 1px solid var(--border);\n}\n\n/* Row header = the clickable preview button + a trailing copy button (a button can't nest inside a button,\n * so they're siblings in this flex row; the hover/open/error tint lives here, spanning both). */\n.jl__rowhead {\n display: flex;\n align-items: stretch;\n}\n.jl__rowhead:hover,\n.jl__rowhead--open {\n background: var(--surface-2);\n}\n\n.jl__row {\n display: flex;\n align-items: baseline;\n gap: 9px;\n flex: 1 1 auto;\n min-width: 0;\n padding: 6px 14px;\n background: transparent;\n border: none;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n/* Trailing per-row copy button: faint by default, brightening on hover; accent flash when just copied. */\n.jl__copy {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n background: transparent;\n border: none;\n color: var(--faint);\n cursor: pointer;\n transition: color 0.15s;\n}\n.jl__copy:hover {\n color: var(--accent);\n}\n.jl__copy--done {\n color: var(--accent);\n}\n.jl__caret {\n flex-shrink: 0;\n width: 9px;\n font-size: 9px;\n color: var(--faint);\n transition: transform 0.12s;\n transform: rotate(0deg);\n align-self: center;\n}\n.jl__caret--open {\n transform: rotate(90deg);\n}\n.jl__ln {\n flex-shrink: 0;\n min-width: 30px;\n text-align: right;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n user-select: none;\n}\n.jl__badge {\n flex-shrink: 0;\n padding: 0 6px;\n border-radius: 5px;\n background: color-mix(in srgb, var(--danger) 16%, var(--surface));\n color: var(--danger);\n font-size: 9.5px;\n font-weight: 700;\n letter-spacing: 0.04em;\n align-self: center;\n}\n.jl__preview {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 12.5px;\n color: var(--text);\n}\n.jl__rowhead--err .jl__preview {\n color: var(--muted);\n}\n\n.jl__full {\n margin: 0;\n padding: 8px 14px 12px 32px;\n overflow-x: auto;\n background: var(--code-bg);\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.55;\n white-space: pre;\n tab-size: 2;\n}\n.jl__full code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n\n.jl__more {\n display: block;\n width: 100%;\n padding: 10px 16px;\n font-family: inherit;\n font-size: 12px;\n color: var(--accent);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.jl__more:hover {\n background: var(--surface-2);\n}\n</style>\n"],"mappings":"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"}
@@ -5,3 +5,5 @@ import t from "./LogViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-b19d397d"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=LogViewer.js.map