@akairos/myno 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -2
- package/dist/.vite/manifest.json +1266 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/apl-B4CMkyY2.js +1 -0
- package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/assets/clike-B9uivgTg.js +1 -0
- package/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/assets/crystal-SjHAIU92.js +1 -0
- package/dist/assets/css-BnMrqG3P.js +1 -0
- package/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/assets/d-pRatUO7H.js +1 -0
- package/dist/assets/diff-DbItnlRl.js +1 -0
- package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/assets/dylan-DwRh75JA.js +1 -0
- package/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/assets/ecl-Cabwm37j.js +1 -0
- package/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/assets/elm-vLlmbW-K.js +1 -0
- package/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/assets/factor-kuTfRLto.js +1 -0
- package/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/assets/forth-Ffai-XNe.js +1 -0
- package/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/assets/gas-Bneqetm1.js +1 -0
- package/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/assets/http-DBlCnlav.js +1 -0
- package/dist/assets/idl-BEugSyMb.js +1 -0
- package/dist/assets/index-6xZjfNBY.js +1 -0
- package/dist/assets/index-7jqFg9Mv.js +13 -0
- package/dist/assets/index-AdP2vXVD.js +1 -0
- package/dist/assets/index-BHPv-A3N.js +1 -0
- package/dist/assets/index-BL9wSBtZ.js +1 -0
- package/dist/assets/index-BjawAflQ.js +1 -0
- package/dist/assets/index-C0JZnjgf.js +3 -0
- package/dist/assets/index-C1vHAMos.js +1 -0
- package/dist/assets/index-CAXLlEvK.js +1 -0
- package/dist/assets/index-CDgREgYC.js +2 -0
- package/dist/assets/index-CIYtVPt-.js +1 -0
- package/dist/assets/index-CddMuxp_.js +6 -0
- package/dist/assets/index-CmlqrlxY.js +7 -0
- package/dist/assets/index-Crr2UUqW.js +1 -0
- package/dist/assets/index-D3-l1kuh.js +1 -0
- package/dist/assets/index-D5AbviI1.js +1 -0
- package/dist/assets/index-D8eibMZn.js +1 -0
- package/dist/assets/index-DLh7eEmr.js +1 -0
- package/dist/assets/index-Q5rzr3Yl.js +1 -0
- package/dist/assets/index-RrNfwOco.js +1 -0
- package/dist/assets/index-kgpju2R2.js +1 -0
- package/dist/assets/index-qjQPn-9r.js +1 -0
- package/dist/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/assets/julia-DuME0IfC.js +1 -0
- package/dist/assets/livescript-BwQOo05w.js +1 -0
- package/dist/assets/lua-VAEuO923.js +1 -0
- package/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/assets/mllike-CXdrOF99.js +1 -0
- package/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/assets/mumps-BT43cFF4.js +1 -0
- package/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/assets/nsis-LY0VT_u5.js +1 -0
- package/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/assets/oz-BzwKVEFT.js +1 -0
- package/dist/assets/pascal--L3eBynH.js +1 -0
- package/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/assets/pig-CevX1Tat.js +1 -0
- package/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/assets/properties-C78fOPTZ.js +1 -0
- package/dist/assets/protobuf-ChK-085T.js +1 -0
- package/dist/assets/pug-DeIclll2.js +1 -0
- package/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/assets/python-BuPzkPfP.js +1 -0
- package/dist/assets/q-pXgVlZs6.js +1 -0
- package/dist/assets/r-B6wPVr8A.js +1 -0
- package/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/assets/ruby-BK5aLJev.js +1 -0
- package/dist/assets/sas-B4kiWyti.js +1 -0
- package/dist/assets/scheme-C41bIUwD.js +1 -0
- package/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/assets/solr-DehyRSwq.js +1 -0
- package/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/assets/sql-D0XecflT.js +1 -0
- package/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/assets/style-Cif_gYUW.css +1 -0
- package/dist/assets/style-DG6dI-L6.css +1 -0
- package/dist/assets/stylus-B533Al4x.js +1 -0
- package/dist/assets/swift-BzpIVaGY.js +1 -0
- package/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/assets/textile-CnDTJFAw.js +1 -0
- package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/assets/tiki-DGYXhP31.js +1 -0
- package/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/assets/troff-wAsdV37c.js +1 -0
- package/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/assets/vb-CmGdzxic.js +1 -0
- package/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/assets/velocity-D8B20fx6.js +1 -0
- package/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/assets/xquery-DzFWVndE.js +1 -0
- package/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/editor/article-menu.d.ts +36 -0
- package/dist/editor/article-menu.d.ts.map +1 -0
- package/dist/editor/article-menu.js +251 -0
- package/dist/editor/article-menu.js.map +1 -0
- package/dist/editor/image-upload.d.ts +9 -0
- package/dist/editor/image-upload.d.ts.map +1 -0
- package/dist/editor/image-upload.js +65 -0
- package/dist/editor/image-upload.js.map +1 -0
- package/dist/editor/index.d.ts +4 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +4 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/myno-editor.d.ts +35 -0
- package/dist/editor/myno-editor.d.ts.map +1 -0
- package/dist/editor/myno-editor.js +117 -0
- package/dist/editor/myno-editor.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/src/studio/studio.html +342 -0
- package/dist/studio/color-palettes.d.ts +16 -0
- package/dist/studio/color-palettes.d.ts.map +1 -0
- package/dist/studio/color-palettes.js +129 -0
- package/dist/studio/color-palettes.js.map +1 -0
- package/dist/studio/config-manager.d.ts +34 -0
- package/dist/studio/config-manager.d.ts.map +1 -0
- package/dist/studio/config-manager.js +30 -0
- package/dist/studio/config-manager.js.map +1 -0
- package/dist/studio/studio.d.ts +2 -0
- package/dist/studio/studio.d.ts.map +1 -0
- package/dist/studio/studio.js +1031 -0
- package/dist/studio/studio.js.map +1 -0
- package/dist/studio/style-packs.d.ts +24 -0
- package/dist/studio/style-packs.d.ts.map +1 -0
- package/dist/studio/style-packs.js +179 -0
- package/dist/studio/style-packs.js.map +1 -0
- package/dist/studio/syntax-presets.d.ts +16 -0
- package/dist/studio/syntax-presets.d.ts.map +1 -0
- package/dist/studio/syntax-presets.js +408 -0
- package/dist/studio/syntax-presets.js.map +1 -0
- package/dist/studio/themes.d.ts +32 -0
- package/dist/studio/themes.d.ts.map +1 -0
- package/dist/studio/themes.js +153 -0
- package/dist/studio/themes.js.map +1 -0
- package/dist/studio/wechat-renderer.d.ts +59 -0
- package/dist/studio/wechat-renderer.d.ts.map +1 -0
- package/dist/studio/wechat-renderer.js +797 -0
- package/dist/studio/wechat-renderer.js.map +1 -0
- package/dist/studio.js +2359 -0
- package/package.json +49 -4
|
@@ -0,0 +1,1031 @@
|
|
|
1
|
+
import articleCss from "@akairos/milkdown-article-style/article.css?raw";
|
|
2
|
+
import { Editor, defaultValueCtx, rootCtx } from "@milkdown/kit/core";
|
|
3
|
+
import { commonmark } from "@milkdown/kit/preset/commonmark";
|
|
4
|
+
import { gfm } from "@milkdown/kit/preset/gfm";
|
|
5
|
+
import { getHTML } from "@milkdown/kit/utils";
|
|
6
|
+
import { getCombinedMilkdownPlugins, getCombinedDefaultCss } from "@akairos/milkdown-article-plugins";
|
|
7
|
+
import { MynoEditor } from "../editor/myno-editor";
|
|
8
|
+
import { STYLE_PACKS } from "./style-packs";
|
|
9
|
+
import { SYNTAX_CATEGORIES, buildCombinedPresetsCss } from "./syntax-presets";
|
|
10
|
+
import { COLOR_PALETTES } from "./color-palettes";
|
|
11
|
+
import { parseStyleConfig, exportStyleConfigBlob } from "./config-manager";
|
|
12
|
+
import { renderWechatHtml, buildWechatThemeCss } from "./wechat-renderer";
|
|
13
|
+
let accounts = {};
|
|
14
|
+
let currentAccount = "default";
|
|
15
|
+
let pluginsList = [];
|
|
16
|
+
let latestWechatHtml = "";
|
|
17
|
+
let debounceTimer = null;
|
|
18
|
+
let showingHtmlSource = false;
|
|
19
|
+
let currentSyntaxPresets = {};
|
|
20
|
+
let renderRevision = 0;
|
|
21
|
+
let rendering = false;
|
|
22
|
+
let mynoEditor = null;
|
|
23
|
+
let currentEditorMode = "visual";
|
|
24
|
+
let standaloneThemeName = "";
|
|
25
|
+
// ── DOM Elements ────────────────────────────────────────────────────────
|
|
26
|
+
const accountSelect = document.getElementById("account-select");
|
|
27
|
+
const configModeBadge = document.getElementById("config-mode-badge");
|
|
28
|
+
const editorMarkdown = document.getElementById("editor-markdown");
|
|
29
|
+
const milkdownEditorRoot = document.getElementById("milkdown-editor-root");
|
|
30
|
+
const previewScreen = document.getElementById("preview-screen");
|
|
31
|
+
const editorStats = document.getElementById("editor-stats");
|
|
32
|
+
const htmlSourceContainer = document.getElementById("html-source-container");
|
|
33
|
+
const htmlSourceCode = document.getElementById("html-source-code");
|
|
34
|
+
const toastContainer = document.getElementById("toast-container");
|
|
35
|
+
// Sliders & Text Inputs
|
|
36
|
+
const inputFontSize = document.getElementById("input-font-size");
|
|
37
|
+
const valFontSize = document.getElementById("val-font-size");
|
|
38
|
+
const inputLineHeight = document.getElementById("input-line-height");
|
|
39
|
+
const valLineHeight = document.getElementById("val-line-height");
|
|
40
|
+
const inputLetterSpacing = document.getElementById("input-letter-spacing");
|
|
41
|
+
const valLetterSpacing = document.getElementById("val-letter-spacing");
|
|
42
|
+
const inputParaSpacing = document.getElementById("input-para-spacing");
|
|
43
|
+
const valParaSpacing = document.getElementById("val-para-spacing");
|
|
44
|
+
const paletteSelect = document.getElementById("palette-select");
|
|
45
|
+
const paletteDesc = document.getElementById("palette-desc");
|
|
46
|
+
const pickerBrandColor = document.getElementById("picker-brand-color");
|
|
47
|
+
const hexBrandColor = document.getElementById("hex-brand-color");
|
|
48
|
+
const pickerTextColor = document.getElementById("picker-text-color");
|
|
49
|
+
const hexTextColor = document.getElementById("hex-text-color");
|
|
50
|
+
const pickerH2Color = document.getElementById("picker-h2-color");
|
|
51
|
+
const hexH2Color = document.getElementById("hex-h2-color");
|
|
52
|
+
const pickerH3Color = document.getElementById("picker-h3-color");
|
|
53
|
+
const hexH3Color = document.getElementById("hex-h3-color");
|
|
54
|
+
const pickerQuoteColor = document.getElementById("picker-quote-color");
|
|
55
|
+
const hexQuoteColor = document.getElementById("hex-quote-color");
|
|
56
|
+
const pickerDividerColor = document.getElementById("picker-divider-color");
|
|
57
|
+
const hexDividerColor = document.getElementById("hex-divider-color");
|
|
58
|
+
const switchNumberedHeadings = document.getElementById("switch-numbered-headings");
|
|
59
|
+
const inputHeadingLabel = document.getElementById("input-heading-label");
|
|
60
|
+
const inputQuoteLabel = document.getElementById("input-quote-label");
|
|
61
|
+
const inputCustomCss = document.getElementById("input-custom-css");
|
|
62
|
+
const btnModeVisual = document.getElementById("btn-mode-visual");
|
|
63
|
+
const btnModeSource = document.getElementById("btn-mode-source");
|
|
64
|
+
const fileImportConfig = document.getElementById("file-import-config");
|
|
65
|
+
const btnImportConfig = document.getElementById("btn-import-config");
|
|
66
|
+
const btnExportConfig = document.getElementById("btn-export-config");
|
|
67
|
+
const btnSave = document.getElementById("btn-save");
|
|
68
|
+
const btnReset = document.getElementById("btn-reset");
|
|
69
|
+
const btnCopy = document.getElementById("btn-copy");
|
|
70
|
+
const btnToggleView = document.getElementById("btn-toggle-view");
|
|
71
|
+
const btnLoadSample = document.getElementById("btn-load-sample");
|
|
72
|
+
const btnClearMd = document.getElementById("btn-clear-md");
|
|
73
|
+
// ── Toast System ────────────────────────────────────────────────────────
|
|
74
|
+
function showToast(message, type = "info") {
|
|
75
|
+
if (!toastContainer)
|
|
76
|
+
return;
|
|
77
|
+
const toast = document.createElement("div");
|
|
78
|
+
toast.className = `toast ${type === "success" ? "toast-success" : type === "error" ? "toast-error" : ""}`;
|
|
79
|
+
toast.textContent = message;
|
|
80
|
+
toastContainer.appendChild(toast);
|
|
81
|
+
requestAnimationFrame(() => toast.classList.add("show"));
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
toast.classList.remove("show");
|
|
84
|
+
setTimeout(() => toast.remove(), 300);
|
|
85
|
+
}, 3000);
|
|
86
|
+
}
|
|
87
|
+
// ── Color Picker Pairing ────────────────────────────────────────────────
|
|
88
|
+
function bindColorPair(picker, hex, onChange) {
|
|
89
|
+
picker.addEventListener("input", () => {
|
|
90
|
+
hex.value = picker.value;
|
|
91
|
+
onChange();
|
|
92
|
+
});
|
|
93
|
+
hex.addEventListener("input", () => {
|
|
94
|
+
if (/^#[0-9a-f]{6}$/i.test(hex.value)) {
|
|
95
|
+
picker.value = hex.value;
|
|
96
|
+
onChange();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function updateStats() {
|
|
101
|
+
const text = getActiveMarkdown();
|
|
102
|
+
const chars = text.replace(/\s+/g, "").length;
|
|
103
|
+
const minutes = Math.max(1, Math.round(chars / 400));
|
|
104
|
+
if (editorStats) {
|
|
105
|
+
editorStats.textContent = `字数: ${chars.toLocaleString()} 字 | 预计阅读: ${minutes} 分钟`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function getActiveMarkdown() {
|
|
109
|
+
if (currentEditorMode === "visual" && mynoEditor) {
|
|
110
|
+
return mynoEditor.getMarkdown();
|
|
111
|
+
}
|
|
112
|
+
return editorMarkdown.value;
|
|
113
|
+
}
|
|
114
|
+
function stripLeadingH1(markdown) {
|
|
115
|
+
return markdown.replace(/^#\s+[^\r\n]*(?:\r?\n|$)/, "");
|
|
116
|
+
}
|
|
117
|
+
function extractFrontmatter(markdown) {
|
|
118
|
+
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
119
|
+
if (!match)
|
|
120
|
+
return { frontmatter: null, content: markdown };
|
|
121
|
+
return { frontmatter: {}, content: markdown.slice(match[0].length) };
|
|
122
|
+
}
|
|
123
|
+
/** 品牌色衍生的卡片底色(--brand-bg 系),非 6 位 hex 时返回 null 以保留账号默认值。 */
|
|
124
|
+
function brandTint(hex, alpha) {
|
|
125
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
126
|
+
if (!m)
|
|
127
|
+
return null;
|
|
128
|
+
const n = parseInt(m[1], 16);
|
|
129
|
+
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
|
|
130
|
+
}
|
|
131
|
+
// ── Read Style Controls ─────────────────────────────────────────────────
|
|
132
|
+
function readStyleControls() {
|
|
133
|
+
const account = accounts[currentAccount] || {
|
|
134
|
+
theme: { exportTheme: {}, editorVars: {}, structure: {} },
|
|
135
|
+
};
|
|
136
|
+
const exportTheme = {
|
|
137
|
+
...account.theme.exportTheme,
|
|
138
|
+
bodyFontSize: `${inputFontSize.value}px`,
|
|
139
|
+
bodyParagraphSpacing: `${inputParaSpacing.value}em`,
|
|
140
|
+
bodyColor: hexTextColor.value,
|
|
141
|
+
h2Color: hexH2Color.value,
|
|
142
|
+
h3Color: hexH3Color.value,
|
|
143
|
+
primaryColor: hexBrandColor.value,
|
|
144
|
+
dividerColor: hexDividerColor.value,
|
|
145
|
+
blockquoteBorderColor: hexQuoteColor.value,
|
|
146
|
+
bodyLineHeight: inputLineHeight.value,
|
|
147
|
+
bodyLetterSpacing: `${inputLetterSpacing.value}em`,
|
|
148
|
+
};
|
|
149
|
+
const brandBg = brandTint(hexBrandColor.value, 0.08);
|
|
150
|
+
const brandBgDeep = brandBg && brandTint(hexBrandColor.value, 0.12);
|
|
151
|
+
const editorVars = {
|
|
152
|
+
...account.theme.editorVars,
|
|
153
|
+
"--brand": hexBrandColor.value,
|
|
154
|
+
...(brandBg && brandBgDeep ? { "--brand-bg": brandBg, "--brand-bg-deep": brandBgDeep } : {}),
|
|
155
|
+
"--text": hexTextColor.value,
|
|
156
|
+
"--divider": hexDividerColor.value,
|
|
157
|
+
};
|
|
158
|
+
const structure = {
|
|
159
|
+
...account.theme.structure,
|
|
160
|
+
numberedHeadings: switchNumberedHeadings.checked,
|
|
161
|
+
headingLabel: inputHeadingLabel.value.trim(),
|
|
162
|
+
quoteLabel: inputQuoteLabel.value.trim(),
|
|
163
|
+
};
|
|
164
|
+
return { editorVars, exportTheme, structure };
|
|
165
|
+
}
|
|
166
|
+
// ── Live Editor Style Synchronization ───────────────────────────────────
|
|
167
|
+
function applyLiveEditorStyles(style, customCss) {
|
|
168
|
+
let styleTag = document.getElementById("studio-live-editor-css");
|
|
169
|
+
if (!styleTag) {
|
|
170
|
+
styleTag = document.createElement("style");
|
|
171
|
+
styleTag.id = "studio-live-editor-css";
|
|
172
|
+
document.head.appendChild(styleTag);
|
|
173
|
+
}
|
|
174
|
+
const themeCss = buildWechatThemeCss(style.editorVars, style.exportTheme);
|
|
175
|
+
const pluginCss = getCombinedDefaultCss();
|
|
176
|
+
// Structure decorations for editor (simulating heading labels / counters)
|
|
177
|
+
let structureCss = "";
|
|
178
|
+
if (style.structure.numberedHeadings) {
|
|
179
|
+
const labelPrefix = style.structure.headingLabel ? `"${style.structure.headingLabel} " ` : "";
|
|
180
|
+
structureCss += `
|
|
181
|
+
.milkdown .editor {
|
|
182
|
+
counter-reset: wx-heading-counter;
|
|
183
|
+
}
|
|
184
|
+
.milkdown .editor h2::before {
|
|
185
|
+
counter-increment: wx-heading-counter;
|
|
186
|
+
content: ${labelPrefix}counter(wx-heading-counter, decimal-leading-zero) " ";
|
|
187
|
+
margin-right: 0.3em;
|
|
188
|
+
opacity: 0.7;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
else if (style.structure.headingLabel) {
|
|
194
|
+
structureCss += `
|
|
195
|
+
.milkdown .editor h2::before {
|
|
196
|
+
content: "${style.structure.headingLabel} ";
|
|
197
|
+
margin-right: 0.3em;
|
|
198
|
+
opacity: 0.7;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
if (style.structure.quoteLabel) {
|
|
204
|
+
structureCss += `
|
|
205
|
+
.milkdown .editor blockquote::before {
|
|
206
|
+
content: "${style.structure.quoteLabel}";
|
|
207
|
+
display: block;
|
|
208
|
+
font-size: 11px;
|
|
209
|
+
font-weight: 600;
|
|
210
|
+
letter-spacing: 0.05em;
|
|
211
|
+
color: var(--wx-brand-ink, var(--wx-brand-accent, #ca6093));
|
|
212
|
+
margin-bottom: 6px;
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
}
|
|
217
|
+
styleTag.textContent = `
|
|
218
|
+
/* ── Crepe resets: 后置于运行时,同特异性即可覆盖 Crepe 主题;不可用 !important,否则压过下方预设 ── */
|
|
219
|
+
.milkdown .ProseMirror {
|
|
220
|
+
padding: 0;
|
|
221
|
+
}
|
|
222
|
+
.milkdown .ProseMirror h1,
|
|
223
|
+
.milkdown .ProseMirror h2,
|
|
224
|
+
.milkdown .ProseMirror h3,
|
|
225
|
+
.milkdown .ProseMirror h4,
|
|
226
|
+
.milkdown .ProseMirror h5,
|
|
227
|
+
.milkdown .ProseMirror h6 {
|
|
228
|
+
padding-top: 0;
|
|
229
|
+
padding-bottom: 0;
|
|
230
|
+
}
|
|
231
|
+
.milkdown .ProseMirror p {
|
|
232
|
+
padding: 0;
|
|
233
|
+
}
|
|
234
|
+
.milkdown .ProseMirror blockquote {
|
|
235
|
+
position: static;
|
|
236
|
+
box-sizing: border-box;
|
|
237
|
+
}
|
|
238
|
+
.milkdown .ProseMirror blockquote::before {
|
|
239
|
+
display: none;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ── Dynamic WeChat Theme Variables & Container Styling ── */
|
|
243
|
+
${themeCss}
|
|
244
|
+
|
|
245
|
+
/* ── Structure Enhancements (Numbered Headings, Chapter / Quote Labels) ── */
|
|
246
|
+
${structureCss}
|
|
247
|
+
|
|
248
|
+
/* ── Article Plugins Styling (Callouts, Badges, Kbd, Article Cards, etc.) ── */
|
|
249
|
+
${pluginCss}
|
|
250
|
+
|
|
251
|
+
/* ── Active Syntax Presets & Custom CSS Overrides ── */
|
|
252
|
+
${customCss}
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
function refreshLiveEditorStyles() {
|
|
256
|
+
const style = readStyleControls();
|
|
257
|
+
const account = accounts[currentAccount];
|
|
258
|
+
const customCss = [
|
|
259
|
+
buildCombinedPresetsCss(currentSyntaxPresets),
|
|
260
|
+
account?.articleThemeCss,
|
|
261
|
+
inputCustomCss.value,
|
|
262
|
+
].filter(Boolean).join("\n\n");
|
|
263
|
+
applyLiveEditorStyles(style, customCss);
|
|
264
|
+
}
|
|
265
|
+
// ── Inlined WeChat Render Pass ──────────────────────────────────────────
|
|
266
|
+
async function doRender() {
|
|
267
|
+
if (rendering)
|
|
268
|
+
return;
|
|
269
|
+
rendering = true;
|
|
270
|
+
const revision = renderRevision;
|
|
271
|
+
const rawMd = getActiveMarkdown();
|
|
272
|
+
const markdown = stripLeadingH1(extractFrontmatter(rawMd).content).trimStart();
|
|
273
|
+
const style = readStyleControls();
|
|
274
|
+
const account = accounts[currentAccount];
|
|
275
|
+
const customCss = [
|
|
276
|
+
buildCombinedPresetsCss(currentSyntaxPresets),
|
|
277
|
+
account?.articleThemeCss,
|
|
278
|
+
inputCustomCss.value,
|
|
279
|
+
].filter(Boolean).join("\n\n");
|
|
280
|
+
applyLiveEditorStyles(style, customCss);
|
|
281
|
+
updateStats();
|
|
282
|
+
try {
|
|
283
|
+
let semanticHtml = "";
|
|
284
|
+
if (currentEditorMode === "visual" && mynoEditor) {
|
|
285
|
+
const crepe = mynoEditor.getCrepe();
|
|
286
|
+
if (crepe) {
|
|
287
|
+
try {
|
|
288
|
+
semanticHtml = crepe.editor.action(getHTML());
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// ignore and fallback
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!semanticHtml) {
|
|
296
|
+
const hiddenHost = document.getElementById("studio-editor-host") || document.createElement("div");
|
|
297
|
+
hiddenHost.innerHTML = "";
|
|
298
|
+
const editor = Editor.make()
|
|
299
|
+
.config((ctx) => {
|
|
300
|
+
ctx.set(rootCtx, hiddenHost);
|
|
301
|
+
ctx.set(defaultValueCtx, markdown);
|
|
302
|
+
})
|
|
303
|
+
.use(commonmark)
|
|
304
|
+
.use(gfm)
|
|
305
|
+
.use(getCombinedMilkdownPlugins());
|
|
306
|
+
try {
|
|
307
|
+
await editor.create();
|
|
308
|
+
semanticHtml = editor.action(getHTML());
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
await editor.destroy();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const inlinedHtml = await renderWechatHtml({
|
|
315
|
+
semanticHtml,
|
|
316
|
+
baseCss: articleCss,
|
|
317
|
+
customCss,
|
|
318
|
+
structure: style.structure,
|
|
319
|
+
editorVars: style.editorVars,
|
|
320
|
+
theme: style.exportTheme,
|
|
321
|
+
strictImageDecode: false,
|
|
322
|
+
});
|
|
323
|
+
if (revision !== renderRevision)
|
|
324
|
+
return;
|
|
325
|
+
latestWechatHtml = inlinedHtml;
|
|
326
|
+
if (previewScreen)
|
|
327
|
+
previewScreen.innerHTML = inlinedHtml;
|
|
328
|
+
if (htmlSourceCode)
|
|
329
|
+
htmlSourceCode.value = inlinedHtml;
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (revision !== renderRevision)
|
|
333
|
+
return;
|
|
334
|
+
console.error("myno render error:", error);
|
|
335
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
336
|
+
if (previewScreen)
|
|
337
|
+
previewScreen.textContent = `渲染错误: ${message}`;
|
|
338
|
+
if (htmlSourceCode)
|
|
339
|
+
htmlSourceCode.value = "";
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
rendering = false;
|
|
343
|
+
if (revision !== renderRevision) {
|
|
344
|
+
if (debounceTimer)
|
|
345
|
+
clearTimeout(debounceTimer);
|
|
346
|
+
void doRender();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function scheduleRender(delay = 60) {
|
|
351
|
+
renderRevision += 1;
|
|
352
|
+
latestWechatHtml = "";
|
|
353
|
+
refreshLiveEditorStyles();
|
|
354
|
+
if (debounceTimer)
|
|
355
|
+
clearTimeout(debounceTimer);
|
|
356
|
+
debounceTimer = setTimeout(() => {
|
|
357
|
+
void doRender();
|
|
358
|
+
}, delay);
|
|
359
|
+
}
|
|
360
|
+
// ── Style Packs (One-Click Application) ──────────────────────────────────
|
|
361
|
+
function renderStylePacks() {
|
|
362
|
+
const container = document.getElementById("style-packs-grid");
|
|
363
|
+
if (!container)
|
|
364
|
+
return;
|
|
365
|
+
container.innerHTML = "";
|
|
366
|
+
for (const pack of STYLE_PACKS) {
|
|
367
|
+
const card = document.createElement("div");
|
|
368
|
+
card.className = "style-pack-card";
|
|
369
|
+
card.dataset.packId = pack.id;
|
|
370
|
+
const head = document.createElement("div");
|
|
371
|
+
head.className = "style-pack-head";
|
|
372
|
+
const titleWrap = document.createElement("div");
|
|
373
|
+
const name = document.createElement("span");
|
|
374
|
+
name.className = "style-pack-name";
|
|
375
|
+
name.textContent = pack.name;
|
|
376
|
+
const sub = document.createElement("span");
|
|
377
|
+
sub.className = "style-pack-subtitle";
|
|
378
|
+
sub.textContent = ` · ${pack.subtitle}`;
|
|
379
|
+
titleWrap.appendChild(name);
|
|
380
|
+
titleWrap.appendChild(sub);
|
|
381
|
+
const swatches = document.createElement("div");
|
|
382
|
+
swatches.className = "style-pack-swatches";
|
|
383
|
+
for (const color of pack.swatch) {
|
|
384
|
+
const dot = document.createElement("span");
|
|
385
|
+
dot.className = "swatch-dot";
|
|
386
|
+
dot.style.background = color;
|
|
387
|
+
swatches.appendChild(dot);
|
|
388
|
+
}
|
|
389
|
+
head.appendChild(titleWrap);
|
|
390
|
+
head.appendChild(swatches);
|
|
391
|
+
card.appendChild(head);
|
|
392
|
+
const desc = document.createElement("div");
|
|
393
|
+
desc.className = "style-pack-desc";
|
|
394
|
+
desc.textContent = pack.description;
|
|
395
|
+
card.appendChild(desc);
|
|
396
|
+
card.addEventListener("click", () => {
|
|
397
|
+
applyStylePack(pack);
|
|
398
|
+
document.querySelectorAll(".style-pack-card").forEach((c) => c.classList.remove("active"));
|
|
399
|
+
card.classList.add("active");
|
|
400
|
+
showToast(`已套用「${pack.name}」风格套系`, "info");
|
|
401
|
+
});
|
|
402
|
+
container.appendChild(card);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function applyStylePack(pack) {
|
|
406
|
+
const t = pack.theme;
|
|
407
|
+
// Font Size
|
|
408
|
+
inputFontSize.value = t.fontSize;
|
|
409
|
+
valFontSize.textContent = `${t.fontSize}px`;
|
|
410
|
+
syncSegmentedState("seg-font-size", t.fontSize);
|
|
411
|
+
// Line Height
|
|
412
|
+
inputLineHeight.value = t.lineHeight;
|
|
413
|
+
valLineHeight.textContent = t.lineHeight;
|
|
414
|
+
syncSegmentedState("seg-line-height", t.lineHeight);
|
|
415
|
+
// Colors
|
|
416
|
+
pickerBrandColor.value = hexBrandColor.value = t.brandColor;
|
|
417
|
+
pickerTextColor.value = hexTextColor.value = t.textColor;
|
|
418
|
+
pickerH2Color.value = hexH2Color.value = t.h2Color;
|
|
419
|
+
pickerH3Color.value = hexH3Color.value = t.h3Color;
|
|
420
|
+
pickerQuoteColor.value = hexQuoteColor.value = t.quoteColor;
|
|
421
|
+
pickerDividerColor.value = hexDividerColor.value = t.dividerColor;
|
|
422
|
+
syncSwatchActiveState(t.brandColor);
|
|
423
|
+
// H2 Preset
|
|
424
|
+
currentSyntaxPresets.h2 = t.h2Preset;
|
|
425
|
+
syncPillActiveState("seg-h2-preset", t.h2Preset);
|
|
426
|
+
// H3 Preset
|
|
427
|
+
if (t.h3Preset)
|
|
428
|
+
currentSyntaxPresets.h3 = t.h3Preset;
|
|
429
|
+
// Numbered Headings
|
|
430
|
+
switchNumberedHeadings.checked = t.numberedHeadings;
|
|
431
|
+
scheduleRender(20);
|
|
432
|
+
}
|
|
433
|
+
function syncSegmentedState(containerId, value) {
|
|
434
|
+
const container = document.getElementById(containerId);
|
|
435
|
+
if (!container)
|
|
436
|
+
return;
|
|
437
|
+
container.querySelectorAll(".seg-btn").forEach((btn) => {
|
|
438
|
+
const el = btn;
|
|
439
|
+
if (el.dataset.val === value) {
|
|
440
|
+
el.classList.add("active");
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
el.classList.remove("active");
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function syncPillActiveState(containerId, presetId) {
|
|
448
|
+
const container = document.getElementById(containerId);
|
|
449
|
+
if (!container)
|
|
450
|
+
return;
|
|
451
|
+
container.querySelectorAll(".pill-btn").forEach((btn) => {
|
|
452
|
+
const el = btn;
|
|
453
|
+
if (el.dataset.preset === presetId) {
|
|
454
|
+
el.classList.add("active");
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
el.classList.remove("active");
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
function syncSwatchActiveState(hexColor) {
|
|
462
|
+
document.querySelectorAll(".swatch-btn").forEach((btn) => {
|
|
463
|
+
const el = btn;
|
|
464
|
+
if (el.dataset.color?.toLowerCase() === hexColor.toLowerCase()) {
|
|
465
|
+
el.style.transform = "scale(1.2)";
|
|
466
|
+
el.style.boxShadow = "0 0 0 2px var(--brand)";
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
el.style.transform = "";
|
|
470
|
+
el.style.boxShadow = "0 0 0 1px var(--border)";
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
// ── Bind Segmented Controls ─────────────────────────────────────────────
|
|
475
|
+
function initSegmentedControls() {
|
|
476
|
+
// Font Size
|
|
477
|
+
const segFontSize = document.getElementById("seg-font-size");
|
|
478
|
+
segFontSize?.querySelectorAll(".seg-btn").forEach((btn) => {
|
|
479
|
+
btn.addEventListener("click", () => {
|
|
480
|
+
const val = btn.dataset.val || "16";
|
|
481
|
+
inputFontSize.value = val;
|
|
482
|
+
valFontSize.textContent = `${val}px`;
|
|
483
|
+
syncSegmentedState("seg-font-size", val);
|
|
484
|
+
scheduleRender(20);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
// Line Height
|
|
488
|
+
const segLineHeight = document.getElementById("seg-line-height");
|
|
489
|
+
segLineHeight?.querySelectorAll(".seg-btn").forEach((btn) => {
|
|
490
|
+
btn.addEventListener("click", () => {
|
|
491
|
+
const val = btn.dataset.val || "1.84";
|
|
492
|
+
inputLineHeight.value = val;
|
|
493
|
+
valLineHeight.textContent = val;
|
|
494
|
+
syncSegmentedState("seg-line-height", val);
|
|
495
|
+
scheduleRender(20);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
// Quick Swatches
|
|
499
|
+
document.querySelectorAll(".swatch-btn").forEach((btn) => {
|
|
500
|
+
btn.addEventListener("click", () => {
|
|
501
|
+
const color = btn.dataset.color || "#ca6093";
|
|
502
|
+
pickerBrandColor.value = hexBrandColor.value = color;
|
|
503
|
+
syncSwatchActiveState(color);
|
|
504
|
+
scheduleRender(20);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
// H2 Pills
|
|
508
|
+
const segH2 = document.getElementById("seg-h2-preset");
|
|
509
|
+
segH2?.querySelectorAll(".pill-btn").forEach((btn) => {
|
|
510
|
+
btn.addEventListener("click", () => {
|
|
511
|
+
const preset = btn.dataset.preset || "pillar";
|
|
512
|
+
currentSyntaxPresets.h2 = preset;
|
|
513
|
+
syncPillActiveState("seg-h2-preset", preset);
|
|
514
|
+
scheduleRender(20);
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
// ── Syntax Presets in Accordion ─────────────────────────────────────────
|
|
519
|
+
function renderSyntaxPresetControls() {
|
|
520
|
+
const container = document.getElementById("syntax-preset-container");
|
|
521
|
+
if (!container)
|
|
522
|
+
return;
|
|
523
|
+
container.innerHTML = "";
|
|
524
|
+
for (const cat of SYNTAX_CATEGORIES) {
|
|
525
|
+
if (cat.key === "h2")
|
|
526
|
+
continue; // H2 has its own pills at top level
|
|
527
|
+
const group = document.createElement("div");
|
|
528
|
+
group.className = "control-group";
|
|
529
|
+
const header = document.createElement("div");
|
|
530
|
+
header.className = "control-label";
|
|
531
|
+
const title = document.createElement("span");
|
|
532
|
+
title.textContent = `${cat.icon} ${cat.label}`;
|
|
533
|
+
header.appendChild(title);
|
|
534
|
+
const hasMultiplePresets = Boolean(cat.presets && cat.presets.length > 1);
|
|
535
|
+
if (hasMultiplePresets && cat.presets) {
|
|
536
|
+
const select = document.createElement("select");
|
|
537
|
+
select.className = "select-control full-width";
|
|
538
|
+
for (const preset of cat.presets) {
|
|
539
|
+
const opt = document.createElement("option");
|
|
540
|
+
opt.value = preset.id;
|
|
541
|
+
opt.textContent = preset.name;
|
|
542
|
+
select.appendChild(opt);
|
|
543
|
+
}
|
|
544
|
+
const activePresetId = currentSyntaxPresets[cat.key] || cat.defaultPresetId || cat.presets[0].id;
|
|
545
|
+
select.value = activePresetId;
|
|
546
|
+
select.addEventListener("change", () => {
|
|
547
|
+
currentSyntaxPresets[cat.key] = select.value;
|
|
548
|
+
scheduleRender(20);
|
|
549
|
+
});
|
|
550
|
+
group.appendChild(header);
|
|
551
|
+
group.appendChild(select);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
group.appendChild(header);
|
|
555
|
+
}
|
|
556
|
+
container.appendChild(group);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// ── Tab 2: Syntax Cards ─────────────────────────────────────────────────
|
|
560
|
+
function renderSyntaxCards(plugins) {
|
|
561
|
+
const container = document.getElementById("syntax-card-list");
|
|
562
|
+
if (!container)
|
|
563
|
+
return;
|
|
564
|
+
container.innerHTML = "";
|
|
565
|
+
for (const doc of plugins) {
|
|
566
|
+
const card = document.createElement("div");
|
|
567
|
+
card.className = "syntax-card";
|
|
568
|
+
const header = document.createElement("div");
|
|
569
|
+
header.className = "syntax-card-header";
|
|
570
|
+
const title = document.createElement("span");
|
|
571
|
+
title.className = "syntax-card-title";
|
|
572
|
+
title.textContent = doc.name;
|
|
573
|
+
const badge = document.createElement("span");
|
|
574
|
+
badge.className = "syntax-card-badge";
|
|
575
|
+
badge.textContent = doc.category === "inline" ? "行内扩展" : "块级组件";
|
|
576
|
+
header.appendChild(title);
|
|
577
|
+
header.appendChild(badge);
|
|
578
|
+
card.appendChild(header);
|
|
579
|
+
const desc = document.createElement("p");
|
|
580
|
+
desc.className = "syntax-card-desc";
|
|
581
|
+
desc.textContent = doc.description;
|
|
582
|
+
card.appendChild(desc);
|
|
583
|
+
const code = document.createElement("pre");
|
|
584
|
+
code.className = "syntax-code-snippet";
|
|
585
|
+
code.textContent = doc.sampleMarkdown;
|
|
586
|
+
card.appendChild(code);
|
|
587
|
+
const insertBtn = document.createElement("button");
|
|
588
|
+
insertBtn.className = "syntax-btn-insert";
|
|
589
|
+
insertBtn.textContent = "+ 插入示例到当前文章";
|
|
590
|
+
insertBtn.addEventListener("click", () => {
|
|
591
|
+
insertTextAtCursor(doc.sampleMarkdown);
|
|
592
|
+
showToast(`已插入「${doc.name}」示例`, "info");
|
|
593
|
+
});
|
|
594
|
+
card.appendChild(insertBtn);
|
|
595
|
+
container.appendChild(card);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function insertTextAtCursor(text) {
|
|
599
|
+
if (currentEditorMode === "visual" && mynoEditor) {
|
|
600
|
+
const current = mynoEditor.getMarkdown();
|
|
601
|
+
const updated = `${current.trimEnd()}\n\n${text}\n\n`;
|
|
602
|
+
mynoEditor.setMarkdown(updated);
|
|
603
|
+
editorMarkdown.value = updated;
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
const start = editorMarkdown.selectionStart;
|
|
607
|
+
const end = editorMarkdown.selectionEnd;
|
|
608
|
+
const original = editorMarkdown.value;
|
|
609
|
+
const insertText = `\n\n${text}\n\n`;
|
|
610
|
+
editorMarkdown.value = original.slice(0, start) + insertText + original.slice(end);
|
|
611
|
+
editorMarkdown.focus();
|
|
612
|
+
editorMarkdown.selectionStart = editorMarkdown.selectionEnd = start + insertText.length;
|
|
613
|
+
}
|
|
614
|
+
scheduleRender(20);
|
|
615
|
+
}
|
|
616
|
+
// ── Account & Config Loading ────────────────────────────────────────────
|
|
617
|
+
function applyAccountConfig(accountKey) {
|
|
618
|
+
const account = accounts[accountKey];
|
|
619
|
+
if (!account)
|
|
620
|
+
return;
|
|
621
|
+
const standalone = Boolean(account.articleThemeStandalone);
|
|
622
|
+
configModeBadge.textContent = standalone ? "独立排版文件" : "账号默认配置";
|
|
623
|
+
configModeBadge.className = `badge ${standalone ? "badge-primary" : "badge-subtle"}`;
|
|
624
|
+
btnSave.disabled = Boolean(account.articleThemeStandalone);
|
|
625
|
+
btnSave.title = account.articleThemeStandalone
|
|
626
|
+
? "当前账号引用独立配置,请通过「导出配置」保存调整后替换原文件"
|
|
627
|
+
: "保存当前调整至本地 config.json 的当前账号";
|
|
628
|
+
const exportTheme = account.theme?.exportTheme || {};
|
|
629
|
+
const editorVars = account.theme?.editorVars || {};
|
|
630
|
+
// Font Size
|
|
631
|
+
const fontSize = exportTheme.bodyFontSize?.replace(/px$/, "") || "16";
|
|
632
|
+
inputFontSize.value = fontSize;
|
|
633
|
+
valFontSize.textContent = `${fontSize}px`;
|
|
634
|
+
syncSegmentedState("seg-font-size", fontSize);
|
|
635
|
+
// Line Height
|
|
636
|
+
const lineHeight = exportTheme.bodyLineHeight || "1.84";
|
|
637
|
+
inputLineHeight.value = lineHeight;
|
|
638
|
+
valLineHeight.textContent = lineHeight;
|
|
639
|
+
syncSegmentedState("seg-line-height", lineHeight);
|
|
640
|
+
// Letter & Paragraph Spacing
|
|
641
|
+
inputLetterSpacing.value = String(parseFloat(exportTheme.bodyLetterSpacing || "0.012em"));
|
|
642
|
+
valLetterSpacing.textContent = `${inputLetterSpacing.value}em`;
|
|
643
|
+
inputParaSpacing.value = String(parseFloat(exportTheme.bodyParagraphSpacing || "1.18em"));
|
|
644
|
+
valParaSpacing.textContent = `${inputParaSpacing.value}em`;
|
|
645
|
+
// Colors
|
|
646
|
+
const brand = editorVars["--brand"] || exportTheme.primaryColor || "#ca6093";
|
|
647
|
+
pickerBrandColor.value = hexBrandColor.value = brand;
|
|
648
|
+
syncSwatchActiveState(brand);
|
|
649
|
+
const text = exportTheme.bodyColor || editorVars["--text"] || "#292526";
|
|
650
|
+
pickerTextColor.value = hexTextColor.value = text;
|
|
651
|
+
if (exportTheme.h2Color)
|
|
652
|
+
pickerH2Color.value = hexH2Color.value = exportTheme.h2Color;
|
|
653
|
+
if (exportTheme.h3Color)
|
|
654
|
+
pickerH3Color.value = hexH3Color.value = exportTheme.h3Color;
|
|
655
|
+
const quote = exportTheme.blockquoteBorderColor || brand;
|
|
656
|
+
pickerQuoteColor.value = hexQuoteColor.value = quote;
|
|
657
|
+
const divider = exportTheme.dividerColor || editorVars["--divider"] || "#e2dcdf";
|
|
658
|
+
pickerDividerColor.value = hexDividerColor.value = divider;
|
|
659
|
+
inputCustomCss.value = account.customCssContent || "";
|
|
660
|
+
// Syntax Presets
|
|
661
|
+
const savedPresets = account.theme?.syntaxPresets || {};
|
|
662
|
+
currentSyntaxPresets = {};
|
|
663
|
+
for (const cat of SYNTAX_CATEGORIES) {
|
|
664
|
+
if (cat.defaultPresetId) {
|
|
665
|
+
currentSyntaxPresets[cat.key] = savedPresets[cat.key] || cat.defaultPresetId;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
syncPillActiveState("seg-h2-preset", currentSyntaxPresets.h2 || "pillar");
|
|
669
|
+
renderSyntaxPresetControls();
|
|
670
|
+
// Structure
|
|
671
|
+
const structure = account.theme?.structure || {};
|
|
672
|
+
switchNumberedHeadings.checked = Boolean(structure.numberedHeadings);
|
|
673
|
+
inputHeadingLabel.value = structure.headingLabel || "";
|
|
674
|
+
inputQuoteLabel.value = structure.quoteLabel || "";
|
|
675
|
+
scheduleRender(20);
|
|
676
|
+
}
|
|
677
|
+
// ── Initialize Color Palettes Dropdown ───────────────────────────────────
|
|
678
|
+
function initColorPalettes() {
|
|
679
|
+
if (!paletteSelect)
|
|
680
|
+
return;
|
|
681
|
+
paletteSelect.innerHTML = `<option value="">-- 选择推荐调色盘 (一键应用) --</option>`;
|
|
682
|
+
for (const p of COLOR_PALETTES) {
|
|
683
|
+
const opt = document.createElement("option");
|
|
684
|
+
opt.value = p.id;
|
|
685
|
+
opt.textContent = `${p.name} · ${p.category}`;
|
|
686
|
+
paletteSelect.appendChild(opt);
|
|
687
|
+
}
|
|
688
|
+
paletteSelect.addEventListener("change", () => {
|
|
689
|
+
const pal = COLOR_PALETTES.find((p) => p.id === paletteSelect.value);
|
|
690
|
+
if (!pal)
|
|
691
|
+
return;
|
|
692
|
+
pickerBrandColor.value = hexBrandColor.value = pal.colors.brand;
|
|
693
|
+
pickerTextColor.value = hexTextColor.value = pal.colors.text;
|
|
694
|
+
pickerH2Color.value = hexH2Color.value = pal.colors.h2;
|
|
695
|
+
pickerH3Color.value = hexH3Color.value = pal.colors.h3;
|
|
696
|
+
pickerQuoteColor.value = hexQuoteColor.value = pal.colors.quote;
|
|
697
|
+
pickerDividerColor.value = hexDividerColor.value = pal.colors.divider;
|
|
698
|
+
syncSwatchActiveState(pal.colors.brand);
|
|
699
|
+
if (paletteDesc)
|
|
700
|
+
paletteDesc.textContent = pal.description;
|
|
701
|
+
showToast(`已套用「${pal.name}」色盘`, "info");
|
|
702
|
+
scheduleRender(20);
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
// ── Studio Main Initialization ──────────────────────────────────────────
|
|
706
|
+
async function initStudio() {
|
|
707
|
+
initColorPalettes();
|
|
708
|
+
initSegmentedControls();
|
|
709
|
+
renderStylePacks();
|
|
710
|
+
// Bind Color Pairs
|
|
711
|
+
bindColorPair(pickerBrandColor, hexBrandColor, () => {
|
|
712
|
+
syncSwatchActiveState(pickerBrandColor.value);
|
|
713
|
+
scheduleRender(20);
|
|
714
|
+
});
|
|
715
|
+
bindColorPair(pickerTextColor, hexTextColor, () => scheduleRender(20));
|
|
716
|
+
bindColorPair(pickerH2Color, hexH2Color, () => scheduleRender(20));
|
|
717
|
+
bindColorPair(pickerH3Color, hexH3Color, () => scheduleRender(20));
|
|
718
|
+
bindColorPair(pickerQuoteColor, hexQuoteColor, () => scheduleRender(20));
|
|
719
|
+
bindColorPair(pickerDividerColor, hexDividerColor, () => scheduleRender(20));
|
|
720
|
+
try {
|
|
721
|
+
const res = await fetch("/api/studio/config");
|
|
722
|
+
if (!res.ok)
|
|
723
|
+
throw new Error(`HTTP ${res.status}`);
|
|
724
|
+
const data = (await res.json());
|
|
725
|
+
accounts = data.accounts || {};
|
|
726
|
+
currentAccount = data.defaultAccount || "default";
|
|
727
|
+
pluginsList = data.plugins || [];
|
|
728
|
+
// Account selector
|
|
729
|
+
accountSelect.innerHTML = "";
|
|
730
|
+
for (const [key, acc] of Object.entries(accounts)) {
|
|
731
|
+
const opt = document.createElement("option");
|
|
732
|
+
opt.value = key;
|
|
733
|
+
opt.textContent = `${acc.name || key} (${key})`;
|
|
734
|
+
if (key === currentAccount)
|
|
735
|
+
opt.selected = true;
|
|
736
|
+
accountSelect.appendChild(opt);
|
|
737
|
+
}
|
|
738
|
+
const initialMd = data.sampleMarkdown || "# 微信排版标题\n\n欢迎使用 myno 微信排版工作台!";
|
|
739
|
+
editorMarkdown.value = initialMd;
|
|
740
|
+
// Mount MynoEditor (Milkdown Crepe)
|
|
741
|
+
if (milkdownEditorRoot) {
|
|
742
|
+
mynoEditor = new MynoEditor({
|
|
743
|
+
root: milkdownEditorRoot,
|
|
744
|
+
defaultValue: initialMd,
|
|
745
|
+
onMarkdownChange: (md) => {
|
|
746
|
+
editorMarkdown.value = md;
|
|
747
|
+
scheduleRender(40);
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
await mynoEditor.create();
|
|
751
|
+
}
|
|
752
|
+
renderSyntaxCards(pluginsList);
|
|
753
|
+
applyAccountConfig(currentAccount);
|
|
754
|
+
}
|
|
755
|
+
catch (err) {
|
|
756
|
+
console.error("Failed to load studio config:", err);
|
|
757
|
+
showToast("无法加载服务器配置,使用本地默认设置", "error");
|
|
758
|
+
scheduleRender(20);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// ── Event Handlers ──────────────────────────────────────────────────────
|
|
762
|
+
accountSelect.addEventListener("change", () => {
|
|
763
|
+
currentAccount = accountSelect.value;
|
|
764
|
+
applyAccountConfig(currentAccount);
|
|
765
|
+
});
|
|
766
|
+
editorMarkdown.addEventListener("input", () => {
|
|
767
|
+
// 源码模式下 textarea 是编辑面;视觉模式下它隐藏,但仍可能被脚本/按钮更新,需回同步给 Crepe
|
|
768
|
+
if (mynoEditor) {
|
|
769
|
+
mynoEditor.setMarkdown(editorMarkdown.value);
|
|
770
|
+
}
|
|
771
|
+
scheduleRender(40);
|
|
772
|
+
});
|
|
773
|
+
// Dual Mode Switcher
|
|
774
|
+
btnModeVisual.addEventListener("click", () => {
|
|
775
|
+
if (currentEditorMode === "visual")
|
|
776
|
+
return;
|
|
777
|
+
currentEditorMode = "visual";
|
|
778
|
+
btnModeVisual.classList.add("active");
|
|
779
|
+
btnModeSource.classList.remove("active");
|
|
780
|
+
milkdownEditorRoot.style.display = "block";
|
|
781
|
+
editorMarkdown.style.display = "none";
|
|
782
|
+
if (mynoEditor) {
|
|
783
|
+
mynoEditor.setMarkdown(editorMarkdown.value);
|
|
784
|
+
}
|
|
785
|
+
refreshLiveEditorStyles();
|
|
786
|
+
showToast("已切换至所见即所得编辑模式", "info");
|
|
787
|
+
});
|
|
788
|
+
btnModeSource.addEventListener("click", () => {
|
|
789
|
+
if (currentEditorMode === "source")
|
|
790
|
+
return;
|
|
791
|
+
currentEditorMode = "source";
|
|
792
|
+
btnModeSource.classList.add("active");
|
|
793
|
+
btnModeVisual.classList.remove("active");
|
|
794
|
+
if (mynoEditor) {
|
|
795
|
+
editorMarkdown.value = mynoEditor.getMarkdown();
|
|
796
|
+
}
|
|
797
|
+
milkdownEditorRoot.style.display = "none";
|
|
798
|
+
editorMarkdown.style.display = "block";
|
|
799
|
+
editorMarkdown.focus();
|
|
800
|
+
showToast("已切换至 Markdown 源码模式", "info");
|
|
801
|
+
});
|
|
802
|
+
// Sliders and Text Inputs
|
|
803
|
+
inputFontSize.addEventListener("input", () => {
|
|
804
|
+
valFontSize.textContent = `${inputFontSize.value}px`;
|
|
805
|
+
syncSegmentedState("seg-font-size", inputFontSize.value);
|
|
806
|
+
scheduleRender(20);
|
|
807
|
+
});
|
|
808
|
+
inputLineHeight.addEventListener("input", () => {
|
|
809
|
+
valLineHeight.textContent = inputLineHeight.value;
|
|
810
|
+
syncSegmentedState("seg-line-height", inputLineHeight.value);
|
|
811
|
+
scheduleRender(20);
|
|
812
|
+
});
|
|
813
|
+
inputLetterSpacing.addEventListener("input", () => {
|
|
814
|
+
valLetterSpacing.textContent = `${inputLetterSpacing.value}em`;
|
|
815
|
+
scheduleRender(20);
|
|
816
|
+
});
|
|
817
|
+
inputParaSpacing.addEventListener("input", () => {
|
|
818
|
+
valParaSpacing.textContent = `${inputParaSpacing.value}em`;
|
|
819
|
+
scheduleRender(20);
|
|
820
|
+
});
|
|
821
|
+
switchNumberedHeadings.addEventListener("change", () => scheduleRender(20));
|
|
822
|
+
inputHeadingLabel.addEventListener("input", () => scheduleRender(40));
|
|
823
|
+
inputQuoteLabel.addEventListener("input", () => scheduleRender(40));
|
|
824
|
+
inputCustomCss.addEventListener("input", () => scheduleRender(60));
|
|
825
|
+
// Tabs
|
|
826
|
+
const tabBtnStyle = document.getElementById("tab-btn-style");
|
|
827
|
+
const tabBtnSyntax = document.getElementById("tab-btn-syntax");
|
|
828
|
+
const tabContentStyle = document.getElementById("tab-content-style");
|
|
829
|
+
const tabContentSyntax = document.getElementById("tab-content-syntax");
|
|
830
|
+
tabBtnStyle?.addEventListener("click", () => {
|
|
831
|
+
tabBtnStyle.classList.add("active");
|
|
832
|
+
tabBtnSyntax.classList.remove("active");
|
|
833
|
+
tabContentStyle.style.display = "block";
|
|
834
|
+
tabContentSyntax.style.display = "none";
|
|
835
|
+
});
|
|
836
|
+
tabBtnSyntax?.addEventListener("click", () => {
|
|
837
|
+
tabBtnSyntax.classList.add("active");
|
|
838
|
+
tabBtnStyle.classList.remove("active");
|
|
839
|
+
tabContentStyle.style.display = "none";
|
|
840
|
+
tabContentSyntax.style.display = "block";
|
|
841
|
+
});
|
|
842
|
+
// Load sample / clear
|
|
843
|
+
btnLoadSample?.addEventListener("click", async () => {
|
|
844
|
+
const res = await fetch("/api/studio/config");
|
|
845
|
+
const data = (await res.json());
|
|
846
|
+
const sample = data.sampleMarkdown || "";
|
|
847
|
+
editorMarkdown.value = sample;
|
|
848
|
+
if (mynoEditor)
|
|
849
|
+
mynoEditor.setMarkdown(sample);
|
|
850
|
+
scheduleRender(20);
|
|
851
|
+
showToast("已加载全语法官方测试稿", "info");
|
|
852
|
+
});
|
|
853
|
+
btnClearMd?.addEventListener("click", () => {
|
|
854
|
+
editorMarkdown.value = "";
|
|
855
|
+
if (mynoEditor)
|
|
856
|
+
mynoEditor.setMarkdown("");
|
|
857
|
+
scheduleRender(20);
|
|
858
|
+
});
|
|
859
|
+
// Toggle HTML view
|
|
860
|
+
btnToggleView?.addEventListener("click", () => {
|
|
861
|
+
showingHtmlSource = !showingHtmlSource;
|
|
862
|
+
if (showingHtmlSource) {
|
|
863
|
+
htmlSourceContainer.style.display = "block";
|
|
864
|
+
btnToggleView.textContent = "📱 返回视图";
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
htmlSourceContainer.style.display = "none";
|
|
868
|
+
btnToggleView.textContent = "🔍 HTML 源码";
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
// Reset
|
|
872
|
+
btnReset?.addEventListener("click", () => {
|
|
873
|
+
applyAccountConfig(currentAccount);
|
|
874
|
+
showToast("已重置为当前账号默认样式", "info");
|
|
875
|
+
});
|
|
876
|
+
// Save to config.json
|
|
877
|
+
btnSave?.addEventListener("click", async () => {
|
|
878
|
+
const account = accounts[currentAccount];
|
|
879
|
+
if (!account)
|
|
880
|
+
return;
|
|
881
|
+
const { structure, ...theme } = readStyleControls();
|
|
882
|
+
const { headerImageUrl: _headerImageUrl, ...savedStructure } = structure;
|
|
883
|
+
const payload = {
|
|
884
|
+
account: currentAccount,
|
|
885
|
+
...theme,
|
|
886
|
+
syntaxPresets: { ...currentSyntaxPresets },
|
|
887
|
+
customCss: inputCustomCss.value,
|
|
888
|
+
structure: savedStructure,
|
|
889
|
+
};
|
|
890
|
+
try {
|
|
891
|
+
const res = await fetch("/api/studio/save-config", {
|
|
892
|
+
method: "POST",
|
|
893
|
+
headers: { "content-type": "application/json" },
|
|
894
|
+
body: JSON.stringify(payload),
|
|
895
|
+
});
|
|
896
|
+
const data = await res.json();
|
|
897
|
+
if (res.ok && data.ok) {
|
|
898
|
+
account.theme = { ...theme, structure, syntaxPresets: payload.syntaxPresets };
|
|
899
|
+
account.customCssContent = payload.customCss;
|
|
900
|
+
showToast("✅ 配置已成功保存至本地 config.json!", "success");
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
showToast(`保存失败: ${data.error || "未知错误"}`, "error");
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
catch (err) {
|
|
907
|
+
showToast(`网络请求失败: ${String(err)}`, "error");
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
// Export Config (wechat-style.json)
|
|
911
|
+
btnExportConfig?.addEventListener("click", () => {
|
|
912
|
+
const account = accounts[currentAccount];
|
|
913
|
+
if (!account)
|
|
914
|
+
return;
|
|
915
|
+
const styleConfig = {
|
|
916
|
+
schemaVersion: 1,
|
|
917
|
+
engineVersion: 1,
|
|
918
|
+
id: standaloneThemeName || "wechat-style",
|
|
919
|
+
name: "myno WeChat 排版配置",
|
|
920
|
+
version: "1.0.0",
|
|
921
|
+
...readStyleControls(),
|
|
922
|
+
syntaxPresets: { ...currentSyntaxPresets },
|
|
923
|
+
css: [account.articleThemeCss, inputCustomCss.value].filter(Boolean).join("\n\n"),
|
|
924
|
+
};
|
|
925
|
+
const { url, filename } = exportStyleConfigBlob(styleConfig);
|
|
926
|
+
const link = document.createElement("a");
|
|
927
|
+
link.href = url;
|
|
928
|
+
link.download = filename;
|
|
929
|
+
link.click();
|
|
930
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
931
|
+
showToast("已导出独立排版文件,CLI 使用 --article-theme 引用该文件", "success");
|
|
932
|
+
});
|
|
933
|
+
// Import Config (wechat-style.json)
|
|
934
|
+
btnImportConfig?.addEventListener("click", () => {
|
|
935
|
+
fileImportConfig.click();
|
|
936
|
+
});
|
|
937
|
+
fileImportConfig?.addEventListener("change", () => {
|
|
938
|
+
const file = fileImportConfig.files?.[0];
|
|
939
|
+
if (!file)
|
|
940
|
+
return;
|
|
941
|
+
const reader = new FileReader();
|
|
942
|
+
reader.onload = () => {
|
|
943
|
+
const content = reader.result;
|
|
944
|
+
const result = parseStyleConfig(content);
|
|
945
|
+
if (!result.ok) {
|
|
946
|
+
showToast(`导入失败: ${result.error}`, "error");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const cfg = result.config;
|
|
950
|
+
standaloneThemeName = cfg.id || file.name.replace(/\.json$/i, "");
|
|
951
|
+
// Apply values to UI
|
|
952
|
+
if (cfg.exportTheme.bodyFontSize) {
|
|
953
|
+
const fs = cfg.exportTheme.bodyFontSize.replace(/px$/, "");
|
|
954
|
+
inputFontSize.value = fs;
|
|
955
|
+
valFontSize.textContent = `${fs}px`;
|
|
956
|
+
syncSegmentedState("seg-font-size", fs);
|
|
957
|
+
}
|
|
958
|
+
if (cfg.exportTheme.bodyLineHeight) {
|
|
959
|
+
inputLineHeight.value = cfg.exportTheme.bodyLineHeight;
|
|
960
|
+
valLineHeight.textContent = cfg.exportTheme.bodyLineHeight;
|
|
961
|
+
syncSegmentedState("seg-line-height", cfg.exportTheme.bodyLineHeight);
|
|
962
|
+
}
|
|
963
|
+
if (cfg.exportTheme.primaryColor || cfg.editorVars["--brand"]) {
|
|
964
|
+
const brand = cfg.exportTheme.primaryColor || cfg.editorVars["--brand"];
|
|
965
|
+
pickerBrandColor.value = hexBrandColor.value = brand;
|
|
966
|
+
syncSwatchActiveState(brand);
|
|
967
|
+
}
|
|
968
|
+
if (cfg.exportTheme.bodyColor || cfg.editorVars["--text"]) {
|
|
969
|
+
pickerTextColor.value = hexTextColor.value = cfg.exportTheme.bodyColor || cfg.editorVars["--text"];
|
|
970
|
+
}
|
|
971
|
+
if (cfg.exportTheme.h2Color)
|
|
972
|
+
pickerH2Color.value = hexH2Color.value = cfg.exportTheme.h2Color;
|
|
973
|
+
if (cfg.exportTheme.h3Color)
|
|
974
|
+
pickerH3Color.value = hexH3Color.value = cfg.exportTheme.h3Color;
|
|
975
|
+
if (cfg.exportTheme.blockquoteBorderColor)
|
|
976
|
+
pickerQuoteColor.value = hexQuoteColor.value = cfg.exportTheme.blockquoteBorderColor;
|
|
977
|
+
if (cfg.exportTheme.dividerColor)
|
|
978
|
+
pickerDividerColor.value = hexDividerColor.value = cfg.exportTheme.dividerColor;
|
|
979
|
+
if (cfg.syntaxPresets) {
|
|
980
|
+
currentSyntaxPresets = { ...cfg.syntaxPresets };
|
|
981
|
+
if (currentSyntaxPresets.h2)
|
|
982
|
+
syncPillActiveState("seg-h2-preset", currentSyntaxPresets.h2);
|
|
983
|
+
}
|
|
984
|
+
if (cfg.structure) {
|
|
985
|
+
switchNumberedHeadings.checked = Boolean(cfg.structure.numberedHeadings);
|
|
986
|
+
inputHeadingLabel.value = cfg.structure.headingLabel || "";
|
|
987
|
+
inputQuoteLabel.value = cfg.structure.quoteLabel || "";
|
|
988
|
+
}
|
|
989
|
+
if (cfg.css) {
|
|
990
|
+
inputCustomCss.value = cfg.css;
|
|
991
|
+
}
|
|
992
|
+
configModeBadge.textContent = `独立配置: ${file.name}`;
|
|
993
|
+
configModeBadge.className = "badge badge-primary";
|
|
994
|
+
showToast(`✅ 成功导入独立配置: ${file.name}`, "success");
|
|
995
|
+
scheduleRender(20);
|
|
996
|
+
};
|
|
997
|
+
reader.readAsText(file);
|
|
998
|
+
});
|
|
999
|
+
// Copy to WeChat
|
|
1000
|
+
btnCopy?.addEventListener("click", async () => {
|
|
1001
|
+
if (!latestWechatHtml) {
|
|
1002
|
+
showToast("文章内容为空", "error");
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
const blobHtml = new Blob([latestWechatHtml], { type: "text/html" });
|
|
1007
|
+
const blobText = new Blob([latestWechatHtml], { type: "text/plain" });
|
|
1008
|
+
await navigator.clipboard.write([
|
|
1009
|
+
new ClipboardItem({
|
|
1010
|
+
"text/html": blobHtml,
|
|
1011
|
+
"text/plain": blobText,
|
|
1012
|
+
}),
|
|
1013
|
+
]);
|
|
1014
|
+
showToast("📋 已复制富文本!可在微信公众号后台直接粘贴 (Cmd+V)", "success");
|
|
1015
|
+
}
|
|
1016
|
+
catch (err) {
|
|
1017
|
+
console.error("Clipboard write error:", err);
|
|
1018
|
+
const listener = (e) => {
|
|
1019
|
+
e.clipboardData?.setData("text/html", latestWechatHtml);
|
|
1020
|
+
e.clipboardData?.setData("text/plain", latestWechatHtml);
|
|
1021
|
+
e.preventDefault();
|
|
1022
|
+
};
|
|
1023
|
+
document.addEventListener("copy", listener);
|
|
1024
|
+
document.execCommand("copy");
|
|
1025
|
+
document.removeEventListener("copy", listener);
|
|
1026
|
+
showToast("📋 已复制富文本(后备通道)", "success");
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
// Start
|
|
1030
|
+
void initStudio();
|
|
1031
|
+
//# sourceMappingURL=studio.js.map
|