@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.
- package/ARTICLE-THEMES.md +2 -0
- package/README.md +27 -4
- package/package.json +7 -5
- package/src/adapter-loader.ts +24 -4
- package/src/adapter-types.ts +14 -0
- package/src/article-theme.test.ts +55 -0
- package/src/article-theme.ts +10 -2
- package/src/commands/article-theme.ts +1 -1
- package/src/commands/init.ts +2 -0
- package/src/commands/publish.ts +1 -1
- package/src/commands/typeset-assets-sync.test.ts +12 -1
- package/src/commands/wechat-export.ts +9 -4
- package/src/plugin-runner.ts +10 -4
- package/src/plugin-runtime.test.ts +19 -0
- package/src/plugin-runtime.ts +11 -2
- package/src/providers/index.ts +4 -2
- package/src/runtime-package.ts +13 -6
- package/src/wechat-preview/assets/browser-dist/.vite/manifest.json +1280 -5
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/apl-B4CMkyY2.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/asn1-EdZsLKOL.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/asterisk-B-8jnY81.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/clike-B9uivgTg.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/clojure-BMjYHr_A.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/cmake-BQqOBYOt.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/cobol-CWcv1MsR.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/crystal-SjHAIU92.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/css-BnMrqG3P.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/d-pRatUO7H.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/diff-DbItnlRl.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/dtd-DF_7sFjM.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/dylan-DwRh75JA.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/ecl-Cabwm37j.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/eiffel-CnydiIhH.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/elm-vLlmbW-K.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/erlang-BNw1qcRV.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/factor-kuTfRLto.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/forth-Ffai-XNe.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/gas-Bneqetm1.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/gherkin-heZmZLOM.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/http-DBlCnlav.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/idl-BEugSyMb.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-B13lCSuc.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BDdU9pNh.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BFLGVgsp.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BHJY0B4E.js +8 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BOnKEJxh.js +7 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BPmBcGI-.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BTVIa90Y.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BYV6TA8f.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BlVpb0dF.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BqKARoyW.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-BzHql5Rp.js +20 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-CPFEJ9fX.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-CdxKyTX6.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-CyXDvcwY.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-DA7Z4paG.js +13 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-DDQGWU_D.js +3 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-DENMoE5l.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-D_EI5vFS.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-DpuVrwze.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-IBlhKHQr.js +8 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-ifI5svkD.js +2 -0
- package/src/wechat-preview/assets/browser-dist/assets/index-vRiJxvj2.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/javascript-iXu5QeM3.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/julia-DuME0IfC.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/livescript-BwQOo05w.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/lua-VAEuO923.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/mirc-CjQqDB4T.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/mllike-CXdrOF99.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/mumps-BT43cFF4.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/nginx-DdIZxoE0.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/nsis-LY0VT_u5.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/octave-Ck1zUtKM.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/oz-BzwKVEFT.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/pascal--L3eBynH.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/perl-CdXCOZ3F.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/pig-CevX1Tat.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/powershell-CFHJl5sT.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/properties-C78fOPTZ.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/protobuf-ChK-085T.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/pug-DeIclll2.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/puppet-DMA9R1ak.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/python-BuPzkPfP.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/q-pXgVlZs6.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/r-B6wPVr8A.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/rpm-CTu-6PCP.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/ruby-BK5aLJev.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/sas-B4kiWyti.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/scheme-C41bIUwD.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/shell-CjFT_Tl9.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/solr-DehyRSwq.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/sparql-DkYu6x3z.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/sql-D0XecflT.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/stex-C3f8Ysf7.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/style-Cif_gYUW.css +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/stylus-B533Al4x.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/swift-BzpIVaGY.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/tcl-DVfN8rqt.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/textile-CnDTJFAw.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/tiki-DGYXhP31.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/toml-Bm5Em-hy.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/troff-wAsdV37c.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/turtle-B1tBg_DP.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/vb-CmGdzxic.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/velocity-D8B20fx6.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/verilog-C6RDOZhf.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/{wechat-renderer-GgNrlSLi.js → wechat-renderer-CcHuq7M-.js} +83 -61
- package/src/wechat-preview/assets/browser-dist/assets/xquery-DzFWVndE.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/src/wechat-preview/assets/browser-dist/assets/z80-Hz9HOZM7.js +1 -0
- package/src/wechat-preview/assets/browser-dist/editor-export.js +1 -1
- package/src/wechat-preview/assets/browser-dist/src/wechat-preview/browser/studio/studio.html +215 -165
- package/src/wechat-preview/assets/browser-dist/studio.js +1148 -237
- package/src/wechat-preview/browser/studio/color-palettes.ts +28 -0
- package/src/wechat-preview/browser/studio/config-manager.ts +49 -0
- package/src/wechat-preview/browser/studio/milkdown-article-menu.ts +251 -0
- package/src/wechat-preview/browser/studio/myno-editor.ts +122 -0
- package/src/wechat-preview/browser/studio/studio.css +493 -250
- package/src/wechat-preview/browser/studio/studio.html +213 -163
- package/src/wechat-preview/browser/studio/studio.ts +684 -211
- package/src/wechat-preview/browser/studio/style-packs.ts +201 -0
- package/src/wechat-preview/browser/studio/syntax-presets.test.ts +10 -32
- package/src/wechat-preview/browser/studio/syntax-presets.ts +19 -357
- package/src/wechat-preview/fixtures/all-nodes.md +2 -11
- package/src/wechat-preview/index.ts +35 -14
- package/src/wechat-preview/server/http.ts +64 -8
- package/src/wechat-preview/server/studio.test.ts +60 -1
- package/src/wechat-preview/wechat-export.integration.test.ts +49 -8
- package/src/wechat-preview/wechat-renderer.ts +134 -11
- package/src/workflow.test.ts +2 -0
- package/src/wechat-preview/assets/browser-dist/assets/style-C9-YtTIm.css +0 -1
|
@@ -25,6 +25,7 @@ import type { WechatThemeOverrides } from "../schema/config";
|
|
|
25
25
|
import { getWechatPreviewStyleName, getWechatPreviewTheme } from "./themes";
|
|
26
26
|
import { stripLeadingH1 } from "../text";
|
|
27
27
|
import { buildCombinedPresetsCss } from "./browser/studio/syntax-presets";
|
|
28
|
+
import type { ArticleStructure } from "./wechat-renderer";
|
|
28
29
|
|
|
29
30
|
interface ViteManifestEntry {
|
|
30
31
|
file: string;
|
|
@@ -99,6 +100,8 @@ export interface ExportMarkdownToWechatHtmlInput {
|
|
|
99
100
|
timeoutMs?: number;
|
|
100
101
|
/** Write intermediate artifacts for debugging. */
|
|
101
102
|
debugDir?: string;
|
|
103
|
+
/** 外部插件提供的已构建入口;浏览器和文件输出继续由宿主管理。 */
|
|
104
|
+
browserScriptPath?: string;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
export interface ExportMarkdownToWechatHtmlResult {
|
|
@@ -131,6 +134,10 @@ export function getWechatPreviewBundleSources(): string[] {
|
|
|
131
134
|
join(WECHAT_PREVIEW_DIR, "browser/studio/studio.css"),
|
|
132
135
|
join(WECHAT_PREVIEW_DIR, "browser/studio/syntax-presets.ts"),
|
|
133
136
|
join(WECHAT_PREVIEW_DIR, "browser/studio/color-palettes.ts"),
|
|
137
|
+
join(WECHAT_PREVIEW_DIR, "browser/studio/style-packs.ts"),
|
|
138
|
+
join(WECHAT_PREVIEW_DIR, "browser/studio/milkdown-article-menu.ts"),
|
|
139
|
+
join(WECHAT_PREVIEW_DIR, "browser/studio/myno-editor.ts"),
|
|
140
|
+
join(WECHAT_PREVIEW_DIR, "browser/studio/config-manager.ts"),
|
|
134
141
|
join(WECHAT_PREVIEW_DIR, "themes.ts"),
|
|
135
142
|
join(WECHAT_PREVIEW_DIR, "frontmatter-handler.ts"),
|
|
136
143
|
join(PACKAGE_ROOT, "src/text.ts"),
|
|
@@ -140,24 +147,27 @@ export function getWechatPreviewBundleSources(): string[] {
|
|
|
140
147
|
];
|
|
141
148
|
}
|
|
142
149
|
|
|
143
|
-
/**
|
|
150
|
+
/** 独立配置跨账号复用;目录模板仍沿用账号覆盖顺序。 */
|
|
144
151
|
export function resolveWechatArticleTheme(
|
|
145
152
|
account: string,
|
|
146
153
|
overrides?: Partial<WechatThemeOverrides>,
|
|
147
154
|
articleTheme?: ArticleThemePackage | null,
|
|
148
155
|
) {
|
|
156
|
+
const standalone = articleTheme?.manifest.css !== undefined;
|
|
157
|
+
const accountOverrides = standalone ? undefined : overrides;
|
|
149
158
|
const { headerImage, ...templateStructure } = articleTheme?.manifest.structure ?? {};
|
|
150
|
-
const theme = getWechatPreviewTheme(account, articleTheme ? {
|
|
151
|
-
editorVars: { ...articleTheme.manifest.editorVars, ...
|
|
152
|
-
exportTheme: { footerText: "", ...articleTheme.manifest.exportTheme, ...
|
|
153
|
-
} :
|
|
154
|
-
const structure = articleTheme ? {
|
|
159
|
+
const theme = getWechatPreviewTheme(standalone ? "default" : account, articleTheme ? {
|
|
160
|
+
editorVars: { ...articleTheme.manifest.editorVars, ...accountOverrides?.editorVars },
|
|
161
|
+
exportTheme: { footerText: "", ...articleTheme.manifest.exportTheme, ...accountOverrides?.exportTheme },
|
|
162
|
+
} : accountOverrides);
|
|
163
|
+
const structure: ArticleStructure | undefined = articleTheme ? {
|
|
155
164
|
...templateStructure,
|
|
156
|
-
...
|
|
165
|
+
...accountOverrides?.structure,
|
|
157
166
|
headerImageUrl: headerImage
|
|
158
|
-
? `data:image/${headerImage.split(".").pop()!.toLowerCase().replace("jpg", "jpeg")};base64,${Buffer.from(articleTheme.files.get(headerImage)!).toString("base64")}` : null,
|
|
159
|
-
} :
|
|
160
|
-
|
|
167
|
+
? `data:image/${headerImage.split(".").pop()!.toLowerCase().replace("jpg", "jpeg")};base64,${Buffer.from(articleTheme.files.get(headerImage)!).toString("base64")}` : articleTheme.manifest.structure.headerImageUrl ?? null,
|
|
168
|
+
} : accountOverrides?.structure;
|
|
169
|
+
const syntaxPresets = accountOverrides?.syntaxPresets ?? articleTheme?.manifest.syntaxPresets;
|
|
170
|
+
return { theme, structure, syntaxPresets };
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
export function isWechatPreviewBundleStale(
|
|
@@ -264,6 +274,14 @@ export function rewriteRelativeImagePaths(markdown: string, baseDir: string): st
|
|
|
264
274
|
}
|
|
265
275
|
return ``;
|
|
266
276
|
})
|
|
277
|
+
.replace(/^([ \t]*\[[^\]]+\]:\s*)(\S+)(.*)$/gm, (match, prefix, rawUrl, suffix) => {
|
|
278
|
+
if (isExternalUrl(rawUrl)) return match;
|
|
279
|
+
const cleanUrl = rawUrl.replace(/^<([^>]+)>$/, "$1");
|
|
280
|
+
const hadAngle = rawUrl.startsWith("<") && rawUrl.endsWith(">");
|
|
281
|
+
const resolved = resolveMarkdownAsset(cleanUrl, baseDir);
|
|
282
|
+
const needsBrackets = resolved ? resolved.includes(" ") : hadAngle;
|
|
283
|
+
return `${prefix}${needsBrackets ? "<" : ""}${resolved}${needsBrackets ? ">" : ""}${suffix}`;
|
|
284
|
+
})
|
|
267
285
|
.replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'][^>]*>)/gi, (_match, prefix, src, suffix) => {
|
|
268
286
|
const resolved = resolveMarkdownAsset(String(src ?? ""), baseDir);
|
|
269
287
|
return `${prefix}${resolved}${suffix}`;
|
|
@@ -389,7 +407,9 @@ export async function exportMarkdownToWechatHtml(
|
|
|
389
407
|
|
|
390
408
|
let bundle: BundleAssets;
|
|
391
409
|
try {
|
|
392
|
-
bundle =
|
|
410
|
+
bundle = input.browserScriptPath
|
|
411
|
+
? { scriptPath: input.browserScriptPath, rebuilt: false, stale: false }
|
|
412
|
+
: ensureBundle();
|
|
393
413
|
bundleRebuilt = bundle.rebuilt;
|
|
394
414
|
bundleStale = bundle.stale;
|
|
395
415
|
} catch (error) {
|
|
@@ -404,9 +424,9 @@ export async function exportMarkdownToWechatHtml(
|
|
|
404
424
|
const bodyMarkdown = stripLeadingH1(stripped).trimStart();
|
|
405
425
|
const markdown = bodyMarkdown;
|
|
406
426
|
const articleTheme = input.articleThemePath ? await readArticleTheme(input.articleThemePath) : null;
|
|
407
|
-
const { theme, structure } = resolveWechatArticleTheme(input.account, input.themeOverrides, articleTheme);
|
|
408
|
-
const presetsCss =
|
|
409
|
-
? buildCombinedPresetsCss(
|
|
427
|
+
const { theme, structure, syntaxPresets } = resolveWechatArticleTheme(input.account, input.themeOverrides, articleTheme);
|
|
428
|
+
const presetsCss = syntaxPresets
|
|
429
|
+
? buildCombinedPresetsCss(syntaxPresets)
|
|
410
430
|
: "";
|
|
411
431
|
const customCss = [presetsCss, articleTheme?.css, input.customCss ? await readFile(input.customCss, "utf-8") : ""].filter(Boolean).join("\n\n");
|
|
412
432
|
const shell = readUtf8(TEMPLATE_PATH);
|
|
@@ -416,6 +436,7 @@ export async function exportMarkdownToWechatHtml(
|
|
|
416
436
|
assetBaseUrl: pathToFileURL(`${dirname(resolve(input.markdownPath))}/`).href,
|
|
417
437
|
editorVars: theme.editorVars,
|
|
418
438
|
exportTheme: theme.exportTheme,
|
|
439
|
+
syntaxPresets,
|
|
419
440
|
customCss,
|
|
420
441
|
structure,
|
|
421
442
|
}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import {
|
|
2
|
+
import { realpath, stat } from "fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import {
|
|
5
6
|
previewBaseUrl,
|
|
@@ -26,12 +27,14 @@ import {
|
|
|
26
27
|
rewriteHtmlLocalAssets,
|
|
27
28
|
} from "./local-file";
|
|
28
29
|
import { renderDashboardHtml, renderFailedEntryHtml } from "./ui";
|
|
29
|
-
import { buildWechatPreviewShell, resolveWechatArticleTheme } from "../index";
|
|
30
|
+
import { buildWechatPreviewShell, resolveWechatArticleTheme, rewriteRelativeImagePaths } from "../index";
|
|
30
31
|
import { getPluginDocs } from "@akairos/milkdown-article-plugins";
|
|
31
32
|
import { readArticleTheme } from "../../article-theme";
|
|
32
33
|
import { PipelineConfigSchema, WechatExportThemeOverridesSchema, WechatStructureOverridesSchema } from "../../schema/config";
|
|
33
34
|
import { loadConfig, saveConfig, getPipelineConfigPath, resolveConfigRelativePath } from "../../config";
|
|
34
35
|
import { DIST_DIR, WECHAT_PREVIEW_DIR } from "../../runtime-paths";
|
|
36
|
+
import { resolveMarkdownRenderer } from "../../adapter-loader";
|
|
37
|
+
import type { MarkdownStudioAssets } from "../../adapter-types";
|
|
35
38
|
import type { PreviewRegisterInput } from "./types";
|
|
36
39
|
|
|
37
40
|
/** 保存入口先校验完整请求,避免写入 CLI 无法加载的配置。 */
|
|
@@ -105,6 +108,32 @@ export async function startPreviewServer(
|
|
|
105
108
|
clearServerLock();
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
// 工作台与导出使用同一个插件;没有工作台能力时给出明确提示。
|
|
112
|
+
const pluginConfig = loadConfig();
|
|
113
|
+
const externalMarkdown = Boolean(pluginConfig.plugins.markdownRenderer);
|
|
114
|
+
let studioAssets: Promise<MarkdownStudioAssets | undefined> | undefined;
|
|
115
|
+
/** 按需加载工作台,故障插件不影响预览登记和历史页面。 */
|
|
116
|
+
function getStudio() {
|
|
117
|
+
return studioAssets ??= resolveMarkdownRenderer(pluginConfig).then(async renderer => {
|
|
118
|
+
const studio = await renderer.studio?.();
|
|
119
|
+
return studio ? { ...studio, root: await realpath(studio.root) } : undefined;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 插件静态资源独立挂载,真实路径也必须留在插件资源目录内。 */
|
|
124
|
+
async function studioFile(name: string): Promise<string | null> {
|
|
125
|
+
try {
|
|
126
|
+
if (!externalMarkdown) return null;
|
|
127
|
+
const studioRoot = (await getStudio())?.root;
|
|
128
|
+
if (!studioRoot) return null;
|
|
129
|
+
const target = await realpath(resolve(studioRoot, name));
|
|
130
|
+
const rel = relative(studioRoot, target);
|
|
131
|
+
return rel && !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`) && (await stat(target)).isFile() ? target : null;
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
108
137
|
let server: ReturnType<typeof Bun.serve>;
|
|
109
138
|
server = Bun.serve({
|
|
110
139
|
hostname: host,
|
|
@@ -210,8 +239,10 @@ export async function startPreviewServer(
|
|
|
210
239
|
const accountsData: Record<string, unknown> = {};
|
|
211
240
|
|
|
212
241
|
for (const [key, account] of Object.entries(config.wx.accounts)) {
|
|
242
|
+
const articleThemePath = resolveConfigRelativePath(account.articleTheme, configPath);
|
|
243
|
+
const articleTheme = articleThemePath ? await readArticleTheme(articleThemePath) : null;
|
|
213
244
|
let customCssContent = "";
|
|
214
|
-
if (account.customCss) {
|
|
245
|
+
if (account.customCss && articleTheme?.manifest.css === undefined) {
|
|
215
246
|
const resolved = resolveConfigRelativePath(account.customCss, configPath);
|
|
216
247
|
if (resolved) {
|
|
217
248
|
try {
|
|
@@ -222,9 +253,7 @@ export async function startPreviewServer(
|
|
|
222
253
|
}
|
|
223
254
|
}
|
|
224
255
|
|
|
225
|
-
const
|
|
226
|
-
const articleTheme = articleThemePath ? await readArticleTheme(articleThemePath) : null;
|
|
227
|
-
const { theme, structure } = resolveWechatArticleTheme(key, account.theme, articleTheme);
|
|
256
|
+
const { theme, structure, syntaxPresets } = resolveWechatArticleTheme(key, account.theme, articleTheme);
|
|
228
257
|
|
|
229
258
|
accountsData[key] = {
|
|
230
259
|
name: account.name || key,
|
|
@@ -233,11 +262,12 @@ export async function startPreviewServer(
|
|
|
233
262
|
theme: {
|
|
234
263
|
editorVars: theme.editorVars,
|
|
235
264
|
exportTheme: theme.exportTheme,
|
|
236
|
-
syntaxPresets:
|
|
265
|
+
syntaxPresets: syntaxPresets || {},
|
|
237
266
|
structure: structure || {},
|
|
238
267
|
},
|
|
239
268
|
articleThemeCss: articleTheme?.css || "",
|
|
240
269
|
articleTheme: account.articleTheme ?? null,
|
|
270
|
+
articleThemeStandalone: articleTheme?.manifest.css !== undefined,
|
|
241
271
|
};
|
|
242
272
|
}
|
|
243
273
|
|
|
@@ -245,7 +275,7 @@ export async function startPreviewServer(
|
|
|
245
275
|
const fixturePath = join(WECHAT_PREVIEW_DIR, "fixtures/all-nodes.md");
|
|
246
276
|
if (existsSync(fixturePath)) {
|
|
247
277
|
try {
|
|
248
|
-
sampleMarkdown = readFileSync(fixturePath, "utf-8");
|
|
278
|
+
sampleMarkdown = rewriteRelativeImagePaths(readFileSync(fixturePath, "utf-8"), dirname(fixturePath));
|
|
249
279
|
} catch {
|
|
250
280
|
// ignore
|
|
251
281
|
}
|
|
@@ -316,6 +346,21 @@ export async function startPreviewServer(
|
|
|
316
346
|
}
|
|
317
347
|
|
|
318
348
|
if (pathname === "/studio" || pathname === "/studio/" || pathname === "/studio/index.html") {
|
|
349
|
+
if (externalMarkdown) {
|
|
350
|
+
let studio: MarkdownStudioAssets | undefined;
|
|
351
|
+
try { studio = await getStudio(); }
|
|
352
|
+
catch (error) { return json({ error: error instanceof Error ? error.message : String(error) }, 500); }
|
|
353
|
+
if (!studio) return html("<h1>当前 Markdown 渲染插件未提供 Studio 工作台。</h1>", 404);
|
|
354
|
+
const entry = await studioFile(studio.entry);
|
|
355
|
+
if (!entry) return html("<h1>插件 Studio 入口不存在或超出资源目录。</h1>", 500);
|
|
356
|
+
// 相对资源按插件入口解析,统一放入命名空间且保留页面导航链接。
|
|
357
|
+
return html(readFileSync(entry, "utf8").replace(/<(?:script|link)\b[^>]*>/g,
|
|
358
|
+
tag => tag.replace(/\b(src|href)=(["'])([^"']+)\2/g, (attribute, name, quote, value: string) => {
|
|
359
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(value)) return attribute;
|
|
360
|
+
const asset = new URL(value, `http://studio/${studio.entry}`);
|
|
361
|
+
return `${name}=${quote}/studio-assets${asset.pathname}${asset.search}${quote}`;
|
|
362
|
+
})));
|
|
363
|
+
}
|
|
319
364
|
const studioHtmlPath = join(DIST_DIR, "src/wechat-preview/browser/studio/studio.html");
|
|
320
365
|
const fallbackPath = join(DIST_DIR, "studio.html");
|
|
321
366
|
const targetHtmlPath = existsSync(studioHtmlPath) ? studioHtmlPath : fallbackPath;
|
|
@@ -325,6 +370,17 @@ export async function startPreviewServer(
|
|
|
325
370
|
return html("<h1>Studio not built. Run bun run build:wechat-preview first.</h1>", 500);
|
|
326
371
|
}
|
|
327
372
|
|
|
373
|
+
if (pathname.startsWith("/studio-assets/")) {
|
|
374
|
+
let name: string;
|
|
375
|
+
try { name = decodeURIComponent(pathname.slice("/studio-assets/".length)); }
|
|
376
|
+
catch { return json({ error: "invalid asset path" }, 400); }
|
|
377
|
+
const target = await studioFile(name);
|
|
378
|
+
if (!target) return json({ error: "Studio asset not found" }, 404);
|
|
379
|
+
return new Response(new Uint8Array(readFileSync(target)), {
|
|
380
|
+
headers: { "content-type": guessContentType(target), "cache-control": "no-cache" },
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
328
384
|
if (pathname === "/studio.js" || pathname === "/editor-export.js") {
|
|
329
385
|
const filePath = join(DIST_DIR, pathname.slice(1));
|
|
330
386
|
if (existsSync(filePath)) {
|
|
@@ -2,7 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { tmpdir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
5
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "fs";
|
|
6
6
|
import { startPreviewServer, type StartPreviewServerResult } from "./http";
|
|
7
7
|
import { loadConfig, saveConfig } from "../../config";
|
|
8
8
|
|
|
@@ -160,11 +160,70 @@ describe("wechat-preview studio server APIs", () => {
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
test("loads standalone defaults without depending on account CSS or styles", async () => {
|
|
164
|
+
const path = join(testDir, "config", "studio-style.json");
|
|
165
|
+
writeFileSync(path, JSON.stringify({ schemaVersion: 1, engineVersion: 1, id: "style", name: "Style", version: "1.0.0",
|
|
166
|
+
css: "p { color: #123456; }", exportTheme: { bodyFontSize: "18px" }, syntaxPresets: { h2: "contrast-green" } }));
|
|
167
|
+
const config = loadConfig();
|
|
168
|
+
const original = { ...config.wx.accounts.default };
|
|
169
|
+
config.wx.accounts.default.articleTheme = "studio-style.json";
|
|
170
|
+
config.wx.accounts.default.customCss = "missing.css";
|
|
171
|
+
saveConfig(config);
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(`${server.url}/api/studio/config`);
|
|
174
|
+
expect(response.status).toBe(200);
|
|
175
|
+
const data = await response.json() as any;
|
|
176
|
+
expect(data.accounts.default.theme.exportTheme.bodyFontSize).toBe("18px");
|
|
177
|
+
expect(data.accounts.default.theme.syntaxPresets.h2).toBe("contrast-green");
|
|
178
|
+
expect(data.accounts.default.articleThemeCss).toContain("#123456");
|
|
179
|
+
expect(data.accounts.default.customCssContent).toBe("");
|
|
180
|
+
expect(data.accounts.default.articleThemeStandalone).toBe(true);
|
|
181
|
+
} finally {
|
|
182
|
+
config.wx.accounts.default = original;
|
|
183
|
+
saveConfig(config);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
163
187
|
test("GET /studio serves the Studio HTML dashboard", async () => {
|
|
164
188
|
const res = await fetch(`${server.url}/studio`);
|
|
165
189
|
expect(res.status).toBe(200);
|
|
166
190
|
const html = await res.text();
|
|
167
191
|
expect(html).toContain("WeChat Visual Studio");
|
|
168
192
|
expect(html).toContain("studio.js");
|
|
193
|
+
expect(html).toContain('id="btn-export-config"');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("external Studio serves plugin resources without exposing files outside its root", async () => {
|
|
197
|
+
const root = join(testDir, "plugin-assets");
|
|
198
|
+
mkdirSync(root);
|
|
199
|
+
writeFileSync(join(root, "studio.html"), '<script src="/studio.js"></script><link href="/style.css"><a href="/">History</a>');
|
|
200
|
+
writeFileSync(join(root, "studio.js"), "/* plugin studio */");
|
|
201
|
+
writeFileSync(join(testDir, "private.txt"), "private");
|
|
202
|
+
symlinkSync(join(testDir, "private.txt"), join(root, "outside.txt"));
|
|
203
|
+
const pluginPath = join(testDir, "studio-plugin.mjs");
|
|
204
|
+
writeFileSync(pluginPath, `export default {name:'studio-test',render(){},studio(){return {root:${JSON.stringify(root)},entry:'studio.html'}}}`);
|
|
205
|
+
const config = loadConfig();
|
|
206
|
+
const original = config.plugins.markdownRenderer;
|
|
207
|
+
config.plugins.markdownRenderer = pluginPath;
|
|
208
|
+
saveConfig(config);
|
|
209
|
+
let external: StartPreviewServerResult | undefined;
|
|
210
|
+
const originalServerDir = process.env.ZZHUB_WECHAT_PREVIEW_DIR;
|
|
211
|
+
process.env.ZZHUB_WECHAT_PREVIEW_DIR = join(testDir, "external-server");
|
|
212
|
+
try {
|
|
213
|
+
external = await startPreviewServer({ host: "127.0.0.1", port: 0, reuseExisting: false });
|
|
214
|
+
const page = await (await fetch(`${external.url}/studio`)).text();
|
|
215
|
+
expect(page).toContain('src="/studio-assets/studio.js"');
|
|
216
|
+
expect(page).toContain('href="/studio-assets/style.css"');
|
|
217
|
+
expect(page).toContain('<a href="/">History</a>');
|
|
218
|
+
expect(await (await fetch(`${external.url}/studio-assets/studio.js`)).text()).toBe("/* plugin studio */");
|
|
219
|
+
expect((await fetch(`${external.url}/studio-assets/outside.txt`)).status).toBe(404);
|
|
220
|
+
expect((await fetch(`${external.url}/studio-assets/..%2fprivate.txt`)).status).toBe(404);
|
|
221
|
+
expect((await fetch(`${external.url}/api/studio/config`)).status).toBe(200);
|
|
222
|
+
} finally {
|
|
223
|
+
external?.stop?.();
|
|
224
|
+
process.env.ZZHUB_WECHAT_PREVIEW_DIR = originalServerDir;
|
|
225
|
+
config.plugins.markdownRenderer = original;
|
|
226
|
+
saveConfig(config);
|
|
227
|
+
}
|
|
169
228
|
});
|
|
170
229
|
});
|
|
@@ -44,10 +44,27 @@ describe("semantic WeChat HTML export", () => {
|
|
|
44
44
|
expect(html).toContain("Chapter</span>");
|
|
45
45
|
expect(html).toContain("01</span>");
|
|
46
46
|
expect(html).toContain("Citation</p>");
|
|
47
|
+
// 公众号编辑器解析不了 currentcolor/多值 border 简写,导出必须展开为可见边单值属性
|
|
48
|
+
expect(html).not.toContain("currentcolor");
|
|
49
|
+
// 引用导出为 section(规避微信编辑器对原生 blockquote 的强制灰色左边框),样式全靠内联
|
|
50
|
+
expect(html).not.toContain("<blockquote");
|
|
51
|
+
expect(html).toContain("border-left: 3px solid");
|
|
47
52
|
expect(html).toContain("Advice</strong>");
|
|
48
53
|
expect(html).toMatch(/background-color: (?:#1f7a4d|rgb\(31, 122, 77\))/);
|
|
49
54
|
}, 30_000);
|
|
50
55
|
|
|
56
|
+
test("h2 pillar preset exports border-left on the heading section", async () => {
|
|
57
|
+
tempDir = await mkdtemp(join(tmpdir(), "zzhub-wechat-pillar-"));
|
|
58
|
+
const markdownPath = join(tempDir, "post.md");
|
|
59
|
+
await writeFile(markdownPath, "## 竖条标题\n\nBody.");
|
|
60
|
+
const { html } = await exportMarkdownToWechatHtml({
|
|
61
|
+
markdownPath, outPath: join(tempDir, "post.html"), account: "default",
|
|
62
|
+
themeOverrides: { syntaxPresets: { h2: "pillar" } },
|
|
63
|
+
});
|
|
64
|
+
const h2Tag = html.match(/<section[^>]*>(?:<span[^>]*>[^<]*<\/span>)*竖条标题/)?.[0] ?? "";
|
|
65
|
+
expect(h2Tag).toContain("border-left: 4px solid");
|
|
66
|
+
}, 30_000);
|
|
67
|
+
|
|
51
68
|
test.skipIf(findChrome() === null)("Studio saves and resets the same style and ignores stale renders", async () => {
|
|
52
69
|
const studio = await readFile(join(DIST_DIR, "src/wechat-preview/browser/studio/studio.html"), "utf8");
|
|
53
70
|
const theme = getWechatPreviewTheme("default");
|
|
@@ -60,14 +77,20 @@ window.fetch = async (url, options) => {
|
|
|
60
77
|
}
|
|
61
78
|
return new Response(JSON.stringify(${JSON.stringify({ defaultAccount: "default", accounts: { default: { name: "default", theme, customCssContent: "" } }, plugins: [], sampleMarkdown: "# Title\n\nInitial body." })}));
|
|
62
79
|
};
|
|
80
|
+
const originalCreateObjectURL = URL.createObjectURL;
|
|
81
|
+
URL.createObjectURL = blob => { window.exportedBlob = blob; return originalCreateObjectURL.call(URL, blob); };
|
|
82
|
+
HTMLAnchorElement.prototype.click = function() { window.downloadName = this.download; };
|
|
63
83
|
window.__zzhubRenderReady = (async () => {
|
|
64
84
|
const waitFor = async predicate => { while (!predicate()) await new Promise(resolve => setTimeout(resolve, 20)); };
|
|
65
85
|
const edit = (id, value) => { const input = document.getElementById(id); input.value = value; input.dispatchEvent(new Event('input')); };
|
|
66
86
|
const source = () => document.getElementById('html-source-code')?.value || '';
|
|
67
87
|
await waitFor(() => source().includes('Initial body.'));
|
|
68
88
|
edit('input-font-size', '18'); edit('input-para-spacing', '1.44');
|
|
69
|
-
edit('input-heading-label', 'Chapter'); edit('input-custom-css', 'p { color: #123456; }');
|
|
89
|
+
edit('input-heading-label', 'Chapter'); edit('input-custom-css', '.milkdown .editor p { color: #123456; }');
|
|
70
90
|
await waitFor(() => source().includes('font-size: 18px') && source().includes('margin-bottom: 1.44em'));
|
|
91
|
+
document.getElementById('btn-export-config').click();
|
|
92
|
+
const exported = JSON.parse(await window.exportedBlob.text());
|
|
93
|
+
const exportDidNotSave = !window.saved;
|
|
71
94
|
document.getElementById('btn-save').click();
|
|
72
95
|
await waitFor(() => window.saved);
|
|
73
96
|
await new Promise(resolve => setTimeout(resolve, 20));
|
|
@@ -79,10 +102,10 @@ window.__zzhubRenderReady = (async () => {
|
|
|
79
102
|
Image.prototype.decode = async function() { await new Promise(resolve => setTimeout(resolve, 250)); return originalDecode.call(this); };
|
|
80
103
|
edit('editor-markdown', '');
|
|
81
104
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
82
|
-
edit('editor-markdown', 'Latest body.');
|
|
83
|
-
await waitFor(() => source().includes('Latest body.'));
|
|
105
|
+
edit('editor-markdown', 'Latest body.\\n\\n');
|
|
106
|
+
await waitFor(() => source().includes('Latest body.') && source().includes('Crepe 图注</p>'));
|
|
84
107
|
const result = document.createElement('script'); result.id = 'zzhub-wechat-export-result'; result.type = 'application/json';
|
|
85
|
-
result.textContent = JSON.stringify({status:'success',html:JSON.stringify({saved:window.saved,resetOk,html:source()})});
|
|
108
|
+
result.textContent = JSON.stringify({status:'success',html:JSON.stringify({saved:window.saved,exported,exportDidNotSave,downloadName:window.downloadName,resetOk,html:source()})});
|
|
86
109
|
document.body.appendChild(result);
|
|
87
110
|
})();
|
|
88
111
|
</script>`);
|
|
@@ -90,12 +113,30 @@ window.__zzhubRenderReady = (async () => {
|
|
|
90
113
|
expect(result.status).toBe("success");
|
|
91
114
|
if (!("html" in result)) throw new Error("Studio did not produce HTML");
|
|
92
115
|
const checked = JSON.parse(result.html);
|
|
116
|
+
expect(checked.exported.account).toBeUndefined();
|
|
117
|
+
expect(checked.exported.exportTheme.bodyFontSize).toBe("18px");
|
|
118
|
+
expect(checked.exported.structure.headingLabel).toBe("Chapter");
|
|
119
|
+
expect(checked.exported.css).toContain("#123456");
|
|
120
|
+
expect(checked.exportDidNotSave).toBe(true);
|
|
121
|
+
expect(checked.downloadName).toBe("wechat-style.json");
|
|
122
|
+
tempDir = await mkdtemp(join(tmpdir(), "zzhub-standalone-export-"));
|
|
123
|
+
const profilePath = join(tempDir, "wechat-style.json");
|
|
124
|
+
const markdownPath = join(tempDir, "post.md");
|
|
125
|
+
await writeFile(profilePath, JSON.stringify(checked.exported));
|
|
126
|
+
await writeFile(markdownPath, "## Section\n\nPortable body.");
|
|
127
|
+
const portable = await exportMarkdownToWechatHtml({
|
|
128
|
+
markdownPath, outPath: join(tempDir, "post.html"), account: "ancientone", articleThemePath: profilePath,
|
|
129
|
+
themeOverrides: { exportTheme: { bodyFontSize: "14px" } },
|
|
130
|
+
});
|
|
131
|
+
expect(portable.html).toMatch(/<p[^>]+font-size: 18px[^>]*>Portable body\.<\/p>/);
|
|
132
|
+
expect(portable.html).toContain("Chapter</span>");
|
|
133
|
+
expect(portable.html).toMatch(/color: (?:#123456|rgb\(18, 52, 86\))/);
|
|
93
134
|
expect(checked.saved.exportTheme.bodyFontSize).toBe("18px");
|
|
94
135
|
expect(checked.saved.exportTheme.bodyParagraphSpacing).toBe("1.44em");
|
|
95
136
|
expect(checked.saved.structure.headingLabel).toBe("Chapter");
|
|
96
137
|
expect(checked.resetOk).toBe(true);
|
|
97
138
|
expect(checked.html).toContain("Latest body.");
|
|
98
|
-
expect(checked.html).
|
|
139
|
+
expect(checked.html).toContain("Crepe 图注</p>");
|
|
99
140
|
}, 20_000);
|
|
100
141
|
|
|
101
142
|
test.skipIf(findChrome() === null)("renders all nodes, cascades CSS, and emits a safe exact preview", async () => {
|
|
@@ -123,8 +164,8 @@ window.__zzhubRenderReady = (async () => {
|
|
|
123
164
|
|
|
124
165
|
expect(result.html).toBe(html);
|
|
125
166
|
expect(html.length).toBeLessThan(65_400);
|
|
126
|
-
expect(html).toContain("
|
|
127
|
-
expect(html).toContain("<blockquote");
|
|
167
|
+
expect(html).toContain("二级标题:清晰但不过度装饰");
|
|
168
|
+
expect(html).not.toContain("<blockquote");
|
|
128
169
|
expect(html).toContain("重点结论</span>");
|
|
129
170
|
expect(html).not.toContain("==重点结论==");
|
|
130
171
|
expect(html).toContain("==不高亮==");
|
|
@@ -170,7 +211,7 @@ window.__zzhubRenderReady = (async () => {
|
|
|
170
211
|
|
|
171
212
|
const tagNames = Array.from(html.matchAll(/<\/?([a-z][a-z0-9-]*)\b/gi), (match) => match[1]?.toLowerCase());
|
|
172
213
|
const allowedTags = new Set([
|
|
173
|
-
"section", "p", "
|
|
214
|
+
"section", "p", "ul", "ol", "li", "pre", "code",
|
|
174
215
|
"span", "strong", "em", "b", "i", "u", "s", "del", "a", "img",
|
|
175
216
|
"br", "hr", "table", "thead", "tbody", "tr", "th", "td",
|
|
176
217
|
]);
|