@akairos/pipeline 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/ARTICLE-THEMES.md +2 -0
  2. package/README.md +27 -4
  3. package/package.json +7 -5
  4. package/src/adapter-loader.ts +24 -4
  5. package/src/adapter-types.ts +14 -0
  6. package/src/article-theme.test.ts +55 -0
  7. package/src/article-theme.ts +10 -2
  8. package/src/commands/article-theme.ts +1 -1
  9. package/src/commands/init.ts +2 -0
  10. package/src/commands/publish.ts +1 -1
  11. package/src/commands/typeset-assets-sync.test.ts +12 -1
  12. package/src/commands/wechat-export.ts +9 -4
  13. package/src/plugin-runner.ts +10 -4
  14. package/src/plugin-runtime.test.ts +19 -0
  15. package/src/plugin-runtime.ts +11 -2
  16. package/src/providers/index.ts +4 -2
  17. package/src/runtime-package.ts +13 -6
  18. package/src/wechat-preview/assets/browser-dist/.vite/manifest.json +1280 -5
  19. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  20. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  21. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  22. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  23. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  24. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  25. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  26. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  27. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  28. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  29. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  30. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  31. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  32. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  33. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  34. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  35. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  36. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  37. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  38. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  39. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  40. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  41. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  42. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  43. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  44. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  45. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  46. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  47. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  48. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  49. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  50. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  51. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  52. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  53. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  54. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  55. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  56. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  57. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  58. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  59. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  60. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  61. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  62. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  63. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  64. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  65. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  66. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  67. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  68. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  69. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  70. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  71. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  72. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  73. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  74. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  75. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  76. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  77. package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  78. package/src/wechat-preview/assets/browser-dist/assets/apl-B4CMkyY2.js +1 -0
  79. package/src/wechat-preview/assets/browser-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  80. package/src/wechat-preview/assets/browser-dist/assets/asn1-EdZsLKOL.js +1 -0
  81. package/src/wechat-preview/assets/browser-dist/assets/asterisk-B-8jnY81.js +1 -0
  82. package/src/wechat-preview/assets/browser-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  83. package/src/wechat-preview/assets/browser-dist/assets/clike-B9uivgTg.js +1 -0
  84. package/src/wechat-preview/assets/browser-dist/assets/clojure-BMjYHr_A.js +1 -0
  85. package/src/wechat-preview/assets/browser-dist/assets/cmake-BQqOBYOt.js +1 -0
  86. package/src/wechat-preview/assets/browser-dist/assets/cobol-CWcv1MsR.js +1 -0
  87. package/src/wechat-preview/assets/browser-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  88. package/src/wechat-preview/assets/browser-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  89. package/src/wechat-preview/assets/browser-dist/assets/crystal-SjHAIU92.js +1 -0
  90. package/src/wechat-preview/assets/browser-dist/assets/css-BnMrqG3P.js +1 -0
  91. package/src/wechat-preview/assets/browser-dist/assets/cypher-C_CwsFkJ.js +1 -0
  92. package/src/wechat-preview/assets/browser-dist/assets/d-pRatUO7H.js +1 -0
  93. package/src/wechat-preview/assets/browser-dist/assets/diff-DbItnlRl.js +1 -0
  94. package/src/wechat-preview/assets/browser-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  95. package/src/wechat-preview/assets/browser-dist/assets/dtd-DF_7sFjM.js +1 -0
  96. package/src/wechat-preview/assets/browser-dist/assets/dylan-DwRh75JA.js +1 -0
  97. package/src/wechat-preview/assets/browser-dist/assets/ebnf-CDyGwa7X.js +1 -0
  98. package/src/wechat-preview/assets/browser-dist/assets/ecl-Cabwm37j.js +1 -0
  99. package/src/wechat-preview/assets/browser-dist/assets/eiffel-CnydiIhH.js +1 -0
  100. package/src/wechat-preview/assets/browser-dist/assets/elm-vLlmbW-K.js +1 -0
  101. package/src/wechat-preview/assets/browser-dist/assets/erlang-BNw1qcRV.js +1 -0
  102. package/src/wechat-preview/assets/browser-dist/assets/factor-kuTfRLto.js +1 -0
  103. package/src/wechat-preview/assets/browser-dist/assets/fcl-Kvtd6kyn.js +1 -0
  104. package/src/wechat-preview/assets/browser-dist/assets/forth-Ffai-XNe.js +1 -0
  105. package/src/wechat-preview/assets/browser-dist/assets/fortran-DYz_wnZ1.js +1 -0
  106. package/src/wechat-preview/assets/browser-dist/assets/gas-Bneqetm1.js +1 -0
  107. package/src/wechat-preview/assets/browser-dist/assets/gherkin-heZmZLOM.js +1 -0
  108. package/src/wechat-preview/assets/browser-dist/assets/groovy-D9Dt4D0W.js +1 -0
  109. package/src/wechat-preview/assets/browser-dist/assets/haskell-Cw1EW3IL.js +1 -0
  110. package/src/wechat-preview/assets/browser-dist/assets/haxe-H-WmDvRZ.js +1 -0
  111. package/src/wechat-preview/assets/browser-dist/assets/http-DBlCnlav.js +1 -0
  112. package/src/wechat-preview/assets/browser-dist/assets/idl-BEugSyMb.js +1 -0
  113. package/src/wechat-preview/assets/browser-dist/assets/index-B13lCSuc.js +1 -0
  114. package/src/wechat-preview/assets/browser-dist/assets/index-BDdU9pNh.js +1 -0
  115. package/src/wechat-preview/assets/browser-dist/assets/index-BFLGVgsp.js +1 -0
  116. package/src/wechat-preview/assets/browser-dist/assets/index-BHJY0B4E.js +8 -0
  117. package/src/wechat-preview/assets/browser-dist/assets/index-BOnKEJxh.js +7 -0
  118. package/src/wechat-preview/assets/browser-dist/assets/index-BPmBcGI-.js +1 -0
  119. package/src/wechat-preview/assets/browser-dist/assets/index-BTVIa90Y.js +1 -0
  120. package/src/wechat-preview/assets/browser-dist/assets/index-BYV6TA8f.js +1 -0
  121. package/src/wechat-preview/assets/browser-dist/assets/index-BlVpb0dF.js +1 -0
  122. package/src/wechat-preview/assets/browser-dist/assets/index-BqKARoyW.js +1 -0
  123. package/src/wechat-preview/assets/browser-dist/assets/index-BzHql5Rp.js +20 -0
  124. package/src/wechat-preview/assets/browser-dist/assets/index-CPFEJ9fX.js +1 -0
  125. package/src/wechat-preview/assets/browser-dist/assets/index-CdxKyTX6.js +1 -0
  126. package/src/wechat-preview/assets/browser-dist/assets/index-CyXDvcwY.js +1 -0
  127. package/src/wechat-preview/assets/browser-dist/assets/index-DA7Z4paG.js +13 -0
  128. package/src/wechat-preview/assets/browser-dist/assets/index-DDQGWU_D.js +3 -0
  129. package/src/wechat-preview/assets/browser-dist/assets/index-DENMoE5l.js +1 -0
  130. package/src/wechat-preview/assets/browser-dist/assets/index-D_EI5vFS.js +1 -0
  131. package/src/wechat-preview/assets/browser-dist/assets/index-DpuVrwze.js +1 -0
  132. package/src/wechat-preview/assets/browser-dist/assets/index-IBlhKHQr.js +8 -0
  133. package/src/wechat-preview/assets/browser-dist/assets/index-ifI5svkD.js +2 -0
  134. package/src/wechat-preview/assets/browser-dist/assets/index-vRiJxvj2.js +1 -0
  135. package/src/wechat-preview/assets/browser-dist/assets/javascript-iXu5QeM3.js +1 -0
  136. package/src/wechat-preview/assets/browser-dist/assets/julia-DuME0IfC.js +1 -0
  137. package/src/wechat-preview/assets/browser-dist/assets/livescript-BwQOo05w.js +1 -0
  138. package/src/wechat-preview/assets/browser-dist/assets/lua-VAEuO923.js +1 -0
  139. package/src/wechat-preview/assets/browser-dist/assets/mathematica-DTrFuWx2.js +1 -0
  140. package/src/wechat-preview/assets/browser-dist/assets/mbox-CNhZ1qSd.js +1 -0
  141. package/src/wechat-preview/assets/browser-dist/assets/mirc-CjQqDB4T.js +1 -0
  142. package/src/wechat-preview/assets/browser-dist/assets/mllike-CXdrOF99.js +1 -0
  143. package/src/wechat-preview/assets/browser-dist/assets/modelica-Dc1JOy9r.js +1 -0
  144. package/src/wechat-preview/assets/browser-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  145. package/src/wechat-preview/assets/browser-dist/assets/mumps-BT43cFF4.js +1 -0
  146. package/src/wechat-preview/assets/browser-dist/assets/nginx-DdIZxoE0.js +1 -0
  147. package/src/wechat-preview/assets/browser-dist/assets/nsis-LY0VT_u5.js +1 -0
  148. package/src/wechat-preview/assets/browser-dist/assets/ntriples-BfvgReVJ.js +1 -0
  149. package/src/wechat-preview/assets/browser-dist/assets/octave-Ck1zUtKM.js +1 -0
  150. package/src/wechat-preview/assets/browser-dist/assets/oz-BzwKVEFT.js +1 -0
  151. package/src/wechat-preview/assets/browser-dist/assets/pascal--L3eBynH.js +1 -0
  152. package/src/wechat-preview/assets/browser-dist/assets/perl-CdXCOZ3F.js +1 -0
  153. package/src/wechat-preview/assets/browser-dist/assets/pig-CevX1Tat.js +1 -0
  154. package/src/wechat-preview/assets/browser-dist/assets/powershell-CFHJl5sT.js +1 -0
  155. package/src/wechat-preview/assets/browser-dist/assets/properties-C78fOPTZ.js +1 -0
  156. package/src/wechat-preview/assets/browser-dist/assets/protobuf-ChK-085T.js +1 -0
  157. package/src/wechat-preview/assets/browser-dist/assets/pug-DeIclll2.js +1 -0
  158. package/src/wechat-preview/assets/browser-dist/assets/puppet-DMA9R1ak.js +1 -0
  159. package/src/wechat-preview/assets/browser-dist/assets/python-BuPzkPfP.js +1 -0
  160. package/src/wechat-preview/assets/browser-dist/assets/q-pXgVlZs6.js +1 -0
  161. package/src/wechat-preview/assets/browser-dist/assets/r-B6wPVr8A.js +1 -0
  162. package/src/wechat-preview/assets/browser-dist/assets/rpm-CTu-6PCP.js +1 -0
  163. package/src/wechat-preview/assets/browser-dist/assets/ruby-BK5aLJev.js +1 -0
  164. package/src/wechat-preview/assets/browser-dist/assets/sas-B4kiWyti.js +1 -0
  165. package/src/wechat-preview/assets/browser-dist/assets/scheme-C41bIUwD.js +1 -0
  166. package/src/wechat-preview/assets/browser-dist/assets/shell-CjFT_Tl9.js +1 -0
  167. package/src/wechat-preview/assets/browser-dist/assets/sieve-C3Gn_uJK.js +1 -0
  168. package/src/wechat-preview/assets/browser-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  169. package/src/wechat-preview/assets/browser-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  170. package/src/wechat-preview/assets/browser-dist/assets/solr-DehyRSwq.js +1 -0
  171. package/src/wechat-preview/assets/browser-dist/assets/sparql-DkYu6x3z.js +1 -0
  172. package/src/wechat-preview/assets/browser-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  173. package/src/wechat-preview/assets/browser-dist/assets/sql-D0XecflT.js +1 -0
  174. package/src/wechat-preview/assets/browser-dist/assets/stex-C3f8Ysf7.js +1 -0
  175. package/src/wechat-preview/assets/browser-dist/assets/style-Cif_gYUW.css +1 -0
  176. package/src/wechat-preview/assets/browser-dist/assets/stylus-B533Al4x.js +1 -0
  177. package/src/wechat-preview/assets/browser-dist/assets/swift-BzpIVaGY.js +1 -0
  178. package/src/wechat-preview/assets/browser-dist/assets/tcl-DVfN8rqt.js +1 -0
  179. package/src/wechat-preview/assets/browser-dist/assets/textile-CnDTJFAw.js +1 -0
  180. package/src/wechat-preview/assets/browser-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  181. package/src/wechat-preview/assets/browser-dist/assets/tiki-DGYXhP31.js +1 -0
  182. package/src/wechat-preview/assets/browser-dist/assets/toml-Bm5Em-hy.js +1 -0
  183. package/src/wechat-preview/assets/browser-dist/assets/troff-wAsdV37c.js +1 -0
  184. package/src/wechat-preview/assets/browser-dist/assets/ttcn-CfJYG6tj.js +1 -0
  185. package/src/wechat-preview/assets/browser-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  186. package/src/wechat-preview/assets/browser-dist/assets/turtle-B1tBg_DP.js +1 -0
  187. package/src/wechat-preview/assets/browser-dist/assets/vb-CmGdzxic.js +1 -0
  188. package/src/wechat-preview/assets/browser-dist/assets/vbscript-BuJXcnF6.js +1 -0
  189. package/src/wechat-preview/assets/browser-dist/assets/velocity-D8B20fx6.js +1 -0
  190. package/src/wechat-preview/assets/browser-dist/assets/verilog-C6RDOZhf.js +1 -0
  191. package/src/wechat-preview/assets/browser-dist/assets/vhdl-lSbBsy5d.js +1 -0
  192. package/src/wechat-preview/assets/browser-dist/assets/webidl-ZXfAyPTL.js +1 -0
  193. package/src/wechat-preview/assets/browser-dist/assets/{wechat-renderer-GgNrlSLi.js → wechat-renderer-CcHuq7M-.js} +83 -61
  194. package/src/wechat-preview/assets/browser-dist/assets/xquery-DzFWVndE.js +1 -0
  195. package/src/wechat-preview/assets/browser-dist/assets/yacas-BJ4BC0dw.js +1 -0
  196. package/src/wechat-preview/assets/browser-dist/assets/z80-Hz9HOZM7.js +1 -0
  197. package/src/wechat-preview/assets/browser-dist/editor-export.js +1 -1
  198. package/src/wechat-preview/assets/browser-dist/src/wechat-preview/browser/studio/studio.html +215 -165
  199. package/src/wechat-preview/assets/browser-dist/studio.js +1148 -237
  200. package/src/wechat-preview/browser/studio/color-palettes.ts +28 -0
  201. package/src/wechat-preview/browser/studio/config-manager.ts +49 -0
  202. package/src/wechat-preview/browser/studio/milkdown-article-menu.ts +251 -0
  203. package/src/wechat-preview/browser/studio/myno-editor.ts +122 -0
  204. package/src/wechat-preview/browser/studio/studio.css +493 -250
  205. package/src/wechat-preview/browser/studio/studio.html +213 -163
  206. package/src/wechat-preview/browser/studio/studio.ts +684 -211
  207. package/src/wechat-preview/browser/studio/style-packs.ts +201 -0
  208. package/src/wechat-preview/browser/studio/syntax-presets.test.ts +10 -32
  209. package/src/wechat-preview/browser/studio/syntax-presets.ts +19 -357
  210. package/src/wechat-preview/fixtures/all-nodes.md +2 -11
  211. package/src/wechat-preview/index.ts +35 -14
  212. package/src/wechat-preview/server/http.ts +64 -8
  213. package/src/wechat-preview/server/studio.test.ts +60 -1
  214. package/src/wechat-preview/wechat-export.integration.test.ts +49 -8
  215. package/src/wechat-preview/wechat-renderer.ts +134 -11
  216. package/src/workflow.test.ts +2 -0
  217. package/src/wechat-preview/assets/browser-dist/assets/style-C9-YtTIm.css +0 -1
