@akairos/pipeline 0.18.1 → 0.18.5
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 +5 -3
- package/package.json +5 -4
- 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 +9 -1
- package/src/commands/wechat-export.ts +9 -4
- package/src/providers/index.ts +4 -2
- package/src/runtime-package.ts +4 -3
- 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 +29 -13
- package/src/wechat-preview/server/http.ts +8 -7
- package/src/wechat-preview/server/studio.test.ts +25 -0
- 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
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
rewriteHtmlLocalAssets,
|
|
27
27
|
} from "./local-file";
|
|
28
28
|
import { renderDashboardHtml, renderFailedEntryHtml } from "./ui";
|
|
29
|
-
import { buildWechatPreviewShell, resolveWechatArticleTheme } from "../index";
|
|
29
|
+
import { buildWechatPreviewShell, resolveWechatArticleTheme, rewriteRelativeImagePaths } from "../index";
|
|
30
30
|
import { getPluginDocs } from "@akairos/milkdown-article-plugins";
|
|
31
31
|
import { readArticleTheme } from "../../article-theme";
|
|
32
32
|
import { PipelineConfigSchema, WechatExportThemeOverridesSchema, WechatStructureOverridesSchema } from "../../schema/config";
|
|
@@ -210,8 +210,10 @@ export async function startPreviewServer(
|
|
|
210
210
|
const accountsData: Record<string, unknown> = {};
|
|
211
211
|
|
|
212
212
|
for (const [key, account] of Object.entries(config.wx.accounts)) {
|
|
213
|
+
const articleThemePath = resolveConfigRelativePath(account.articleTheme, configPath);
|
|
214
|
+
const articleTheme = articleThemePath ? await readArticleTheme(articleThemePath) : null;
|
|
213
215
|
let customCssContent = "";
|
|
214
|
-
if (account.customCss) {
|
|
216
|
+
if (account.customCss && articleTheme?.manifest.css === undefined) {
|
|
215
217
|
const resolved = resolveConfigRelativePath(account.customCss, configPath);
|
|
216
218
|
if (resolved) {
|
|
217
219
|
try {
|
|
@@ -222,9 +224,7 @@ export async function startPreviewServer(
|
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
const
|
|
226
|
-
const articleTheme = articleThemePath ? await readArticleTheme(articleThemePath) : null;
|
|
227
|
-
const { theme, structure } = resolveWechatArticleTheme(key, account.theme, articleTheme);
|
|
227
|
+
const { theme, structure, syntaxPresets } = resolveWechatArticleTheme(key, account.theme, articleTheme);
|
|
228
228
|
|
|
229
229
|
accountsData[key] = {
|
|
230
230
|
name: account.name || key,
|
|
@@ -233,11 +233,12 @@ export async function startPreviewServer(
|
|
|
233
233
|
theme: {
|
|
234
234
|
editorVars: theme.editorVars,
|
|
235
235
|
exportTheme: theme.exportTheme,
|
|
236
|
-
syntaxPresets:
|
|
236
|
+
syntaxPresets: syntaxPresets || {},
|
|
237
237
|
structure: structure || {},
|
|
238
238
|
},
|
|
239
239
|
articleThemeCss: articleTheme?.css || "",
|
|
240
240
|
articleTheme: account.articleTheme ?? null,
|
|
241
|
+
articleThemeStandalone: articleTheme?.manifest.css !== undefined,
|
|
241
242
|
};
|
|
242
243
|
}
|
|
243
244
|
|
|
@@ -245,7 +246,7 @@ export async function startPreviewServer(
|
|
|
245
246
|
const fixturePath = join(WECHAT_PREVIEW_DIR, "fixtures/all-nodes.md");
|
|
246
247
|
if (existsSync(fixturePath)) {
|
|
247
248
|
try {
|
|
248
|
-
sampleMarkdown = readFileSync(fixturePath, "utf-8");
|
|
249
|
+
sampleMarkdown = rewriteRelativeImagePaths(readFileSync(fixturePath, "utf-8"), dirname(fixturePath));
|
|
249
250
|
} catch {
|
|
250
251
|
// ignore
|
|
251
252
|
}
|
|
@@ -160,11 +160,36 @@ 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"');
|
|
169
194
|
});
|
|
170
195
|
});
|
|
@@ -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
|
]);
|
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
260
|
+
const ratioRaw = element.getAttribute("ratio");
|
|
261
|
+
const ratioFromAttr = ratioRaw ? Number(ratioRaw) : NaN;
|
|
253
262
|
const alt = element.getAttribute("alt") ?? "";
|
|
254
|
-
const ratio =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
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;
|
package/src/workflow.test.ts
CHANGED
|
@@ -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}
|