@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
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import articleCss from "@akairos/milkdown-article-style/article.css?raw";
|
|
1
2
|
import { Editor, defaultValueCtx, rootCtx } from "@milkdown/kit/core";
|
|
2
3
|
import { commonmark } from "@milkdown/kit/preset/commonmark";
|
|
3
4
|
import { gfm } from "@milkdown/kit/preset/gfm";
|
|
4
5
|
import { getHTML } from "@milkdown/kit/utils";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import type { ArticleStructure } from "../../wechat-renderer";
|
|
9
|
-
import type { WechatExportTheme } from "../../themes";
|
|
6
|
+
import { getCombinedMilkdownPlugins, getCombinedDefaultCss, type WechatPluginDoc } from "@akairos/milkdown-article-plugins";
|
|
7
|
+
import { MynoEditor } from "./myno-editor";
|
|
8
|
+
import { STYLE_PACKS, type StylePack } from "./style-packs";
|
|
10
9
|
import { SYNTAX_CATEGORIES, buildCombinedPresetsCss } from "./syntax-presets";
|
|
11
10
|
import { COLOR_PALETTES } from "./color-palettes";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { parseStyleConfig, exportStyleConfigBlob, type StandaloneStyleConfig } from "./config-manager";
|
|
12
|
+
import { renderWechatHtml, buildWechatThemeCss, type ArticleStructure } from "../../wechat-renderer";
|
|
13
|
+
import type { WechatExportTheme } from "../../themes";
|
|
14
14
|
|
|
15
15
|
interface AccountData {
|
|
16
16
|
name: string;
|
|
@@ -24,6 +24,7 @@ interface AccountData {
|
|
|
24
24
|
};
|
|
25
25
|
articleThemeCss?: string;
|
|
26
26
|
articleTheme?: string | null;
|
|
27
|
+
articleThemeStandalone?: boolean;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
interface StudioConfigResponse {
|
|
@@ -40,21 +41,24 @@ let latestWechatHtml = "";
|
|
|
40
41
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
41
42
|
let showingHtmlSource = false;
|
|
42
43
|
let currentSyntaxPresets: Record<string, string> = {};
|
|
43
|
-
// 串行渲染,旧结果不能覆盖最新编辑或账号。
|
|
44
44
|
let renderRevision = 0;
|
|
45
45
|
let rendering = false;
|
|
46
|
+
let mynoEditor: MynoEditor | null = null;
|
|
47
|
+
let currentEditorMode: "visual" | "source" = "visual";
|
|
48
|
+
let standaloneThemeName = "";
|
|
46
49
|
|
|
47
|
-
// DOM Elements
|
|
50
|
+
// ── DOM Elements ────────────────────────────────────────────────────────
|
|
48
51
|
const accountSelect = document.getElementById("account-select") as HTMLSelectElement;
|
|
52
|
+
const configModeBadge = document.getElementById("config-mode-badge") as HTMLElement;
|
|
49
53
|
const editorMarkdown = document.getElementById("editor-markdown") as HTMLTextAreaElement;
|
|
54
|
+
const milkdownEditorRoot = document.getElementById("milkdown-editor-root") as HTMLElement;
|
|
50
55
|
const previewScreen = document.getElementById("preview-screen") as HTMLElement;
|
|
51
56
|
const editorStats = document.getElementById("editor-stats") as HTMLElement;
|
|
52
57
|
const htmlSourceContainer = document.getElementById("html-source-container") as HTMLElement;
|
|
53
58
|
const htmlSourceCode = document.getElementById("html-source-code") as HTMLTextAreaElement;
|
|
54
59
|
const toastContainer = document.getElementById("toast-container") as HTMLElement;
|
|
55
|
-
const hiddenHost = document.getElementById("studio-editor-host") as HTMLElement;
|
|
56
60
|
|
|
57
|
-
//
|
|
61
|
+
// Sliders & Text Inputs
|
|
58
62
|
const inputFontSize = document.getElementById("input-font-size") as HTMLInputElement;
|
|
59
63
|
const valFontSize = document.getElementById("val-font-size") as HTMLElement;
|
|
60
64
|
const inputLineHeight = document.getElementById("input-line-height") as HTMLInputElement;
|
|
@@ -85,9 +89,24 @@ const inputHeadingLabel = document.getElementById("input-heading-label") as HTML
|
|
|
85
89
|
const inputQuoteLabel = document.getElementById("input-quote-label") as HTMLInputElement;
|
|
86
90
|
const inputCustomCss = document.getElementById("input-custom-css") as HTMLTextAreaElement;
|
|
87
91
|
|
|
92
|
+
const btnModeVisual = document.getElementById("btn-mode-visual") as HTMLButtonElement;
|
|
93
|
+
const btnModeSource = document.getElementById("btn-mode-source") as HTMLButtonElement;
|
|
94
|
+
|
|
95
|
+
const fileImportConfig = document.getElementById("file-import-config") as HTMLInputElement;
|
|
96
|
+
const btnImportConfig = document.getElementById("btn-import-config") as HTMLButtonElement;
|
|
97
|
+
const btnExportConfig = document.getElementById("btn-export-config") as HTMLButtonElement;
|
|
98
|
+
const btnSave = document.getElementById("btn-save") as HTMLButtonElement;
|
|
99
|
+
const btnReset = document.getElementById("btn-reset") as HTMLButtonElement;
|
|
100
|
+
const btnCopy = document.getElementById("btn-copy") as HTMLButtonElement;
|
|
101
|
+
const btnToggleView = document.getElementById("btn-toggle-view") as HTMLButtonElement;
|
|
102
|
+
const btnLoadSample = document.getElementById("btn-load-sample") as HTMLButtonElement;
|
|
103
|
+
const btnClearMd = document.getElementById("btn-clear-md") as HTMLButtonElement;
|
|
104
|
+
|
|
105
|
+
// ── Toast System ────────────────────────────────────────────────────────
|
|
88
106
|
function showToast(message: string, type: "info" | "success" | "error" = "info") {
|
|
107
|
+
if (!toastContainer) return;
|
|
89
108
|
const toast = document.createElement("div");
|
|
90
|
-
toast.className = `toast ${type === "success" ? "toast-success" : ""}`;
|
|
109
|
+
toast.className = `toast ${type === "success" ? "toast-success" : type === "error" ? "toast-error" : ""}`;
|
|
91
110
|
toast.textContent = message;
|
|
92
111
|
toastContainer.appendChild(toast);
|
|
93
112
|
requestAnimationFrame(() => toast.classList.add("show"));
|
|
@@ -97,6 +116,7 @@ function showToast(message: string, type: "info" | "success" | "error" = "info")
|
|
|
97
116
|
}, 3000);
|
|
98
117
|
}
|
|
99
118
|
|
|
119
|
+
// ── Color Picker Pairing ────────────────────────────────────────────────
|
|
100
120
|
function bindColorPair(picker: HTMLInputElement, hex: HTMLInputElement, onChange: () => void) {
|
|
101
121
|
picker.addEventListener("input", () => {
|
|
102
122
|
hex.value = picker.value;
|
|
@@ -111,15 +131,45 @@ function bindColorPair(picker: HTMLInputElement, hex: HTMLInputElement, onChange
|
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
function updateStats() {
|
|
114
|
-
const text =
|
|
134
|
+
const text = getActiveMarkdown();
|
|
115
135
|
const chars = text.replace(/\s+/g, "").length;
|
|
116
136
|
const minutes = Math.max(1, Math.round(chars / 400));
|
|
117
|
-
|
|
137
|
+
if (editorStats) {
|
|
138
|
+
editorStats.textContent = `字数: ${chars.toLocaleString()} 字 | 预计阅读: ${minutes} 分钟`;
|
|
139
|
+
}
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
|
|
142
|
+
function getActiveMarkdown(): string {
|
|
143
|
+
if (currentEditorMode === "visual" && mynoEditor) {
|
|
144
|
+
return mynoEditor.getMarkdown();
|
|
145
|
+
}
|
|
146
|
+
return editorMarkdown.value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function stripLeadingH1(markdown: string): string {
|
|
150
|
+
return markdown.replace(/^#\s+[^\r\n]*(?:\r?\n|$)/, "");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function extractFrontmatter(markdown: string): { frontmatter: Record<string, unknown> | null; content: string } {
|
|
154
|
+
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
155
|
+
if (!match) return { frontmatter: null, content: markdown };
|
|
156
|
+
return { frontmatter: {}, content: markdown.slice(match[0].length) };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 品牌色衍生的卡片底色(--brand-bg 系),非 6 位 hex 时返回 null 以保留账号默认值。 */
|
|
160
|
+
function brandTint(hex: string, alpha: number): string | null {
|
|
161
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
162
|
+
if (!m) return null;
|
|
163
|
+
const n = parseInt(m[1], 16);
|
|
164
|
+
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Read Style Controls ─────────────────────────────────────────────────
|
|
121
168
|
function readStyleControls() {
|
|
122
|
-
const account = accounts[currentAccount]
|
|
169
|
+
const account = accounts[currentAccount] || {
|
|
170
|
+
theme: { exportTheme: {}, editorVars: {}, structure: {} },
|
|
171
|
+
};
|
|
172
|
+
|
|
123
173
|
const exportTheme: WechatExportTheme = {
|
|
124
174
|
...account.theme.exportTheme as WechatExportTheme,
|
|
125
175
|
bodyFontSize: `${inputFontSize.value}px`,
|
|
@@ -133,52 +183,186 @@ function readStyleControls() {
|
|
|
133
183
|
bodyLineHeight: inputLineHeight.value,
|
|
134
184
|
bodyLetterSpacing: `${inputLetterSpacing.value}em`,
|
|
135
185
|
};
|
|
186
|
+
|
|
187
|
+
const brandBg = brandTint(hexBrandColor.value, 0.08);
|
|
188
|
+
const brandBgDeep = brandBg && brandTint(hexBrandColor.value, 0.12);
|
|
136
189
|
const editorVars = {
|
|
137
190
|
...account.theme.editorVars,
|
|
138
191
|
"--brand": hexBrandColor.value,
|
|
192
|
+
...(brandBg && brandBgDeep ? { "--brand-bg": brandBg, "--brand-bg-deep": brandBgDeep } : {}),
|
|
139
193
|
"--text": hexTextColor.value,
|
|
140
194
|
"--divider": hexDividerColor.value,
|
|
141
195
|
};
|
|
196
|
+
|
|
142
197
|
const structure: ArticleStructure = {
|
|
143
198
|
...account.theme.structure,
|
|
144
199
|
numberedHeadings: switchNumberedHeadings.checked,
|
|
145
200
|
headingLabel: inputHeadingLabel.value.trim(),
|
|
146
201
|
quoteLabel: inputQuoteLabel.value.trim(),
|
|
147
202
|
};
|
|
203
|
+
|
|
148
204
|
return { editorVars, exportTheme, structure };
|
|
149
205
|
}
|
|
150
206
|
|
|
207
|
+
// ── Live Editor Style Synchronization ───────────────────────────────────
|
|
208
|
+
function applyLiveEditorStyles(
|
|
209
|
+
style: ReturnType<typeof readStyleControls>,
|
|
210
|
+
customCss: string,
|
|
211
|
+
) {
|
|
212
|
+
let styleTag = document.getElementById("studio-live-editor-css") as HTMLStyleElement | null;
|
|
213
|
+
if (!styleTag) {
|
|
214
|
+
styleTag = document.createElement("style");
|
|
215
|
+
styleTag.id = "studio-live-editor-css";
|
|
216
|
+
document.head.appendChild(styleTag);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const themeCss = buildWechatThemeCss(style.editorVars, style.exportTheme);
|
|
220
|
+
const pluginCss = getCombinedDefaultCss();
|
|
221
|
+
|
|
222
|
+
// Structure decorations for editor (simulating heading labels / counters)
|
|
223
|
+
let structureCss = "";
|
|
224
|
+
if (style.structure.numberedHeadings) {
|
|
225
|
+
const labelPrefix = style.structure.headingLabel ? `"${style.structure.headingLabel} " ` : "";
|
|
226
|
+
structureCss += `
|
|
227
|
+
.milkdown .editor {
|
|
228
|
+
counter-reset: wx-heading-counter;
|
|
229
|
+
}
|
|
230
|
+
.milkdown .editor h2::before {
|
|
231
|
+
counter-increment: wx-heading-counter;
|
|
232
|
+
content: ${labelPrefix}counter(wx-heading-counter, decimal-leading-zero) " ";
|
|
233
|
+
margin-right: 0.3em;
|
|
234
|
+
opacity: 0.7;
|
|
235
|
+
font-weight: 600;
|
|
236
|
+
}
|
|
237
|
+
`;
|
|
238
|
+
} else if (style.structure.headingLabel) {
|
|
239
|
+
structureCss += `
|
|
240
|
+
.milkdown .editor h2::before {
|
|
241
|
+
content: "${style.structure.headingLabel} ";
|
|
242
|
+
margin-right: 0.3em;
|
|
243
|
+
opacity: 0.7;
|
|
244
|
+
font-weight: 600;
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (style.structure.quoteLabel) {
|
|
250
|
+
structureCss += `
|
|
251
|
+
.milkdown .editor blockquote::before {
|
|
252
|
+
content: "${style.structure.quoteLabel}";
|
|
253
|
+
display: block;
|
|
254
|
+
font-size: 11px;
|
|
255
|
+
font-weight: 600;
|
|
256
|
+
letter-spacing: 0.05em;
|
|
257
|
+
color: var(--wx-brand-ink, var(--wx-brand-accent, #ca6093));
|
|
258
|
+
margin-bottom: 6px;
|
|
259
|
+
text-transform: uppercase;
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
styleTag.textContent = `
|
|
265
|
+
/* ── Crepe resets: 后置于运行时,同特异性即可覆盖 Crepe 主题;不可用 !important,否则压过下方预设 ── */
|
|
266
|
+
.milkdown .ProseMirror {
|
|
267
|
+
padding: 0;
|
|
268
|
+
}
|
|
269
|
+
.milkdown .ProseMirror h1,
|
|
270
|
+
.milkdown .ProseMirror h2,
|
|
271
|
+
.milkdown .ProseMirror h3,
|
|
272
|
+
.milkdown .ProseMirror h4,
|
|
273
|
+
.milkdown .ProseMirror h5,
|
|
274
|
+
.milkdown .ProseMirror h6 {
|
|
275
|
+
padding-top: 0;
|
|
276
|
+
padding-bottom: 0;
|
|
277
|
+
}
|
|
278
|
+
.milkdown .ProseMirror p {
|
|
279
|
+
padding: 0;
|
|
280
|
+
}
|
|
281
|
+
.milkdown .ProseMirror blockquote {
|
|
282
|
+
position: static;
|
|
283
|
+
box-sizing: border-box;
|
|
284
|
+
}
|
|
285
|
+
.milkdown .ProseMirror blockquote::before {
|
|
286
|
+
display: none;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* ── Dynamic WeChat Theme Variables & Container Styling ── */
|
|
290
|
+
${themeCss}
|
|
291
|
+
|
|
292
|
+
/* ── Structure Enhancements (Numbered Headings, Chapter / Quote Labels) ── */
|
|
293
|
+
${structureCss}
|
|
294
|
+
|
|
295
|
+
/* ── Article Plugins Styling (Callouts, Badges, Kbd, Article Cards, etc.) ── */
|
|
296
|
+
${pluginCss}
|
|
297
|
+
|
|
298
|
+
/* ── Active Syntax Presets & Custom CSS Overrides ── */
|
|
299
|
+
${customCss}
|
|
300
|
+
`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function refreshLiveEditorStyles() {
|
|
304
|
+
const style = readStyleControls();
|
|
305
|
+
const account = accounts[currentAccount];
|
|
306
|
+
const customCss = [
|
|
307
|
+
buildCombinedPresetsCss(currentSyntaxPresets),
|
|
308
|
+
account?.articleThemeCss,
|
|
309
|
+
inputCustomCss.value,
|
|
310
|
+
].filter(Boolean).join("\n\n");
|
|
311
|
+
applyLiveEditorStyles(style, customCss);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── Inlined WeChat Render Pass ──────────────────────────────────────────
|
|
151
315
|
async function doRender() {
|
|
152
|
-
if (rendering
|
|
316
|
+
if (rendering) return;
|
|
153
317
|
rendering = true;
|
|
154
318
|
const revision = renderRevision;
|
|
155
|
-
const
|
|
319
|
+
const rawMd = getActiveMarkdown();
|
|
320
|
+
const markdown = stripLeadingH1(extractFrontmatter(rawMd).content).trimStart();
|
|
156
321
|
const style = readStyleControls();
|
|
322
|
+
const account = accounts[currentAccount];
|
|
323
|
+
|
|
157
324
|
const customCss = [
|
|
158
325
|
buildCombinedPresetsCss(currentSyntaxPresets),
|
|
159
|
-
|
|
326
|
+
account?.articleThemeCss,
|
|
160
327
|
inputCustomCss.value,
|
|
161
328
|
].filter(Boolean).join("\n\n");
|
|
329
|
+
|
|
330
|
+
applyLiveEditorStyles(style, customCss);
|
|
331
|
+
|
|
162
332
|
updateStats();
|
|
163
333
|
|
|
164
334
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
let semanticHtml: string;
|
|
176
|
-
try {
|
|
177
|
-
await editor.create();
|
|
178
|
-
semanticHtml = editor.action(getHTML());
|
|
179
|
-
} finally {
|
|
180
|
-
await editor.destroy();
|
|
335
|
+
let semanticHtml = "";
|
|
336
|
+
if (currentEditorMode === "visual" && mynoEditor) {
|
|
337
|
+
const crepe = mynoEditor.getCrepe();
|
|
338
|
+
if (crepe) {
|
|
339
|
+
try {
|
|
340
|
+
semanticHtml = crepe.editor.action(getHTML());
|
|
341
|
+
} catch {
|
|
342
|
+
// ignore and fallback
|
|
343
|
+
}
|
|
344
|
+
}
|
|
181
345
|
}
|
|
346
|
+
|
|
347
|
+
if (!semanticHtml) {
|
|
348
|
+
const hiddenHost = document.getElementById("studio-editor-host") || document.createElement("div");
|
|
349
|
+
hiddenHost.innerHTML = "";
|
|
350
|
+
const editor = Editor.make()
|
|
351
|
+
.config((ctx) => {
|
|
352
|
+
ctx.set(rootCtx, hiddenHost);
|
|
353
|
+
ctx.set(defaultValueCtx, markdown);
|
|
354
|
+
})
|
|
355
|
+
.use(commonmark)
|
|
356
|
+
.use(gfm)
|
|
357
|
+
.use(getCombinedMilkdownPlugins());
|
|
358
|
+
try {
|
|
359
|
+
await editor.create();
|
|
360
|
+
semanticHtml = editor.action(getHTML());
|
|
361
|
+
} finally {
|
|
362
|
+
await editor.destroy();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
182
366
|
const inlinedHtml = await renderWechatHtml({
|
|
183
367
|
semanticHtml,
|
|
184
368
|
baseCss: articleCss,
|
|
@@ -186,17 +370,19 @@ async function doRender() {
|
|
|
186
370
|
structure: style.structure,
|
|
187
371
|
editorVars: style.editorVars,
|
|
188
372
|
theme: style.exportTheme,
|
|
373
|
+
strictImageDecode: false,
|
|
189
374
|
});
|
|
375
|
+
|
|
190
376
|
if (revision !== renderRevision) return;
|
|
191
377
|
latestWechatHtml = inlinedHtml;
|
|
192
|
-
previewScreen.innerHTML = inlinedHtml;
|
|
193
|
-
htmlSourceCode.value = inlinedHtml;
|
|
378
|
+
if (previewScreen) previewScreen.innerHTML = inlinedHtml;
|
|
379
|
+
if (htmlSourceCode) htmlSourceCode.value = inlinedHtml;
|
|
194
380
|
} catch (error) {
|
|
195
381
|
if (revision !== renderRevision) return;
|
|
196
|
-
console.error("
|
|
382
|
+
console.error("myno render error:", error);
|
|
197
383
|
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
-
previewScreen.textContent = `渲染错误: ${message}`;
|
|
199
|
-
htmlSourceCode.value = "";
|
|
384
|
+
if (previewScreen) previewScreen.textContent = `渲染错误: ${message}`;
|
|
385
|
+
if (htmlSourceCode) htmlSourceCode.value = "";
|
|
200
386
|
} finally {
|
|
201
387
|
rendering = false;
|
|
202
388
|
if (revision !== renderRevision) {
|
|
@@ -209,210 +395,351 @@ async function doRender() {
|
|
|
209
395
|
function scheduleRender(delay = 60) {
|
|
210
396
|
renderRevision += 1;
|
|
211
397
|
latestWechatHtml = "";
|
|
398
|
+
refreshLiveEditorStyles();
|
|
212
399
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
213
400
|
debounceTimer = setTimeout(() => {
|
|
214
401
|
void doRender();
|
|
215
402
|
}, delay);
|
|
216
403
|
}
|
|
217
404
|
|
|
218
|
-
|
|
219
|
-
|
|
405
|
+
// ── Style Packs (One-Click Application) ──────────────────────────────────
|
|
406
|
+
function renderStylePacks() {
|
|
407
|
+
const container = document.getElementById("style-packs-grid");
|
|
220
408
|
if (!container) return;
|
|
221
409
|
container.innerHTML = "";
|
|
222
410
|
|
|
223
|
-
for (const
|
|
411
|
+
for (const pack of STYLE_PACKS) {
|
|
224
412
|
const card = document.createElement("div");
|
|
225
|
-
card.className = "
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
413
|
+
card.className = "style-pack-card";
|
|
414
|
+
card.dataset.packId = pack.id;
|
|
415
|
+
|
|
416
|
+
const head = document.createElement("div");
|
|
417
|
+
head.className = "style-pack-head";
|
|
418
|
+
|
|
419
|
+
const titleWrap = document.createElement("div");
|
|
420
|
+
const name = document.createElement("span");
|
|
421
|
+
name.className = "style-pack-name";
|
|
422
|
+
name.textContent = pack.name;
|
|
423
|
+
const sub = document.createElement("span");
|
|
424
|
+
sub.className = "style-pack-subtitle";
|
|
425
|
+
sub.textContent = ` · ${pack.subtitle}`;
|
|
426
|
+
titleWrap.appendChild(name);
|
|
427
|
+
titleWrap.appendChild(sub);
|
|
428
|
+
|
|
429
|
+
const swatches = document.createElement("div");
|
|
430
|
+
swatches.className = "style-pack-swatches";
|
|
431
|
+
for (const color of pack.swatch) {
|
|
432
|
+
const dot = document.createElement("span");
|
|
433
|
+
dot.className = "swatch-dot";
|
|
434
|
+
dot.style.background = color;
|
|
435
|
+
swatches.appendChild(dot);
|
|
436
|
+
}
|
|
237
437
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
card.appendChild(
|
|
438
|
+
head.appendChild(titleWrap);
|
|
439
|
+
head.appendChild(swatches);
|
|
440
|
+
card.appendChild(head);
|
|
241
441
|
|
|
242
|
-
const desc = document.createElement("
|
|
243
|
-
desc.className = "
|
|
244
|
-
desc.textContent =
|
|
442
|
+
const desc = document.createElement("div");
|
|
443
|
+
desc.className = "style-pack-desc";
|
|
444
|
+
desc.textContent = pack.description;
|
|
245
445
|
card.appendChild(desc);
|
|
246
446
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const insertBtn = document.createElement("button");
|
|
253
|
-
insertBtn.className = "syntax-btn-insert";
|
|
254
|
-
insertBtn.textContent = "+ 插入示例到文章中";
|
|
255
|
-
insertBtn.addEventListener("click", () => {
|
|
256
|
-
insertTextAtCursor(doc.sampleMarkdown);
|
|
257
|
-
showToast(`已插入「${doc.name}」示例`, "info");
|
|
447
|
+
card.addEventListener("click", () => {
|
|
448
|
+
applyStylePack(pack);
|
|
449
|
+
document.querySelectorAll(".style-pack-card").forEach((c) => c.classList.remove("active"));
|
|
450
|
+
card.classList.add("active");
|
|
451
|
+
showToast(`已套用「${pack.name}」风格套系`, "info");
|
|
258
452
|
});
|
|
259
|
-
card.appendChild(insertBtn);
|
|
260
453
|
|
|
261
454
|
container.appendChild(card);
|
|
262
455
|
}
|
|
263
456
|
}
|
|
264
457
|
|
|
265
|
-
function
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
458
|
+
function applyStylePack(pack: StylePack) {
|
|
459
|
+
const t = pack.theme;
|
|
460
|
+
// Font Size
|
|
461
|
+
inputFontSize.value = t.fontSize;
|
|
462
|
+
valFontSize.textContent = `${t.fontSize}px`;
|
|
463
|
+
syncSegmentedState("seg-font-size", t.fontSize);
|
|
464
|
+
|
|
465
|
+
// Line Height
|
|
466
|
+
inputLineHeight.value = t.lineHeight;
|
|
467
|
+
valLineHeight.textContent = t.lineHeight;
|
|
468
|
+
syncSegmentedState("seg-line-height", t.lineHeight);
|
|
469
|
+
|
|
470
|
+
// Colors
|
|
471
|
+
pickerBrandColor.value = hexBrandColor.value = t.brandColor;
|
|
472
|
+
pickerTextColor.value = hexTextColor.value = t.textColor;
|
|
473
|
+
pickerH2Color.value = hexH2Color.value = t.h2Color;
|
|
474
|
+
pickerH3Color.value = hexH3Color.value = t.h3Color;
|
|
475
|
+
pickerQuoteColor.value = hexQuoteColor.value = t.quoteColor;
|
|
476
|
+
pickerDividerColor.value = hexDividerColor.value = t.dividerColor;
|
|
477
|
+
syncSwatchActiveState(t.brandColor);
|
|
478
|
+
|
|
479
|
+
// H2 Preset
|
|
480
|
+
currentSyntaxPresets.h2 = t.h2Preset;
|
|
481
|
+
syncPillActiveState("seg-h2-preset", t.h2Preset);
|
|
482
|
+
|
|
483
|
+
// H3 Preset
|
|
484
|
+
if (t.h3Preset) currentSyntaxPresets.h3 = t.h3Preset;
|
|
485
|
+
|
|
486
|
+
// Numbered Headings
|
|
487
|
+
switchNumberedHeadings.checked = t.numberedHeadings;
|
|
488
|
+
|
|
273
489
|
scheduleRender(20);
|
|
274
490
|
}
|
|
275
491
|
|
|
492
|
+
function syncSegmentedState(containerId: string, value: string) {
|
|
493
|
+
const container = document.getElementById(containerId);
|
|
494
|
+
if (!container) return;
|
|
495
|
+
container.querySelectorAll(".seg-btn").forEach((btn) => {
|
|
496
|
+
const el = btn as HTMLElement;
|
|
497
|
+
if (el.dataset.val === value) {
|
|
498
|
+
el.classList.add("active");
|
|
499
|
+
} else {
|
|
500
|
+
el.classList.remove("active");
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function syncPillActiveState(containerId: string, presetId: string) {
|
|
506
|
+
const container = document.getElementById(containerId);
|
|
507
|
+
if (!container) return;
|
|
508
|
+
container.querySelectorAll(".pill-btn").forEach((btn) => {
|
|
509
|
+
const el = btn as HTMLElement;
|
|
510
|
+
if (el.dataset.preset === presetId) {
|
|
511
|
+
el.classList.add("active");
|
|
512
|
+
} else {
|
|
513
|
+
el.classList.remove("active");
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function syncSwatchActiveState(hexColor: string) {
|
|
519
|
+
document.querySelectorAll(".swatch-btn").forEach((btn) => {
|
|
520
|
+
const el = btn as HTMLElement;
|
|
521
|
+
if (el.dataset.color?.toLowerCase() === hexColor.toLowerCase()) {
|
|
522
|
+
el.style.transform = "scale(1.2)";
|
|
523
|
+
el.style.boxShadow = "0 0 0 2px var(--brand)";
|
|
524
|
+
} else {
|
|
525
|
+
el.style.transform = "";
|
|
526
|
+
el.style.boxShadow = "0 0 0 1px var(--border)";
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ── Bind Segmented Controls ─────────────────────────────────────────────
|
|
532
|
+
function initSegmentedControls() {
|
|
533
|
+
// Font Size
|
|
534
|
+
const segFontSize = document.getElementById("seg-font-size");
|
|
535
|
+
segFontSize?.querySelectorAll(".seg-btn").forEach((btn) => {
|
|
536
|
+
btn.addEventListener("click", () => {
|
|
537
|
+
const val = (btn as HTMLElement).dataset.val || "16";
|
|
538
|
+
inputFontSize.value = val;
|
|
539
|
+
valFontSize.textContent = `${val}px`;
|
|
540
|
+
syncSegmentedState("seg-font-size", val);
|
|
541
|
+
scheduleRender(20);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Line Height
|
|
546
|
+
const segLineHeight = document.getElementById("seg-line-height");
|
|
547
|
+
segLineHeight?.querySelectorAll(".seg-btn").forEach((btn) => {
|
|
548
|
+
btn.addEventListener("click", () => {
|
|
549
|
+
const val = (btn as HTMLElement).dataset.val || "1.84";
|
|
550
|
+
inputLineHeight.value = val;
|
|
551
|
+
valLineHeight.textContent = val;
|
|
552
|
+
syncSegmentedState("seg-line-height", val);
|
|
553
|
+
scheduleRender(20);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Quick Swatches
|
|
558
|
+
document.querySelectorAll(".swatch-btn").forEach((btn) => {
|
|
559
|
+
btn.addEventListener("click", () => {
|
|
560
|
+
const color = (btn as HTMLElement).dataset.color || "#ca6093";
|
|
561
|
+
pickerBrandColor.value = hexBrandColor.value = color;
|
|
562
|
+
syncSwatchActiveState(color);
|
|
563
|
+
scheduleRender(20);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// H2 Pills
|
|
568
|
+
const segH2 = document.getElementById("seg-h2-preset");
|
|
569
|
+
segH2?.querySelectorAll(".pill-btn").forEach((btn) => {
|
|
570
|
+
btn.addEventListener("click", () => {
|
|
571
|
+
const preset = (btn as HTMLElement).dataset.preset || "pillar";
|
|
572
|
+
currentSyntaxPresets.h2 = preset;
|
|
573
|
+
syncPillActiveState("seg-h2-preset", preset);
|
|
574
|
+
scheduleRender(20);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// ── Syntax Presets in Accordion ─────────────────────────────────────────
|
|
276
580
|
function renderSyntaxPresetControls() {
|
|
277
581
|
const container = document.getElementById("syntax-preset-container");
|
|
278
582
|
if (!container) return;
|
|
279
583
|
container.innerHTML = "";
|
|
280
584
|
|
|
281
585
|
for (const cat of SYNTAX_CATEGORIES) {
|
|
586
|
+
if (cat.key === "h2") continue; // H2 has its own pills at top level
|
|
282
587
|
const group = document.createElement("div");
|
|
283
|
-
group.className = "
|
|
588
|
+
group.className = "control-group";
|
|
284
589
|
|
|
285
590
|
const header = document.createElement("div");
|
|
286
|
-
header.className = "
|
|
287
|
-
|
|
591
|
+
header.className = "control-label";
|
|
288
592
|
const title = document.createElement("span");
|
|
289
|
-
title.className = "preset-title";
|
|
290
593
|
title.textContent = `${cat.icon} ${cat.label}`;
|
|
291
594
|
header.appendChild(title);
|
|
292
595
|
|
|
293
596
|
const hasMultiplePresets = Boolean(cat.presets && cat.presets.length > 1);
|
|
294
|
-
|
|
295
|
-
const extractBtn = document.createElement("button");
|
|
296
|
-
extractBtn.type = "button";
|
|
297
|
-
extractBtn.className = "btn-extract-css";
|
|
298
|
-
extractBtn.textContent = "📋 提取 CSS";
|
|
299
|
-
extractBtn.title = "将此语法的 CSS 追加到底部自定义编辑器,方便微调覆盖";
|
|
300
|
-
extractBtn.addEventListener("click", () => {
|
|
301
|
-
let snippet = "";
|
|
302
|
-
if (hasMultiplePresets && cat.presets) {
|
|
303
|
-
const activeId = currentSyntaxPresets[cat.key] || cat.defaultPresetId || cat.presets[0].id;
|
|
304
|
-
const p = cat.presets.find((preset) => preset.id === activeId) || cat.presets[0];
|
|
305
|
-
snippet = `/* [${cat.label}] - ${p.name} */\n${p.css.trim()}\n`;
|
|
306
|
-
} else {
|
|
307
|
-
const cssContent = cat.css || (cat.presets && cat.presets[0]?.css) || "";
|
|
308
|
-
snippet = `/* [${cat.label}] 基础样式 */\n${cssContent.trim()}\n`;
|
|
309
|
-
}
|
|
310
|
-
if (!snippet) return;
|
|
311
|
-
inputCustomCss.value = inputCustomCss.value ? `${inputCustomCss.value.trim()}\n\n${snippet}` : snippet;
|
|
312
|
-
inputCustomCss.focus();
|
|
313
|
-
inputCustomCss.scrollTop = inputCustomCss.scrollHeight;
|
|
314
|
-
showToast(`已将「${cat.label}」CSS 追加到底部编辑框!`, "info");
|
|
315
|
-
scheduleRender(20);
|
|
316
|
-
});
|
|
317
|
-
header.appendChild(extractBtn);
|
|
318
|
-
|
|
319
|
-
group.appendChild(header);
|
|
320
|
-
|
|
321
597
|
if (hasMultiplePresets && cat.presets) {
|
|
322
598
|
const select = document.createElement("select");
|
|
323
|
-
select.className = "
|
|
324
|
-
select.dataset.category = cat.key;
|
|
325
|
-
|
|
599
|
+
select.className = "select-control full-width";
|
|
326
600
|
for (const preset of cat.presets) {
|
|
327
601
|
const opt = document.createElement("option");
|
|
328
602
|
opt.value = preset.id;
|
|
329
603
|
opt.textContent = preset.name;
|
|
330
604
|
select.appendChild(opt);
|
|
331
605
|
}
|
|
332
|
-
|
|
333
606
|
const activePresetId = currentSyntaxPresets[cat.key] || cat.defaultPresetId || cat.presets[0].id;
|
|
334
607
|
select.value = activePresetId;
|
|
335
|
-
group.appendChild(select);
|
|
336
|
-
|
|
337
|
-
const desc = document.createElement("div");
|
|
338
|
-
desc.className = "preset-desc";
|
|
339
|
-
const activePreset = cat.presets.find((p) => p.id === activePresetId) || cat.presets[0];
|
|
340
|
-
desc.textContent = activePreset?.description || "";
|
|
341
|
-
group.appendChild(desc);
|
|
342
|
-
|
|
343
608
|
select.addEventListener("change", () => {
|
|
344
609
|
currentSyntaxPresets[cat.key] = select.value;
|
|
345
|
-
const p = cat.presets!.find((preset) => preset.id === select.value);
|
|
346
|
-
desc.textContent = p?.description || "";
|
|
347
610
|
scheduleRender(20);
|
|
348
611
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
group.appendChild(desc);
|
|
612
|
+
group.appendChild(header);
|
|
613
|
+
group.appendChild(select);
|
|
614
|
+
} else {
|
|
615
|
+
group.appendChild(header);
|
|
354
616
|
}
|
|
355
617
|
|
|
356
618
|
container.appendChild(group);
|
|
357
619
|
}
|
|
358
620
|
}
|
|
359
621
|
|
|
622
|
+
// ── Tab 2: Syntax Cards ─────────────────────────────────────────────────
|
|
623
|
+
function renderSyntaxCards(plugins: WechatPluginDoc[]) {
|
|
624
|
+
const container = document.getElementById("syntax-card-list");
|
|
625
|
+
if (!container) return;
|
|
626
|
+
container.innerHTML = "";
|
|
627
|
+
|
|
628
|
+
for (const doc of plugins) {
|
|
629
|
+
const card = document.createElement("div");
|
|
630
|
+
card.className = "syntax-card";
|
|
631
|
+
|
|
632
|
+
const header = document.createElement("div");
|
|
633
|
+
header.className = "syntax-card-header";
|
|
634
|
+
|
|
635
|
+
const title = document.createElement("span");
|
|
636
|
+
title.className = "syntax-card-title";
|
|
637
|
+
title.textContent = doc.name;
|
|
638
|
+
|
|
639
|
+
const badge = document.createElement("span");
|
|
640
|
+
badge.className = "syntax-card-badge";
|
|
641
|
+
badge.textContent = doc.category === "inline" ? "行内扩展" : "块级组件";
|
|
642
|
+
|
|
643
|
+
header.appendChild(title);
|
|
644
|
+
header.appendChild(badge);
|
|
645
|
+
card.appendChild(header);
|
|
646
|
+
|
|
647
|
+
const desc = document.createElement("p");
|
|
648
|
+
desc.className = "syntax-card-desc";
|
|
649
|
+
desc.textContent = doc.description;
|
|
650
|
+
card.appendChild(desc);
|
|
651
|
+
|
|
652
|
+
const code = document.createElement("pre");
|
|
653
|
+
code.className = "syntax-code-snippet";
|
|
654
|
+
code.textContent = doc.sampleMarkdown;
|
|
655
|
+
card.appendChild(code);
|
|
656
|
+
|
|
657
|
+
const insertBtn = document.createElement("button");
|
|
658
|
+
insertBtn.className = "syntax-btn-insert";
|
|
659
|
+
insertBtn.textContent = "+ 插入示例到当前文章";
|
|
660
|
+
insertBtn.addEventListener("click", () => {
|
|
661
|
+
insertTextAtCursor(doc.sampleMarkdown);
|
|
662
|
+
showToast(`已插入「${doc.name}」示例`, "info");
|
|
663
|
+
});
|
|
664
|
+
card.appendChild(insertBtn);
|
|
665
|
+
|
|
666
|
+
container.appendChild(card);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function insertTextAtCursor(text: string) {
|
|
671
|
+
if (currentEditorMode === "visual" && mynoEditor) {
|
|
672
|
+
const current = mynoEditor.getMarkdown();
|
|
673
|
+
const updated = `${current.trimEnd()}\n\n${text}\n\n`;
|
|
674
|
+
mynoEditor.setMarkdown(updated);
|
|
675
|
+
editorMarkdown.value = updated;
|
|
676
|
+
} else {
|
|
677
|
+
const start = editorMarkdown.selectionStart;
|
|
678
|
+
const end = editorMarkdown.selectionEnd;
|
|
679
|
+
const original = editorMarkdown.value;
|
|
680
|
+
const insertText = `\n\n${text}\n\n`;
|
|
681
|
+
editorMarkdown.value = original.slice(0, start) + insertText + original.slice(end);
|
|
682
|
+
editorMarkdown.focus();
|
|
683
|
+
editorMarkdown.selectionStart = editorMarkdown.selectionEnd = start + insertText.length;
|
|
684
|
+
}
|
|
685
|
+
scheduleRender(20);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// ── Account & Config Loading ────────────────────────────────────────────
|
|
360
689
|
function applyAccountConfig(accountKey: string) {
|
|
361
690
|
const account = accounts[accountKey];
|
|
362
691
|
if (!account) return;
|
|
363
692
|
|
|
693
|
+
const standalone = Boolean(account.articleThemeStandalone);
|
|
694
|
+
configModeBadge.textContent = standalone ? "独立排版文件" : "账号默认配置";
|
|
695
|
+
configModeBadge.className = `badge ${standalone ? "badge-primary" : "badge-subtle"}`;
|
|
696
|
+
|
|
697
|
+
btnSave.disabled = Boolean(account.articleThemeStandalone);
|
|
698
|
+
btnSave.title = account.articleThemeStandalone
|
|
699
|
+
? "当前账号引用独立配置,请通过「导出配置」保存调整后替换原文件"
|
|
700
|
+
: "保存当前调整至本地 config.json 的当前账号";
|
|
701
|
+
|
|
364
702
|
const exportTheme = account.theme?.exportTheme || {};
|
|
365
703
|
const editorVars = account.theme?.editorVars || {};
|
|
366
704
|
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
valFontSize.textContent = `${
|
|
371
|
-
|
|
372
|
-
|
|
705
|
+
// Font Size
|
|
706
|
+
const fontSize = exportTheme.bodyFontSize?.replace(/px$/, "") || "16";
|
|
707
|
+
inputFontSize.value = fontSize;
|
|
708
|
+
valFontSize.textContent = `${fontSize}px`;
|
|
709
|
+
syncSegmentedState("seg-font-size", fontSize);
|
|
710
|
+
|
|
711
|
+
// Line Height
|
|
712
|
+
const lineHeight = exportTheme.bodyLineHeight || "1.84";
|
|
713
|
+
inputLineHeight.value = lineHeight;
|
|
714
|
+
valLineHeight.textContent = lineHeight;
|
|
715
|
+
syncSegmentedState("seg-line-height", lineHeight);
|
|
716
|
+
|
|
717
|
+
// Letter & Paragraph Spacing
|
|
373
718
|
inputLetterSpacing.value = String(parseFloat(exportTheme.bodyLetterSpacing || "0.012em"));
|
|
374
719
|
valLetterSpacing.textContent = `${inputLetterSpacing.value}em`;
|
|
375
720
|
inputParaSpacing.value = String(parseFloat(exportTheme.bodyParagraphSpacing || "1.18em"));
|
|
376
721
|
valParaSpacing.textContent = `${inputParaSpacing.value}em`;
|
|
377
|
-
paletteSelect.value = "";
|
|
378
|
-
paletteDesc.textContent = "";
|
|
379
|
-
|
|
380
|
-
// 4. Colors
|
|
381
|
-
const brand = editorVars["--brand"] || exportTheme.primaryColor;
|
|
382
|
-
if (brand) {
|
|
383
|
-
pickerBrandColor.value = brand;
|
|
384
|
-
hexBrandColor.value = brand;
|
|
385
|
-
}
|
|
386
|
-
const textColor = exportTheme.bodyColor || editorVars["--text"];
|
|
387
|
-
if (textColor) {
|
|
388
|
-
pickerTextColor.value = textColor;
|
|
389
|
-
hexTextColor.value = textColor;
|
|
390
|
-
}
|
|
391
|
-
if (exportTheme.h2Color) {
|
|
392
|
-
pickerH2Color.value = exportTheme.h2Color;
|
|
393
|
-
hexH2Color.value = exportTheme.h2Color;
|
|
394
|
-
}
|
|
395
|
-
if (exportTheme.h3Color) {
|
|
396
|
-
pickerH3Color.value = exportTheme.h3Color;
|
|
397
|
-
hexH3Color.value = exportTheme.h3Color;
|
|
398
|
-
}
|
|
399
|
-
const quoteColor = exportTheme.blockquoteBorderColor || brand;
|
|
400
|
-
if (quoteColor) {
|
|
401
|
-
pickerQuoteColor.value = quoteColor;
|
|
402
|
-
hexQuoteColor.value = quoteColor;
|
|
403
|
-
}
|
|
404
|
-
const dividerColor = exportTheme.dividerColor || editorVars["--divider"];
|
|
405
|
-
if (dividerColor) {
|
|
406
|
-
pickerDividerColor.value = dividerColor;
|
|
407
|
-
hexDividerColor.value = dividerColor;
|
|
408
|
-
}
|
|
409
|
-
if (account.customCssContent) {
|
|
410
|
-
inputCustomCss.value = account.customCssContent;
|
|
411
|
-
} else {
|
|
412
|
-
inputCustomCss.value = "";
|
|
413
|
-
}
|
|
414
722
|
|
|
415
|
-
//
|
|
723
|
+
// Colors
|
|
724
|
+
const brand = editorVars["--brand"] || exportTheme.primaryColor || "#ca6093";
|
|
725
|
+
pickerBrandColor.value = hexBrandColor.value = brand;
|
|
726
|
+
syncSwatchActiveState(brand);
|
|
727
|
+
|
|
728
|
+
const text = exportTheme.bodyColor || editorVars["--text"] || "#292526";
|
|
729
|
+
pickerTextColor.value = hexTextColor.value = text;
|
|
730
|
+
|
|
731
|
+
if (exportTheme.h2Color) pickerH2Color.value = hexH2Color.value = exportTheme.h2Color;
|
|
732
|
+
if (exportTheme.h3Color) pickerH3Color.value = hexH3Color.value = exportTheme.h3Color;
|
|
733
|
+
|
|
734
|
+
const quote = exportTheme.blockquoteBorderColor || brand;
|
|
735
|
+
pickerQuoteColor.value = hexQuoteColor.value = quote;
|
|
736
|
+
|
|
737
|
+
const divider = exportTheme.dividerColor || editorVars["--divider"] || "#e2dcdf";
|
|
738
|
+
pickerDividerColor.value = hexDividerColor.value = divider;
|
|
739
|
+
|
|
740
|
+
inputCustomCss.value = account.customCssContent || "";
|
|
741
|
+
|
|
742
|
+
// Syntax Presets
|
|
416
743
|
const savedPresets = account.theme?.syntaxPresets || {};
|
|
417
744
|
currentSyntaxPresets = {};
|
|
418
745
|
for (const cat of SYNTAX_CATEGORIES) {
|
|
@@ -420,9 +747,10 @@ function applyAccountConfig(accountKey: string) {
|
|
|
420
747
|
currentSyntaxPresets[cat.key] = savedPresets[cat.key] || cat.defaultPresetId;
|
|
421
748
|
}
|
|
422
749
|
}
|
|
750
|
+
syncPillActiveState("seg-h2-preset", currentSyntaxPresets.h2 || "pillar");
|
|
423
751
|
renderSyntaxPresetControls();
|
|
424
752
|
|
|
425
|
-
//
|
|
753
|
+
// Structure
|
|
426
754
|
const structure = account.theme?.structure || {};
|
|
427
755
|
switchNumberedHeadings.checked = Boolean(structure.numberedHeadings);
|
|
428
756
|
inputHeadingLabel.value = structure.headingLabel || "";
|
|
@@ -431,6 +759,7 @@ function applyAccountConfig(accountKey: string) {
|
|
|
431
759
|
scheduleRender(20);
|
|
432
760
|
}
|
|
433
761
|
|
|
762
|
+
// ── Initialize Color Palettes Dropdown ───────────────────────────────────
|
|
434
763
|
function initColorPalettes() {
|
|
435
764
|
if (!paletteSelect) return;
|
|
436
765
|
paletteSelect.innerHTML = `<option value="">-- 选择推荐调色盘 (一键应用) --</option>`;
|
|
@@ -440,6 +769,7 @@ function initColorPalettes() {
|
|
|
440
769
|
opt.textContent = `${p.name} · ${p.category}`;
|
|
441
770
|
paletteSelect.appendChild(opt);
|
|
442
771
|
}
|
|
772
|
+
|
|
443
773
|
paletteSelect.addEventListener("change", () => {
|
|
444
774
|
const pal = COLOR_PALETTES.find((p) => p.id === paletteSelect.value);
|
|
445
775
|
if (!pal) return;
|
|
@@ -449,14 +779,30 @@ function initColorPalettes() {
|
|
|
449
779
|
pickerH3Color.value = hexH3Color.value = pal.colors.h3;
|
|
450
780
|
pickerQuoteColor.value = hexQuoteColor.value = pal.colors.quote;
|
|
451
781
|
pickerDividerColor.value = hexDividerColor.value = pal.colors.divider;
|
|
782
|
+
syncSwatchActiveState(pal.colors.brand);
|
|
452
783
|
if (paletteDesc) paletteDesc.textContent = pal.description;
|
|
453
|
-
showToast(
|
|
784
|
+
showToast(`已套用「${pal.name}」色盘`, "info");
|
|
454
785
|
scheduleRender(20);
|
|
455
786
|
});
|
|
456
787
|
}
|
|
457
788
|
|
|
789
|
+
// ── Studio Main Initialization ──────────────────────────────────────────
|
|
458
790
|
async function initStudio() {
|
|
459
791
|
initColorPalettes();
|
|
792
|
+
initSegmentedControls();
|
|
793
|
+
renderStylePacks();
|
|
794
|
+
|
|
795
|
+
// Bind Color Pairs
|
|
796
|
+
bindColorPair(pickerBrandColor, hexBrandColor, () => {
|
|
797
|
+
syncSwatchActiveState(pickerBrandColor.value);
|
|
798
|
+
scheduleRender(20);
|
|
799
|
+
});
|
|
800
|
+
bindColorPair(pickerTextColor, hexTextColor, () => scheduleRender(20));
|
|
801
|
+
bindColorPair(pickerH2Color, hexH2Color, () => scheduleRender(20));
|
|
802
|
+
bindColorPair(pickerH3Color, hexH3Color, () => scheduleRender(20));
|
|
803
|
+
bindColorPair(pickerQuoteColor, hexQuoteColor, () => scheduleRender(20));
|
|
804
|
+
bindColorPair(pickerDividerColor, hexDividerColor, () => scheduleRender(20));
|
|
805
|
+
|
|
460
806
|
try {
|
|
461
807
|
const res = await fetch("/api/studio/config");
|
|
462
808
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
@@ -466,7 +812,7 @@ async function initStudio() {
|
|
|
466
812
|
currentAccount = data.defaultAccount || "default";
|
|
467
813
|
pluginsList = data.plugins || [];
|
|
468
814
|
|
|
469
|
-
//
|
|
815
|
+
// Account selector
|
|
470
816
|
accountSelect.innerHTML = "";
|
|
471
817
|
for (const [key, acc] of Object.entries(accounts)) {
|
|
472
818
|
const opt = document.createElement("option");
|
|
@@ -476,36 +822,83 @@ async function initStudio() {
|
|
|
476
822
|
accountSelect.appendChild(opt);
|
|
477
823
|
}
|
|
478
824
|
|
|
479
|
-
|
|
480
|
-
editorMarkdown.value =
|
|
825
|
+
const initialMd = data.sampleMarkdown || "# 微信排版标题\n\n欢迎使用 myno 微信排版工作台!";
|
|
826
|
+
editorMarkdown.value = initialMd;
|
|
827
|
+
|
|
828
|
+
// Mount MynoEditor (Milkdown Crepe)
|
|
829
|
+
if (milkdownEditorRoot) {
|
|
830
|
+
mynoEditor = new MynoEditor({
|
|
831
|
+
root: milkdownEditorRoot,
|
|
832
|
+
defaultValue: initialMd,
|
|
833
|
+
onMarkdownChange: (md) => {
|
|
834
|
+
editorMarkdown.value = md;
|
|
835
|
+
scheduleRender(40);
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
await mynoEditor.create();
|
|
839
|
+
}
|
|
481
840
|
|
|
482
|
-
// Render plugins cheatsheet
|
|
483
841
|
renderSyntaxCards(pluginsList);
|
|
484
|
-
|
|
485
|
-
// Apply active account config
|
|
486
842
|
applyAccountConfig(currentAccount);
|
|
487
843
|
} catch (err) {
|
|
488
844
|
console.error("Failed to load studio config:", err);
|
|
489
|
-
showToast("
|
|
845
|
+
showToast("无法加载服务器配置,使用本地默认设置", "error");
|
|
490
846
|
scheduleRender(20);
|
|
491
847
|
}
|
|
492
848
|
}
|
|
493
849
|
|
|
494
|
-
// Event
|
|
850
|
+
// ── Event Handlers ──────────────────────────────────────────────────────
|
|
495
851
|
accountSelect.addEventListener("change", () => {
|
|
496
852
|
currentAccount = accountSelect.value;
|
|
497
853
|
applyAccountConfig(currentAccount);
|
|
498
854
|
});
|
|
499
855
|
|
|
500
|
-
editorMarkdown.addEventListener("input", () =>
|
|
856
|
+
editorMarkdown.addEventListener("input", () => {
|
|
857
|
+
// 源码模式下 textarea 是编辑面;视觉模式下它隐藏,但仍可能被脚本/按钮更新,需回同步给 Crepe
|
|
858
|
+
if (mynoEditor) {
|
|
859
|
+
mynoEditor.setMarkdown(editorMarkdown.value);
|
|
860
|
+
}
|
|
861
|
+
scheduleRender(40);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
// Dual Mode Switcher
|
|
865
|
+
btnModeVisual.addEventListener("click", () => {
|
|
866
|
+
if (currentEditorMode === "visual") return;
|
|
867
|
+
currentEditorMode = "visual";
|
|
868
|
+
btnModeVisual.classList.add("active");
|
|
869
|
+
btnModeSource.classList.remove("active");
|
|
870
|
+
milkdownEditorRoot.style.display = "block";
|
|
871
|
+
editorMarkdown.style.display = "none";
|
|
872
|
+
if (mynoEditor) {
|
|
873
|
+
mynoEditor.setMarkdown(editorMarkdown.value);
|
|
874
|
+
}
|
|
875
|
+
refreshLiveEditorStyles();
|
|
876
|
+
showToast("已切换至所见即所得编辑模式", "info");
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
btnModeSource.addEventListener("click", () => {
|
|
880
|
+
if (currentEditorMode === "source") return;
|
|
881
|
+
currentEditorMode = "source";
|
|
882
|
+
btnModeSource.classList.add("active");
|
|
883
|
+
btnModeVisual.classList.remove("active");
|
|
884
|
+
if (mynoEditor) {
|
|
885
|
+
editorMarkdown.value = mynoEditor.getMarkdown();
|
|
886
|
+
}
|
|
887
|
+
milkdownEditorRoot.style.display = "none";
|
|
888
|
+
editorMarkdown.style.display = "block";
|
|
889
|
+
editorMarkdown.focus();
|
|
890
|
+
showToast("已切换至 Markdown 源码模式", "info");
|
|
891
|
+
});
|
|
501
892
|
|
|
502
|
-
// Sliders
|
|
893
|
+
// Sliders and Text Inputs
|
|
503
894
|
inputFontSize.addEventListener("input", () => {
|
|
504
895
|
valFontSize.textContent = `${inputFontSize.value}px`;
|
|
896
|
+
syncSegmentedState("seg-font-size", inputFontSize.value);
|
|
505
897
|
scheduleRender(20);
|
|
506
898
|
});
|
|
507
899
|
inputLineHeight.addEventListener("input", () => {
|
|
508
900
|
valLineHeight.textContent = inputLineHeight.value;
|
|
901
|
+
syncSegmentedState("seg-line-height", inputLineHeight.value);
|
|
509
902
|
scheduleRender(20);
|
|
510
903
|
});
|
|
511
904
|
inputLetterSpacing.addEventListener("input", () => {
|
|
@@ -517,15 +910,6 @@ inputParaSpacing.addEventListener("input", () => {
|
|
|
517
910
|
scheduleRender(20);
|
|
518
911
|
});
|
|
519
912
|
|
|
520
|
-
// Color Pickers
|
|
521
|
-
bindColorPair(pickerBrandColor, hexBrandColor, () => scheduleRender(20));
|
|
522
|
-
bindColorPair(pickerTextColor, hexTextColor, () => scheduleRender(20));
|
|
523
|
-
bindColorPair(pickerH2Color, hexH2Color, () => scheduleRender(20));
|
|
524
|
-
bindColorPair(pickerH3Color, hexH3Color, () => scheduleRender(20));
|
|
525
|
-
bindColorPair(pickerQuoteColor, hexQuoteColor, () => scheduleRender(20));
|
|
526
|
-
bindColorPair(pickerDividerColor, hexDividerColor, () => scheduleRender(20));
|
|
527
|
-
|
|
528
|
-
// Switches & text inputs
|
|
529
913
|
switchNumberedHeadings.addEventListener("change", () => scheduleRender(20));
|
|
530
914
|
inputHeadingLabel.addEventListener("input", () => scheduleRender(40));
|
|
531
915
|
inputQuoteLabel.addEventListener("input", () => scheduleRender(40));
|
|
@@ -537,14 +921,14 @@ const tabBtnSyntax = document.getElementById("tab-btn-syntax") as HTMLButtonElem
|
|
|
537
921
|
const tabContentStyle = document.getElementById("tab-content-style") as HTMLElement;
|
|
538
922
|
const tabContentSyntax = document.getElementById("tab-content-syntax") as HTMLElement;
|
|
539
923
|
|
|
540
|
-
tabBtnStyle
|
|
924
|
+
tabBtnStyle?.addEventListener("click", () => {
|
|
541
925
|
tabBtnStyle.classList.add("active");
|
|
542
926
|
tabBtnSyntax.classList.remove("active");
|
|
543
927
|
tabContentStyle.style.display = "block";
|
|
544
928
|
tabContentSyntax.style.display = "none";
|
|
545
929
|
});
|
|
546
930
|
|
|
547
|
-
tabBtnSyntax
|
|
931
|
+
tabBtnSyntax?.addEventListener("click", () => {
|
|
548
932
|
tabBtnSyntax.classList.add("active");
|
|
549
933
|
tabBtnStyle.classList.remove("active");
|
|
550
934
|
tabContentStyle.style.display = "none";
|
|
@@ -552,44 +936,45 @@ tabBtnSyntax.addEventListener("click", () => {
|
|
|
552
936
|
});
|
|
553
937
|
|
|
554
938
|
// Load sample / clear
|
|
555
|
-
|
|
939
|
+
btnLoadSample?.addEventListener("click", async () => {
|
|
556
940
|
const res = await fetch("/api/studio/config");
|
|
557
941
|
const data = (await res.json()) as StudioConfigResponse;
|
|
558
|
-
|
|
942
|
+
const sample = data.sampleMarkdown || "";
|
|
943
|
+
editorMarkdown.value = sample;
|
|
944
|
+
if (mynoEditor) mynoEditor.setMarkdown(sample);
|
|
559
945
|
scheduleRender(20);
|
|
560
|
-
showToast("
|
|
946
|
+
showToast("已加载全语法官方测试稿", "info");
|
|
561
947
|
});
|
|
562
948
|
|
|
563
|
-
|
|
949
|
+
btnClearMd?.addEventListener("click", () => {
|
|
564
950
|
editorMarkdown.value = "";
|
|
951
|
+
if (mynoEditor) mynoEditor.setMarkdown("");
|
|
565
952
|
scheduleRender(20);
|
|
566
953
|
});
|
|
567
954
|
|
|
568
955
|
// Toggle HTML view
|
|
569
|
-
|
|
570
|
-
const btn = e.currentTarget as HTMLButtonElement;
|
|
956
|
+
btnToggleView?.addEventListener("click", () => {
|
|
571
957
|
showingHtmlSource = !showingHtmlSource;
|
|
572
958
|
if (showingHtmlSource) {
|
|
573
959
|
htmlSourceContainer.style.display = "block";
|
|
574
|
-
|
|
960
|
+
btnToggleView.textContent = "📱 返回视图";
|
|
575
961
|
} else {
|
|
576
962
|
htmlSourceContainer.style.display = "none";
|
|
577
|
-
|
|
963
|
+
btnToggleView.textContent = "🔍 HTML 源码";
|
|
578
964
|
}
|
|
579
965
|
});
|
|
580
966
|
|
|
581
967
|
// Reset
|
|
582
|
-
|
|
968
|
+
btnReset?.addEventListener("click", () => {
|
|
583
969
|
applyAccountConfig(currentAccount);
|
|
584
970
|
showToast("已重置为当前账号默认样式", "info");
|
|
585
971
|
});
|
|
586
972
|
|
|
587
973
|
// Save to config.json
|
|
588
|
-
|
|
974
|
+
btnSave?.addEventListener("click", async () => {
|
|
589
975
|
const account = accounts[currentAccount];
|
|
590
976
|
if (!account) return;
|
|
591
977
|
const { structure, ...theme } = readStyleControls();
|
|
592
|
-
// 页首素材由正文模板提供,不写入账号结构配置。
|
|
593
978
|
const { headerImageUrl: _headerImageUrl, ...savedStructure } = structure;
|
|
594
979
|
const payload = {
|
|
595
980
|
account: currentAccount,
|
|
@@ -618,8 +1003,97 @@ document.getElementById("btn-save")?.addEventListener("click", async () => {
|
|
|
618
1003
|
}
|
|
619
1004
|
});
|
|
620
1005
|
|
|
1006
|
+
// Export Config (wechat-style.json)
|
|
1007
|
+
btnExportConfig?.addEventListener("click", () => {
|
|
1008
|
+
const account = accounts[currentAccount];
|
|
1009
|
+
if (!account) return;
|
|
1010
|
+
const styleConfig: StandaloneStyleConfig = {
|
|
1011
|
+
schemaVersion: 1,
|
|
1012
|
+
engineVersion: 1,
|
|
1013
|
+
id: standaloneThemeName || "wechat-style",
|
|
1014
|
+
name: "myno WeChat 排版配置",
|
|
1015
|
+
version: "1.0.0",
|
|
1016
|
+
...readStyleControls(),
|
|
1017
|
+
syntaxPresets: { ...currentSyntaxPresets },
|
|
1018
|
+
css: [account.articleThemeCss, inputCustomCss.value].filter(Boolean).join("\n\n"),
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
const { url, filename } = exportStyleConfigBlob(styleConfig);
|
|
1022
|
+
const link = document.createElement("a");
|
|
1023
|
+
link.href = url;
|
|
1024
|
+
link.download = filename;
|
|
1025
|
+
link.click();
|
|
1026
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
1027
|
+
showToast("已导出独立排版文件,CLI 使用 --article-theme 引用该文件", "success");
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// Import Config (wechat-style.json)
|
|
1031
|
+
btnImportConfig?.addEventListener("click", () => {
|
|
1032
|
+
fileImportConfig.click();
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
fileImportConfig?.addEventListener("change", () => {
|
|
1036
|
+
const file = fileImportConfig.files?.[0];
|
|
1037
|
+
if (!file) return;
|
|
1038
|
+
const reader = new FileReader();
|
|
1039
|
+
reader.onload = () => {
|
|
1040
|
+
const content = reader.result as string;
|
|
1041
|
+
const result = parseStyleConfig(content);
|
|
1042
|
+
if (!result.ok) {
|
|
1043
|
+
showToast(`导入失败: ${result.error}`, "error");
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const cfg = result.config;
|
|
1047
|
+
standaloneThemeName = cfg.id || file.name.replace(/\.json$/i, "");
|
|
1048
|
+
|
|
1049
|
+
// Apply values to UI
|
|
1050
|
+
if (cfg.exportTheme.bodyFontSize) {
|
|
1051
|
+
const fs = cfg.exportTheme.bodyFontSize.replace(/px$/, "");
|
|
1052
|
+
inputFontSize.value = fs;
|
|
1053
|
+
valFontSize.textContent = `${fs}px`;
|
|
1054
|
+
syncSegmentedState("seg-font-size", fs);
|
|
1055
|
+
}
|
|
1056
|
+
if (cfg.exportTheme.bodyLineHeight) {
|
|
1057
|
+
inputLineHeight.value = cfg.exportTheme.bodyLineHeight;
|
|
1058
|
+
valLineHeight.textContent = cfg.exportTheme.bodyLineHeight;
|
|
1059
|
+
syncSegmentedState("seg-line-height", cfg.exportTheme.bodyLineHeight);
|
|
1060
|
+
}
|
|
1061
|
+
if (cfg.exportTheme.primaryColor || cfg.editorVars["--brand"]) {
|
|
1062
|
+
const brand = cfg.exportTheme.primaryColor || cfg.editorVars["--brand"];
|
|
1063
|
+
pickerBrandColor.value = hexBrandColor.value = brand;
|
|
1064
|
+
syncSwatchActiveState(brand);
|
|
1065
|
+
}
|
|
1066
|
+
if (cfg.exportTheme.bodyColor || cfg.editorVars["--text"]) {
|
|
1067
|
+
pickerTextColor.value = hexTextColor.value = cfg.exportTheme.bodyColor || cfg.editorVars["--text"];
|
|
1068
|
+
}
|
|
1069
|
+
if (cfg.exportTheme.h2Color) pickerH2Color.value = hexH2Color.value = cfg.exportTheme.h2Color;
|
|
1070
|
+
if (cfg.exportTheme.h3Color) pickerH3Color.value = hexH3Color.value = cfg.exportTheme.h3Color;
|
|
1071
|
+
if (cfg.exportTheme.blockquoteBorderColor) pickerQuoteColor.value = hexQuoteColor.value = cfg.exportTheme.blockquoteBorderColor;
|
|
1072
|
+
if (cfg.exportTheme.dividerColor) pickerDividerColor.value = hexDividerColor.value = cfg.exportTheme.dividerColor;
|
|
1073
|
+
|
|
1074
|
+
if (cfg.syntaxPresets) {
|
|
1075
|
+
currentSyntaxPresets = { ...cfg.syntaxPresets };
|
|
1076
|
+
if (currentSyntaxPresets.h2) syncPillActiveState("seg-h2-preset", currentSyntaxPresets.h2);
|
|
1077
|
+
}
|
|
1078
|
+
if (cfg.structure) {
|
|
1079
|
+
switchNumberedHeadings.checked = Boolean(cfg.structure.numberedHeadings);
|
|
1080
|
+
inputHeadingLabel.value = cfg.structure.headingLabel || "";
|
|
1081
|
+
inputQuoteLabel.value = cfg.structure.quoteLabel || "";
|
|
1082
|
+
}
|
|
1083
|
+
if (cfg.css) {
|
|
1084
|
+
inputCustomCss.value = cfg.css;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
configModeBadge.textContent = `独立配置: ${file.name}`;
|
|
1088
|
+
configModeBadge.className = "badge badge-primary";
|
|
1089
|
+
showToast(`✅ 成功导入独立配置: ${file.name}`, "success");
|
|
1090
|
+
scheduleRender(20);
|
|
1091
|
+
};
|
|
1092
|
+
reader.readAsText(file);
|
|
1093
|
+
});
|
|
1094
|
+
|
|
621
1095
|
// Copy to WeChat
|
|
622
|
-
|
|
1096
|
+
btnCopy?.addEventListener("click", async () => {
|
|
623
1097
|
if (!latestWechatHtml) {
|
|
624
1098
|
showToast("文章内容为空", "error");
|
|
625
1099
|
return;
|
|
@@ -637,7 +1111,6 @@ document.getElementById("btn-copy")?.addEventListener("click", async () => {
|
|
|
637
1111
|
showToast("📋 已复制富文本!可在微信公众号后台直接粘贴 (Cmd+V)", "success");
|
|
638
1112
|
} catch (err) {
|
|
639
1113
|
console.error("Clipboard write error:", err);
|
|
640
|
-
// Fallback: execCommand copy
|
|
641
1114
|
const listener = (e: ClipboardEvent) => {
|
|
642
1115
|
e.clipboardData?.setData("text/html", latestWechatHtml);
|
|
643
1116
|
e.clipboardData?.setData("text/plain", latestWechatHtml);
|