@@ -29,6 +29,7 @@ export interface WechatRenderContext {
29
29
  theme: WechatExportTheme;
30
30
  references: WechatLinkReference[];
31
31
  structure?: ArticleStructure;
32
+ strictImageDecode?: boolean;
32
33
  }
33
34
 
34
35
  /** 模板仅声明有限的结构变化,不注入脚本或任意 HTML。 */
@@ -61,6 +62,7 @@ export interface RenderWechatHtmlInput {
61
62
  editorVars?: Record<string, string>;
62
63
  theme: WechatExportTheme;
63
64
  renderers?: WechatRendererOverrides;
65
+ strictImageDecode?: boolean;
64
66
  }
65
67
 
66
68
  const ALLOWED_TAGS = new Set([
@@ -131,6 +133,7 @@ const ALLOWED_STYLE_PROPERTIES = new Set([
131
133
  "text-decoration-style",
132
134
  "text-indent",
133
135
  "background",
136
+ "background-image",
134
137
  "background-color",
135
138
  "border",
136
139
  "border-top",
@@ -234,7 +237,8 @@ function imageIsOnlyContent(parent: HTMLElement, image: HTMLElement): boolean {
234
237
  });
235
238
  }
236
239
 
237
- // 图片缩放与图注共用导出流程,分别读取 alt 和 title。
240
+ // 图片缩放与图注共用导出流程:Markdown 往返读两位小数 alt 与 title;
241
+ // Crepe 块级图片 DOM 读 ratio 与 caption 属性。
238
242
  const imageRenderer: WechatElementRenderer = {
239
243
  kind: "image",
240
244
  selector: "img",
@@ -245,35 +249,66 @@ const imageRenderer: WechatElementRenderer = {
245
249
  if (src) element.setAttribute("data-src", src);
246
250
 
247
251
  const parent = element.parentElement;
248
- if (!parent || parent.tagName.toLowerCase() !== "p" || !imageIsOnlyContent(parent, element)) {
252
+ const inOwnParagraph =
253
+ !!parent && parent.tagName.toLowerCase() === "p" && imageIsOnlyContent(parent, element);
254
+ // Crepe 块级图片是文章根的顶层裸 <img>,同样视为独立图片。
255
+ const topLevelBlock = parent === context.root;
256
+ if (!parent || (!inOwnParagraph && !topLevelBlock)) {
249
257
  return;
250
258
  }
251
259
 
252
- // Crepe 仅将独立图片的缩放比例序列化为两位小数。
260
+ const ratioRaw = element.getAttribute("ratio");
261
+ const ratioFromAttr = ratioRaw ? Number(ratioRaw) : NaN;
253
262
  const alt = element.getAttribute("alt") ?? "";
254
- const ratio = /^(?:0|[1-9]\d*)\.\d{2}$/.test(alt) ? Number(alt) : 0;
263
+ const ratio =
264
+ Number.isFinite(ratioFromAttr) && ratioFromAttr > 0
265
+ ? ratioFromAttr
266
+ : /^(?:0|[1-9]\d*)\.\d{2}$/.test(alt) ? Number(alt) : 0;
255
267
  if (ratio > 0 && Number.isFinite(ratio)) {
256
268
  // DOMParser 文档不加载图片,用浏览器图片对象读取原始尺寸。
257
269
  const sourceImage = new Image();
258
- sourceImage.src = src ?? "";
270
+ let imageSrc = src ?? "";
271
+ if (
272
+ typeof window !== "undefined" &&
273
+ window.location &&
274
+ window.location.origin &&
275
+ window.location.origin.startsWith("http") &&
276
+ imageSrc &&
277
+ !/^(?:https?:|data:|blob:|\/\/)/i.test(imageSrc)
278
+ ) {
279
+ if (imageSrc.startsWith("/") && !imageSrc.startsWith("/local-file")) {
280
+ imageSrc = `/local-file?path=${encodeURIComponent(imageSrc)}`;
281
+ }
282
+ }
283
+ sourceImage.src = imageSrc;
259
284
  try {
260
285
  await sourceImage.decode();
261
286
  if (!sourceImage.naturalWidth) throw new Error("图片原始宽度为空");
287
+ element.style.width = `${Math.min(ratio, 1) * 100}%`;
288
+ element.style.maxWidth = `${sourceImage.naturalWidth * ratio}px`;
289
+ element.style.height = "auto";
262
290
  } catch (error) {
263
- throw new Error(`无法读取缩放图片的原始尺寸:${element.getAttribute("title") || "无图注"}`, { cause: error });
291
+ if (context.strictImageDecode ?? true) {
292
+ throw new Error(`无法读取缩放图片的原始尺寸:${element.getAttribute("title") || "无图注"}`, { cause: error });
293
+ }
294
+ // 非严格模式(如 Studio 可视化编辑预览)降级处理,保证编辑器正常渲染
295
+ element.style.width = `${Math.min(ratio, 1) * 100}%`;
296
+ element.style.height = "auto";
264
297
  }
265
- element.style.width = `${Math.min(ratio, 1) * 100}%`;
266
- element.style.maxWidth = `${sourceImage.naturalWidth * ratio}px`;
267
- element.style.height = "auto";
268
298
  }
269
299
 
270
300
  const figure = context.document.createElement("figure");
271
301
  setNodeKind(figure, "image-block");
272
- parent.replaceWith(figure);
302
+ if (inOwnParagraph) {
303
+ parent.replaceWith(figure);
304
+ } else {
305
+ element.replaceWith(figure);
306
+ }
273
307
  figure.appendChild(element);
274
308
 
275
- const caption = element.getAttribute("title")?.trim();
309
+ const caption = (element.getAttribute("caption") || element.getAttribute("title") || "").trim();
276
310
  element.removeAttribute("title");
311
+ element.removeAttribute("caption");
277
312
  if (caption) {
278
313
  const figcaption = context.document.createElement("figcaption");
279
314
  setNodeKind(figcaption, "image-caption");
@@ -382,6 +417,10 @@ const blockquoteRenderer: WechatElementRenderer = {
382
417
  element.prepend(label);
383
418
  }
384
419
  },
420
+ finalize(element) {
421
+ // 微信编辑器会给原生 blockquote 强制加灰色左边框,导出时降级为 section,外观全靠内联样式。
422
+ replaceTag(element, "section");
423
+ },
385
424
  };
386
425
 
387
426
  /** Normalize URL for reference dedupe (same target → one footnote entry). */
@@ -631,6 +670,33 @@ export function buildWechatThemeCss(
631
670
  .milkdown .editor p {
632
671
  ${theme.bodyFontSize ? `font-size: ${theme.bodyFontSize};` : ""}
633
672
  ${theme.bodyParagraphSpacing ? `margin-bottom: ${theme.bodyParagraphSpacing};` : ""}
673
+ ${theme.bodyLineHeight ? `line-height: ${theme.bodyLineHeight};` : ""}
674
+ ${theme.bodyLetterSpacing ? `letter-spacing: ${theme.bodyLetterSpacing};` : ""}
675
+ ${theme.bodyColor ? `color: ${theme.bodyColor};` : ""}
676
+ }
677
+ `;
678
+ }
679
+
680
+ /**
681
+ * 结构装饰(编号 / 章节标签 / 引用标签)在导出时是真实节点,编辑器里是 ::before。
682
+ * 此处样式与 studio 编辑器的 ::before 视觉对齐,否则预览中退化为普通正文。
683
+ */
684
+ function buildStructureDecorationCss(structure?: ArticleStructure): string {
685
+ if (!structure?.numberedHeadings && !structure?.headingLabel && !structure?.quoteLabel) return "";
686
+ return `
687
+ .milkdown .editor [data-wechat-node="heading-number"],
688
+ .milkdown .editor [data-wechat-node="heading-label"] {
689
+ margin-right: 0.3em;
690
+ opacity: 0.7;
691
+ font-weight: 600;
692
+ }
693
+ .milkdown .editor [data-wechat-node="quote-label"] {
694
+ margin: 0 0 6px;
695
+ font-size: 11px;
696
+ font-weight: 600;
697
+ letter-spacing: 0.05em;
698
+ color: var(--wx-brand-ink, var(--wx-brand-accent, #ca6093));
699
+ text-transform: uppercase;
634
700
  }
635
701
  `;
636
702
  }
@@ -639,6 +705,59 @@ function parseHtml(html: string): Document {
639
705
  return new DOMParser().parseFromString(`<!doctype html><html><body>${html}</body></html>`, "text/html");
640
706
  }
641
707
 
708
+ const BORDER_SIDES = ["top", "right", "bottom", "left"] as const;
709
+
710
+ /**
711
+ * 浏览器把 `border: none; border-left: X` 或 `border-left: none` 序列化成多值简写或
712
+ * none 边的长属性,其中 border-color 是 currentcolor——公众号编辑器解析不了,整条
713
+ * 声明作废后边框退回正文灰色。展开为可见边的单值属性,丢弃 none/hidden 边。
714
+ */
715
+ function normalizeBorderShorthands(element: HTMLElement): void {
716
+ const style = element.style;
717
+ const sides = BORDER_SIDES.map((side) => ({
718
+ side,
719
+ width: style.getPropertyValue(`border-${side}-width`),
720
+ style: style.getPropertyValue(`border-${side}-style`),
721
+ color: style.getPropertyValue(`border-${side}-color`),
722
+ }));
723
+
724
+ const invisible = (entry: (typeof sides)[number]) => entry.style === "none" || entry.style === "hidden";
725
+ const hasShorthand =
726
+ !!style.getPropertyValue("border-style") ||
727
+ !!style.getPropertyValue("border-width") ||
728
+ !!style.getPropertyValue("border-color");
729
+ if (!hasShorthand && !sides.some(invisible)) return;
730
+
731
+ if (!hasShorthand) {
732
+ // 单边 none/hidden 长属性:只删除,其余保持原样
733
+ for (const { side, style: borderStyle } of sides) {
734
+ if (borderStyle !== "none" && borderStyle !== "hidden") continue;
735
+ style.removeProperty(`border-${side}`);
736
+ style.removeProperty(`border-${side}-width`);
737
+ style.removeProperty(`border-${side}-style`);
738
+ style.removeProperty(`border-${side}-color`);
739
+ }
740
+ return;
741
+ }
742
+
743
+ const removable = ["border", "border-width", "border-style", "border-color"];
744
+ for (const { side } of sides) {
745
+ removable.push(`border-${side}`, `border-${side}-width`, `border-${side}-style`, `border-${side}-color`);
746
+ }
747
+ for (const property of removable) style.removeProperty(property);
748
+
749
+ for (const entry of sides) {
750
+ if (!entry.style || invisible(entry)) continue;
751
+ if (entry.width && entry.color) {
752
+ style.setProperty(`border-${entry.side}`, `${entry.width} ${entry.style} ${entry.color}`);
753
+ } else {
754
+ if (entry.width) style.setProperty(`border-${entry.side}-width`, entry.width);
755
+ style.setProperty(`border-${entry.side}-style`, entry.style);
756
+ if (entry.color) style.setProperty(`border-${entry.side}-color`, entry.color);
757
+ }
758
+ }
759
+ }
760
+
642
761
  function sanitizeStyle(element: HTMLElement): void {
643
762
  const propertyNames: string[] = [];
644
763
  for (let index = 0; index < element.style.length; index += 1) {
@@ -711,6 +830,7 @@ function sanitizeTree(root: HTMLElement): void {
711
830
  sanitizeImageUrl(element, "data-src");
712
831
  }
713
832
  sanitizeStyle(element);
833
+ normalizeBorderShorthands(element);
714
834
  }
715
835
  }
716
836
 
@@ -753,11 +873,13 @@ export async function renderWechatHtml(input: RenderWechatHtmlInput): Promise<st
753
873
  theme: input.theme,
754
874
  references,
755
875
  structure: input.structure,
876
+ strictImageDecode: input.strictImageDecode ?? true,
756
877
  });
757
878
 
758
879
  const css = [
759
880
  input.baseCss,
760
881
  buildWechatThemeCss(input.editorVars ?? {}, input.theme),
882
+ buildStructureDecorationCss(input.structure),
761
883
  pluginCss,
762
884
  input.customCss ?? "",
763
885
  ].filter(Boolean).join("\n");
@@ -788,6 +910,7 @@ export async function renderWechatHtml(input: RenderWechatHtmlInput): Promise<st
788
910
  theme: input.theme,
789
911
  references,
790
912
  structure: input.structure,
913
+ strictImageDecode: input.strictImageDecode ?? true,
791
914
  });
792
915
  sanitizeTree(finalRoot);
793
916
  return finalRoot.outerHTML;
@@ -150,12 +150,14 @@ describe("init", () => {
150
150
  "--content-form", "article",
151
151
  "--targets", "wechat@ancientone",
152
152
  "--content-origin", "user",
153
+ "--article-theme", "./styles/standalone.json",
153
154
  ]));
154
155
  const state = await readState(output.state_path);
155
156
  expect(state.publish_targets).toEqual([
156
157
  { route: "wechat-article", account: "ancientone" },
157
158
  ]);
158
159
  expect(state.route.primary).toBe("wechat-article");
160
+ expect(state.intent.article_theme).toBe(join(process.cwd(), "styles/standalone.json"));
159
161
  });
160
162
 
161
163
  test("normalizes underscore flags and resolves route/account from intent text", async () => {
@@ -1 +0,0 @@
1
- :root{--bg-main: #f8fafc;--bg-panel: #ffffff;--border: #e2e8f0;--border-focus: #ca6093;--text-main: #1e293b;--text-muted: #64748b;--brand: #ca6093;--brand-hover: #b35082;--brand-light: rgba(202, 96, 147, .08);--success: #10b981;--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;--font-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font-sans);color:var(--text-main);background:var(--bg-main);height:100vh;display:flex;flex-direction:column;overflow:hidden}.studio-header{height:52px;background:var(--bg-panel);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 16px;z-index:10}.brand-section{display:flex;align-items:center;gap:10px}.brand-logo{font-size:18px}.brand-title{font-size:15px;font-weight:700;color:var(--text-main);letter-spacing:-.01em}.brand-badge{font-size:11px;font-weight:600;background:var(--brand-light);color:var(--brand);padding:2px 8px;border-radius:999px}.header-controls{display:flex;align-items:center;gap:12px}.account-select-wrap{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--text-muted)}.account-select{padding:5px 10px;font-size:13px;border-radius:6px;border:1px solid var(--border);background:#fff;color:var(--text-main);outline:none;cursor:pointer}.account-select:focus{border-color:var(--border-focus)}.btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:#fff;color:var(--text-main);transition:all .15s ease;text-decoration:none}.btn:hover{background:#f1f5f9}.btn-primary{background:var(--brand);color:#fff;border-color:var(--brand)}.btn-primary:hover{background:var(--brand-hover);border-color:var(--brand-hover)}.btn-success{background:#059669;color:#fff;border-color:#059669}.btn-success:hover{background:#047857;border-color:#047857}.studio-body{flex:1;display:flex;min-height:0;overflow:hidden}.panel{display:flex;flex-direction:column;background:var(--bg-panel);min-height:0}.panel-header{padding:10px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:#fafbfc}.panel-title{font-size:13px;font-weight:650;color:var(--text-main);display:flex;align-items:center;gap:6px}.left-panel{width:360px;border-right:1px solid var(--border);flex-shrink:0}.tab-nav{display:flex;border-bottom:1px solid var(--border);background:#f8fafc}.tab-btn{flex:1;padding:10px 12px;font-size:13px;font-weight:600;border:none;background:transparent;color:var(--text-muted);cursor:pointer;border-bottom:2px solid transparent;transition:all .15s}.tab-btn:hover{color:var(--text-main)}.tab-btn.active{color:var(--brand);background:#fff;border-bottom-color:var(--brand)}.tab-content{flex:1;overflow-y:auto;padding:16px}.config-section{margin-bottom:22px}.section-label{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:12px;display:flex;align-items:center;gap:6px}.form-group{margin-bottom:14px}.form-label{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:var(--text-main);margin-bottom:6px}.form-val{font-family:var(--font-mono);font-size:11px;color:var(--text-muted)}.slider-input{width:100%;accent-color:var(--brand);cursor:pointer}.color-input-row{display:flex;align-items:center;gap:8px}.color-picker{-webkit-appearance:none;appearance:none;border:1px solid var(--border);border-radius:6px;width:32px;height:32px;cursor:pointer;padding:2px;background:#fff}.color-picker::-webkit-color-swatch-wrapper{padding:0}.color-picker::-webkit-color-swatch{border:none;border-radius:4px}.color-hex{flex:1;padding:6px 10px;font-family:var(--font-mono);font-size:12px;border:1px solid var(--border);border-radius:6px;color:var(--text-main)}.switch-label{display:flex;align-items:center;justify-content:space-between;cursor:pointer;font-size:12px}.switch-toggle{position:relative;display:inline-block;width:38px;height:20px}.switch-toggle input{opacity:0;width:0;height:0}.slider-round{position:absolute;cursor:pointer;inset:0;background-color:#cbd5e1;transition:.2s;border-radius:20px}.slider-round:before{position:absolute;content:"";height:14px;width:14px;left:3px;bottom:3px;background-color:#fff;transition:.2s;border-radius:50%}input:checked+.slider-round{background-color:var(--brand)}input:checked+.slider-round:before{transform:translate(18px)}.css-editor{width:100%;height:140px;font-family:var(--font-mono);font-size:12px;line-height:1.5;padding:8px 10px;border:1px solid var(--border);border-radius:6px;background:#fafbfc;color:#0f172a;resize:vertical;outline:none}.css-editor:focus{border-color:var(--border-focus);background:#fff}.syntax-card{border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:12px;background:#fff;transition:border-color .15s}.syntax-card:hover{border-color:#cbd5e1}.syntax-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.syntax-card-title{font-size:13px;font-weight:650;color:var(--text-main)}.syntax-card-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:#f1f5f9;color:var(--text-muted)}.syntax-card-desc{font-size:12px;color:var(--text-muted);margin-bottom:8px;line-height:1.4}.syntax-code-snippet{background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;padding:8px 10px;font-family:var(--font-mono);font-size:11px;color:#334155;white-space:pre-wrap;margin-bottom:8px}.syntax-btn-insert{width:100%;padding:4px 8px;font-size:11px;font-weight:600;border:1px dashed var(--border);border-radius:4px;background:#fafbfc;color:var(--brand);cursor:pointer}.syntax-btn-insert:hover{background:var(--brand-light);border-color:var(--brand)}.editor-panel{flex:1;border-right:1px solid var(--border);min-width:320px}.editor-toolbar{display:flex;gap:8px;align-items:center}.editor-textarea{flex:1;width:100%;border:none;outline:none;resize:none;padding:16px 20px;font-family:var(--font-mono);font-size:13px;line-height:1.7;color:#1e293b;background:#fff}.editor-footer{padding:6px 16px;border-top:1px solid var(--border);background:#fafbfc;font-size:11px;color:var(--text-muted);display:flex;justify-content:space-between}.preview-panel{width:480px;flex-shrink:0;background:#f1f5f9}.preview-container{flex:1;overflow-y:auto;display:flex;flex-direction:column;align-items:center;padding:24px 16px 48px}.device-frame{width:430px;min-height:680px;background:#fff;border-radius:18px;box-shadow:0 10px 30px #00000014,0 1px 3px #0000000d;overflow:hidden;display:flex;flex-direction:column;border:1px solid #e2e8f0}.device-notch-bar{height:28px;background:#fafbfc;border-bottom:1px solid #f1f5f9;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-size:11px;font-weight:600;color:#64748b}.device-screen{flex:1;padding:20px 16px 36px;box-sizing:border-box;overflow-y:auto}.toast-container{position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}.toast{background:#1e293b;color:#fff;padding:10px 16px;border-radius:8px;font-size:13px;font-weight:500;box-shadow:0 6px 18px #00000026;opacity:0;transform:translateY(10px);transition:all .25s ease;pointer-events:auto}.toast.show{opacity:1;transform:translateY(0)}.toast.toast-success{background:#065f46;border:1px solid #10b981}.preset-group{margin-bottom:10px;padding:10px 12px;background:#f8fafc;border:1px solid var(--border);border-radius:var(--radius-sm);transition:border-color .15s}.preset-group:hover{border-color:#cbd5e1}.preset-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.preset-title{font-size:12px;font-weight:650;color:var(--text-main);display:flex;align-items:center;gap:5px}.preset-select{width:100%;padding:5px 8px;font-size:12px;font-weight:500;border:1px solid var(--border);border-radius:4px;background:#fff;color:var(--text-main);outline:none;cursor:pointer;transition:border-color .15s}.preset-select:focus{border-color:var(--brand)}.preset-desc{font-size:11px;color:var(--text-muted);margin-top:5px;line-height:1.4;padding-left:2px}.btn-extract-css{padding:2px 7px;font-size:11px;font-weight:550;color:var(--brand);background:#fff;border:1px solid var(--border);border-radius:4px;cursor:pointer;transition:all .15s ease;white-space:nowrap}.btn-extract-css:hover{background:#f1f5f9;border-color:var(--brand)}#studio-editor-host{display:none}