@wenyan-md/core 3.0.7 → 3.0.9

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/dist/core.js CHANGED
@@ -100,6 +100,12 @@ async function loadCssBySource(source) {
100
100
  throw new Error("Unknown source type");
101
101
  }
102
102
  }
103
+ function createSvgDataUrl(svg) {
104
+ if (!svg.hasAttribute("xmlns")) {
105
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
106
+ }
107
+ return `data:image/svg+xml,${encodeURIComponent(svg.outerHTML)}`;
108
+ }
103
109
  const __vite_glob_0_0$1 = "pre{background:#282c34}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}";
104
110
  const __vite_glob_0_1$1 = "pre{background:#fafafa}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}";
105
111
  const __vite_glob_0_2$1 = "pre{background:#282936}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#e9e9f4;background:#282936}.hljs ::selection,.hljs::selection{background-color:#4d4f68;color:#e9e9f4}.hljs-comment{color:#626483}.hljs-tag{color:#62d6e8}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#e9e9f4}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#ea51b2}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#b45bcf}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#00f769}.hljs-strong{font-weight:700;color:#00f769}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#ebff87}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#a1efe4}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#62d6e8}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#b45bcf}.hljs-emphasis{color:#b45bcf;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#00f769}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}\n";
@@ -196,7 +202,7 @@ async function handleFrontMatter(markdown) {
196
202
  const { attributes, body } = fm(markdown);
197
203
  const result = { content: body || "" };
198
204
  let head = "";
199
- const { title, description, cover, author, source_url, need_open_comment, only_fans_can_comment, image_list } = attributes;
205
+ const { title, description, cover, author, source_url, need_open_comment, only_fans_can_comment, image_list, type } = attributes;
200
206
  if (title) {
201
207
  result.title = title;
202
208
  }
@@ -207,9 +213,6 @@ async function handleFrontMatter(markdown) {
207
213
  if (cover) {
208
214
  result.cover = cover;
209
215
  }
210
- if (head) {
211
- result.content = head + result.content;
212
- }
213
216
  if (author) {
214
217
  result.author = author;
215
218
  }
@@ -225,6 +228,12 @@ async function handleFrontMatter(markdown) {
225
228
  if (image_list) {
226
229
  result.image_list = image_list;
227
230
  }
231
+ if (type) {
232
+ result.type = type;
233
+ }
234
+ if (head) {
235
+ result.content = head + result.content;
236
+ }
228
237
  return result;
229
238
  }
230
239
  const parseOptions = {
@@ -446,15 +455,65 @@ function createMarkedClient() {
446
455
  const match = rule.exec(src);
447
456
  if (!match) return;
448
457
  const isImage = !!match[1];
458
+ const text = match[2];
459
+ const inner = match[3].trim();
460
+ let href = "";
461
+ let title = "";
462
+ const titleMatch = inner.match(/(.*?)\s+["']([^"']*)["']$/);
463
+ if (titleMatch) {
464
+ href = titleMatch[1].trim();
465
+ title = titleMatch[2];
466
+ } else {
467
+ href = inner;
468
+ }
449
469
  return {
450
470
  type: isImage ? "image" : "link",
451
471
  raw: match[0],
452
- text: match[2],
453
- href: match[3],
454
- tokens: this.lexer.inlineTokens(match[2])
472
+ text,
473
+ href,
474
+ title,
475
+ tokens: this.lexer.inlineTokens(text)
455
476
  };
456
477
  }
457
478
  },
479
+ // Obsidian WikiLinks 图片语法扩展 ![[filename]] / ![[filename|alt]] / ![[filename|width]] / ![[filename|widthxheight]]
480
+ {
481
+ name: "wikiImage",
482
+ level: "inline",
483
+ start(src) {
484
+ return src.match(/!\[\[/)?.index;
485
+ },
486
+ tokenizer(src) {
487
+ const rule = /^!\[\[([^\]|]+?)(?:\|([^\]]*))?\]\]/;
488
+ const match = rule.exec(src);
489
+ if (!match) return;
490
+ const href = match[1].trim();
491
+ const modifier = match[2]?.trim() ?? "";
492
+ const dimOnly = /^(\d+)(?:x(\d+))?$/.exec(modifier);
493
+ const alt = dimOnly ? "" : modifier;
494
+ const width = dimOnly ? dimOnly[1] : "";
495
+ const height = dimOnly ? dimOnly[2] ?? "" : "";
496
+ return {
497
+ type: "wikiImage",
498
+ raw: match[0],
499
+ href,
500
+ alt,
501
+ width,
502
+ height,
503
+ tokens: []
504
+ };
505
+ },
506
+ renderer(token) {
507
+ const href = normalizeHref(token.href);
508
+ const altAttr = token.alt ? ` alt="${token.alt}"` : "";
509
+ const titleAttr = token.alt ? ` title="${token.alt}"` : "";
510
+ const styleParts = [];
511
+ if (token.width) styleParts.push(`width:${token.width}px`);
512
+ if (token.height) styleParts.push(`height:${token.height}px`);
513
+ const styleAttr = styleParts.length ? ` style="${styleParts.join("; ")}"` : "";
514
+ return `<img src="${href}"${altAttr}${titleAttr}${styleAttr}>`;
515
+ }
516
+ },
458
517
  // 自定义图片语法扩展 ![](){...}
459
518
  {
460
519
  name: "attributeImage",
@@ -634,24 +693,7 @@ function containsMermaidCodeBlock(html) {
634
693
  return html.includes("language-mermaid");
635
694
  }
636
695
  const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
637
- function wechatPostRender(element) {
638
- const mathElements = element.querySelectorAll("mjx-container");
639
- mathElements.forEach((mathContainer) => {
640
- const svg = mathContainer.querySelector("svg");
641
- if (!svg) return;
642
- svg.style.width = svg.getAttribute("width") || "";
643
- svg.style.height = svg.getAttribute("height") || "";
644
- svg.removeAttribute("width");
645
- svg.removeAttribute("height");
646
- const parent = mathContainer.parentElement;
647
- if (parent) {
648
- mathContainer.remove();
649
- parent.appendChild(svg);
650
- if (parent.classList.contains("block-equation")) {
651
- parent.setAttribute("style", "text-align: center; margin-bottom: 1rem;");
652
- }
653
- }
654
- });
696
+ function postRenderMermaidDiagrams(element) {
655
697
  const mermaidSvgs = element.querySelectorAll('[data-wenyan-diagram="mermaid"] svg');
656
698
  mermaidSvgs.forEach((svg) => {
657
699
  svg.style.maxWidth = "100%";
@@ -659,21 +701,6 @@ function wechatPostRender(element) {
659
701
  inlineMermaidSvgStyles(svg);
660
702
  flattenMermaidMarkers(svg);
661
703
  });
662
- const codeElements = element.querySelectorAll("pre code");
663
- codeElements.forEach((codeEl) => {
664
- codeEl.innerHTML = codeEl.innerHTML.replace(/\n/g, "<br>").replace(/(>[^<]+)|(^[^<]+)/g, (str) => str.replace(/\s/g, "&nbsp;"));
665
- });
666
- const listElements = element.querySelectorAll("li");
667
- listElements.forEach((li) => {
668
- const doc = element.ownerDocument;
669
- const section = doc.createElement("section");
670
- while (li.firstChild) {
671
- section.appendChild(li.firstChild);
672
- }
673
- li.appendChild(section);
674
- });
675
- element.style.color = "rgb(0, 0, 0)";
676
- element.style.caretColor = "rgb(0, 0, 0)";
677
704
  }
678
705
  function inlineMermaidSvgStyles(svg) {
679
706
  const styleElements = Array.from(svg.querySelectorAll("style"));
@@ -937,6 +964,41 @@ function isInlineStyleTarget(node) {
937
964
  }
938
965
  return node instanceof defaultView.SVGElement || node instanceof defaultView.HTMLElement;
939
966
  }
967
+ function wechatPostRender(element) {
968
+ const mathElements = element.querySelectorAll("mjx-container");
969
+ mathElements.forEach((mathContainer) => {
970
+ const svg = mathContainer.querySelector("svg");
971
+ if (!svg) return;
972
+ svg.style.width = svg.getAttribute("width") || "";
973
+ svg.style.height = svg.getAttribute("height") || "";
974
+ svg.removeAttribute("width");
975
+ svg.removeAttribute("height");
976
+ const parent = mathContainer.parentElement;
977
+ if (parent) {
978
+ mathContainer.remove();
979
+ parent.appendChild(svg);
980
+ if (parent.classList.contains("block-equation")) {
981
+ parent.setAttribute("style", "text-align: center; margin-bottom: 1rem;");
982
+ }
983
+ }
984
+ });
985
+ postRenderMermaidDiagrams(element);
986
+ const codeElements = element.querySelectorAll("pre code");
987
+ codeElements.forEach((codeEl) => {
988
+ codeEl.innerHTML = codeEl.innerHTML.replace(/\n/g, "<br>").replace(/(>[^<]+)|(^[^<]+)/g, (str) => str.replace(/\s/g, "&nbsp;"));
989
+ });
990
+ const listElements = element.querySelectorAll("li");
991
+ listElements.forEach((li) => {
992
+ const doc = element.ownerDocument;
993
+ const section = doc.createElement("section");
994
+ while (li.firstChild) {
995
+ section.appendChild(li.firstChild);
996
+ }
997
+ li.appendChild(section);
998
+ });
999
+ element.style.color = "rgb(0, 0, 0)";
1000
+ element.style.caretColor = "rgb(0, 0, 0)";
1001
+ }
940
1002
  const themeModifier = createCssModifier({});
941
1003
  function renderTheme(wenyanElement, themeCss) {
942
1004
  const modifiedCss = themeModifier(themeCss);
@@ -1480,6 +1542,8 @@ function getContentForZhihu(wenyanElement) {
1480
1542
  return wenyanElement.outerHTML;
1481
1543
  }
1482
1544
  function getContentForToutiao(wenyanElement) {
1545
+ postRenderMermaidDiagrams(wenyanElement);
1546
+ processMermaid(wenyanElement);
1483
1547
  const containers = wenyanElement.querySelectorAll("mjx-container");
1484
1548
  const doc = wenyanElement.ownerDocument;
1485
1549
  containers.forEach((container) => {
@@ -1488,11 +1552,7 @@ function getContentForToutiao(wenyanElement) {
1488
1552
  return;
1489
1553
  }
1490
1554
  const img = doc.createElement("img");
1491
- if (!svg.hasAttribute("xmlns")) {
1492
- svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1493
- }
1494
- const encodedSVG = encodeURIComponent(svg.outerHTML);
1495
- img.src = `data:image/svg+xml,${encodedSVG}`;
1555
+ img.src = createSvgDataUrl(svg);
1496
1556
  const style = svg.getAttribute("style");
1497
1557
  if (style) {
1498
1558
  img.setAttribute("style", style);
@@ -1507,10 +1567,67 @@ function getContentForToutiao(wenyanElement) {
1507
1567
  });
1508
1568
  return wenyanElement.outerHTML;
1509
1569
  }
1570
+ function processMermaid(root) {
1571
+ const figures = root.querySelectorAll('[data-wenyan-diagram="mermaid"]');
1572
+ const doc = root.ownerDocument;
1573
+ figures.forEach((figure) => {
1574
+ const svg = figure.querySelector("svg");
1575
+ if (!svg) {
1576
+ return;
1577
+ }
1578
+ let svgString = svg.outerHTML;
1579
+ svgString = fixMermaidSvgForServerRender(svgString);
1580
+ const img = doc.createElement("img");
1581
+ const encodedData = btoa(unescape(encodeURIComponent(svgString)));
1582
+ img.src = `data:image/svg+xml;base64,${encodedData}`;
1583
+ const style = svg.getAttribute("style");
1584
+ if (style) {
1585
+ img.setAttribute("style", style);
1586
+ }
1587
+ const ariaLabel = figure.getAttribute("aria-label") || figure.getAttribute("title") || svg.getAttribute("aria-label") || svg.getAttribute("title");
1588
+ if (ariaLabel) {
1589
+ img.alt = ariaLabel;
1590
+ }
1591
+ figure.replaceWith(img);
1592
+ });
1593
+ }
1594
+ function fixMermaidSvgForServerRender(svgString) {
1595
+ const baseFontSize = 16;
1596
+ let fixedSvg = svgString;
1597
+ fixedSvg = fixedSvg.replace(/([xy]|dx|dy)="([-0-9.]+)em"/g, (_, attr, val) => {
1598
+ const pxValue = parseFloat(val) * baseFontSize;
1599
+ return `${attr}="${pxValue}px"`;
1600
+ });
1601
+ let previousSvg;
1602
+ do {
1603
+ previousSvg = fixedSvg;
1604
+ fixedSvg = fixedSvg.replace(/<tspan(?![^>]*\b[xy]=)[^>]*>([\s\S]*?)<\/tspan>/g, "$1");
1605
+ } while (fixedSvg !== previousSvg);
1606
+ fixedSvg = fixedSvg.replace(/<tspan([^>]+)>/g, (match, attrs) => {
1607
+ const yMatch = attrs.match(/y="([-0-9.]+)(?:px)?"/);
1608
+ const dyMatch = attrs.match(/dy="([-0-9.]+)(?:px)?"/);
1609
+ if (yMatch && dyMatch) {
1610
+ const yVal = parseFloat(yMatch[1]);
1611
+ const dyVal = parseFloat(dyMatch[1]);
1612
+ const finalY = yVal + dyVal;
1613
+ let newAttrs = attrs.replace(/y="[-0-9.]+(?:px)?"\s*/, "").replace(/dy="[-0-9.]+(?:px)?"\s*/, "");
1614
+ return `<tspan ${newAttrs.trim()} y="${finalY}">`;
1615
+ }
1616
+ return match;
1617
+ });
1618
+ fixedSvg = fixedSvg.replace(/class="([^"]*?)text-inner-tspan([^"]*?)"/g, 'class="$1$2"');
1619
+ fixedSvg = fixedSvg.replace(/\sclass=""/g, "");
1620
+ return fixedSvg;
1621
+ }
1622
+ const DEFAULT_MERMAID_FONT_FAMILY = "'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Source Han Sans SC', Arial, sans-serif";
1510
1623
  const DEFAULT_MERMAID_CONFIG = {
1624
+ fontFamily: DEFAULT_MERMAID_FONT_FAMILY,
1511
1625
  htmlLabels: false,
1512
1626
  flowchart: {
1513
1627
  htmlLabels: false
1628
+ },
1629
+ themeVariables: {
1630
+ fontFamily: DEFAULT_MERMAID_FONT_FAMILY
1514
1631
  }
1515
1632
  };
1516
1633
  async function replaceMermaidCodeBlocks(root, renderDiagram) {
@@ -1519,7 +1636,7 @@ async function replaceMermaidCodeBlocks(root, renderDiagram) {
1519
1636
  return false;
1520
1637
  }
1521
1638
  const ownerDocument = getOwnerDocument(root);
1522
- for (const [index, codeBlock] of codeBlocks.entries()) {
1639
+ for (const [_, codeBlock] of codeBlocks.entries()) {
1523
1640
  const pre = codeBlock.parentElement;
1524
1641
  const parent = pre?.parentElement;
1525
1642
  if (!pre || !parent) {
@@ -1527,7 +1644,7 @@ async function replaceMermaidCodeBlocks(root, renderDiagram) {
1527
1644
  }
1528
1645
  const code = codeBlock.textContent ?? "";
1529
1646
  const svg = await renderDiagram({
1530
- id: `wenyan-mermaid-${index + 1}`,
1647
+ id: `wenyan-mermaid-${crypto.randomUUID()}`,
1531
1648
  code
1532
1649
  });
1533
1650
  const figure = ownerDocument.createElement("figure");
@@ -1548,7 +1665,6 @@ function createBrowserMermaidRenderer(options = {}) {
1548
1665
  const document = parser.parseFromString(`<body>${html}</body>`, "text/html");
1549
1666
  const root = document.body;
1550
1667
  const mermaid = await getMermaidModule();
1551
- mermaid.initialize(createMermaidConfig(options.mermaidConfig));
1552
1668
  try {
1553
1669
  await replaceMermaidCodeBlocks(root, async ({ id, code }) => {
1554
1670
  const { svg } = await mermaid.render(id, code);
@@ -1565,7 +1681,9 @@ function createBrowserMermaidRenderer(options = {}) {
1565
1681
  mermaidModulePromise = import("mermaid");
1566
1682
  }
1567
1683
  const module = await mermaidModulePromise;
1568
- return module.default;
1684
+ const mermaid = module.default;
1685
+ mermaid.initialize(createMermaidConfig(options.mermaidConfig));
1686
+ return mermaid;
1569
1687
  }
1570
1688
  }
1571
1689
  function createMermaidRenderError(error) {
@@ -1574,6 +1692,7 @@ function createMermaidRenderError(error) {
1574
1692
  }
1575
1693
  function createMermaidConfig(overrides = {}) {
1576
1694
  const flowchartOverrides = getRecord(overrides.flowchart);
1695
+ const themeVariablesOverrides = getRecord(overrides.themeVariables);
1577
1696
  return {
1578
1697
  ...DEFAULT_MERMAID_CONFIG,
1579
1698
  startOnLoad: false,
@@ -1582,6 +1701,10 @@ function createMermaidConfig(overrides = {}) {
1582
1701
  flowchart: {
1583
1702
  ...getRecord(DEFAULT_MERMAID_CONFIG.flowchart),
1584
1703
  ...flowchartOverrides
1704
+ },
1705
+ themeVariables: {
1706
+ ...getRecord(DEFAULT_MERMAID_CONFIG.themeVariables),
1707
+ ...themeVariablesOverrides
1585
1708
  }
1586
1709
  };
1587
1710
  }
@@ -1612,12 +1735,15 @@ async function createWenyanCore(options = {}) {
1612
1735
  async handleFrontMatter(markdown) {
1613
1736
  return await handleFrontMatter(markdown);
1614
1737
  },
1615
- async renderMarkdown(markdown) {
1738
+ async renderMarkdown(markdown, disableMermaid = false) {
1616
1739
  let html = await markedClient.parse(markdown);
1617
1740
  if (isConvertMathJax) {
1618
1741
  html = mathJaxParser.parser(html);
1619
1742
  }
1620
- return await mermaidParser.parser(html);
1743
+ if (!disableMermaid) {
1744
+ html = await mermaidParser.parser(html);
1745
+ }
1746
+ return html;
1621
1747
  },
1622
1748
  async applyStylesWithTheme(wenyanElement, options2 = {}) {
1623
1749
  const {
package/dist/publish.js CHANGED
@@ -16,7 +16,7 @@ class TokenStore {
16
16
  try {
17
17
  const loadedData = await this.adapter.loadToken();
18
18
  if (loadedData) {
19
- this.cache = loadedData;
19
+ this.cache = { ...defaultTokenCache, ...loadedData };
20
20
  }
21
21
  } catch (error) {
22
22
  throw new Error(`无法加载 token: ${error instanceof Error ? error.message : String(error)}`);
@@ -32,15 +32,16 @@ class TokenStore {
32
32
  async waitForInit() {
33
33
  await this.initPromise;
34
34
  }
35
- isValid(appid) {
35
+ async isValid(appid) {
36
+ await this.initPromise;
36
37
  const currentTime = Math.floor(Date.now() / 1e3);
37
38
  const bufferTime = 600;
38
39
  const isAppidMatch = this.cache.appid === appid;
39
- const isNotExpired = this.cache.expireAt > currentTime + bufferTime;
40
+ const isNotExpired = this.cache.expireAt < 0 ? true : this.cache.expireAt > currentTime + bufferTime;
40
41
  return isAppidMatch && isNotExpired;
41
42
  }
42
- getToken(appid) {
43
- return this.isValid(appid) ? this.cache.accessToken : null;
43
+ async getToken(appid) {
44
+ return await this.isValid(appid) ? this.cache.accessToken : null;
44
45
  }
45
46
  async setToken(appid, accessToken, expiresIn) {
46
47
  await this.initPromise;
@@ -52,6 +53,16 @@ class TokenStore {
52
53
  };
53
54
  await this.save();
54
55
  }
56
+ async setExternalToken(appid, accessToken) {
57
+ await this.initPromise;
58
+ this.cache = {
59
+ appid,
60
+ accessToken,
61
+ expireAt: -1
62
+ // 标记为外部托管,永不校验过期
63
+ };
64
+ await this.save();
65
+ }
55
66
  async clear() {
56
67
  await this.initPromise;
57
68
  this.cache = { ...defaultTokenCache };
@@ -115,12 +126,14 @@ class CredentialStore {
115
126
  constructor(adapter) {
116
127
  this.adapter = adapter;
117
128
  this.initPromise = this.load();
129
+ this.initPromise.catch(() => {
130
+ });
118
131
  }
119
132
  async load() {
120
133
  try {
121
134
  const loadedData = await this.adapter.loadCredential();
122
135
  if (loadedData) {
123
- this.credential = loadedData;
136
+ this.credential = { ...defaultCredential, ...loadedData };
124
137
  }
125
138
  } catch (error) {
126
139
  throw new Error(`无法加载凭据: ${error instanceof Error ? error.message : String(error)}`);
@@ -133,27 +146,47 @@ class CredentialStore {
133
146
  throw new Error(`无法保存凭据: ${error instanceof Error ? error.message : String(error)}`);
134
147
  }
135
148
  }
136
- async _getWechatCredential() {
149
+ /**
150
+ * 获取微信凭据 (通过 appId 或 alias)
151
+ */
152
+ async getWechatCredential(appIdOrAlias) {
137
153
  await this.initPromise;
138
- return this.credential.wechat ?? {};
139
- }
140
- async getWechatCredential(appId) {
141
- const wechat = await this._getWechatCredential();
142
- if (!wechat) return null;
143
- const appSecret = wechat[appId];
144
- if (!appSecret) return null;
145
- return { appId, appSecret };
154
+ const wechat = this.credential.wechat ?? {};
155
+ if (wechat[appIdOrAlias]) {
156
+ return {
157
+ appId: appIdOrAlias,
158
+ ...wechat[appIdOrAlias]
159
+ };
160
+ }
161
+ const entry = Object.entries(wechat).find(([_, item]) => item.alias === appIdOrAlias);
162
+ if (entry) {
163
+ return {
164
+ appId: entry[0],
165
+ ...entry[1]
166
+ };
167
+ }
168
+ return null;
146
169
  }
147
- async saveWechatCredential(appId, appSecret) {
170
+ /**
171
+ * 保存或更新微信凭据
172
+ */
173
+ async saveWechatCredential(appId, appSecret, alias) {
148
174
  await this.initPromise;
149
175
  this.credential.wechat ??= {};
150
- this.credential.wechat[appId] = appSecret;
176
+ const item = { appSecret };
177
+ if (alias) {
178
+ item.alias = alias;
179
+ }
180
+ this.credential.wechat[appId] = item;
151
181
  await this.save();
152
182
  }
153
- async deleteWechatCredential(appId) {
154
- await this.initPromise;
155
- if (this.credential.wechat) {
156
- delete this.credential.wechat[appId];
183
+ /**
184
+ * 删除微信凭据 (通过 appId 或 alias)
185
+ */
186
+ async deleteWechatCredential(appIdOrAlias) {
187
+ const target = await this.getWechatCredential(appIdOrAlias);
188
+ if (target && this.credential.wechat) {
189
+ delete this.credential.wechat[target.appId];
157
190
  await this.save();
158
191
  }
159
192
  }
@@ -177,7 +210,7 @@ class WechatPublisher {
177
210
  const result2 = await this.fetchAccessToken(appId, appSecret);
178
211
  return result2.access_token;
179
212
  }
180
- const cached = this.tokenStore.getToken(appId);
213
+ const cached = await this.tokenStore.getToken(appId);
181
214
  if (cached) {
182
215
  return cached;
183
216
  }
@@ -217,6 +250,11 @@ class WechatPublisher {
217
250
  await this.uploadCacheStore.clear();
218
251
  }
219
252
  }
253
+ async setExternalToken(appid, accessToken) {
254
+ if (this.tokenStore) {
255
+ await this.tokenStore.setExternalToken(appid, accessToken);
256
+ }
257
+ }
220
258
  }
221
259
  export {
222
260
  CredentialStore,
@@ -15,7 +15,7 @@ export interface ApplyStylesOptions {
15
15
  }
16
16
  export declare function createWenyanCore(options?: WenyanOptions): Promise<{
17
17
  handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
18
- renderMarkdown(markdown: string): Promise<string>;
18
+ renderMarkdown(markdown: string, disableMermaid?: boolean): Promise<string>;
19
19
  applyStylesWithTheme(wenyanElement: HTMLElement, options?: ApplyStylesOptions): Promise<string>;
20
20
  applyStylesWithResolvedCss(wenyanElement: HTMLElement, options: {
21
21
  themeCss: string;
@@ -8,5 +8,6 @@ export interface FrontMatterResult {
8
8
  need_open_comment?: boolean;
9
9
  only_fans_can_comment?: boolean;
10
10
  image_list?: string[];
11
+ type?: string;
11
12
  }
12
13
  export declare function handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
@@ -0,0 +1 @@
1
+ export declare function postRenderMermaidDiagrams(element: HTMLElement): void;
@@ -22,3 +22,4 @@ export type CssSource = {
22
22
  url: string;
23
23
  };
24
24
  export declare function loadCssBySource(source: CssSource): Promise<string>;
25
+ export declare function createSvgDataUrl(svg: SVGSVGElement): string;
@@ -1,5 +1,9 @@
1
+ export interface WechatCredentialItem {
2
+ appSecret: string;
3
+ alias?: string;
4
+ }
1
5
  export interface WenyanCredential {
2
- wechat?: Record<string, string>;
6
+ wechat?: Record<string, WechatCredentialItem>;
3
7
  }
4
8
  export declare const defaultCredential: WenyanCredential;
5
9
  export interface CredentialStorageAdapter {
@@ -14,11 +18,20 @@ export declare class CredentialStore {
14
18
  constructor(adapter: CredentialStorageAdapter);
15
19
  private load;
16
20
  private save;
17
- private _getWechatCredential;
18
- getWechatCredential(appId: string): Promise<{
21
+ /**
22
+ * 获取微信凭据 (通过 appId 或 alias)
23
+ */
24
+ getWechatCredential(appIdOrAlias: string): Promise<{
19
25
  appId: string;
20
26
  appSecret: string;
27
+ alias?: string;
21
28
  } | null>;
22
- saveWechatCredential(appId: string, appSecret: string): Promise<void>;
23
- deleteWechatCredential(appId: string): Promise<void>;
29
+ /**
30
+ * 保存或更新微信凭据
31
+ */
32
+ saveWechatCredential(appId: string, appSecret: string, alias?: string | null): Promise<void>;
33
+ /**
34
+ * 删除微信凭据 (通过 appId 或 alias)
35
+ */
36
+ deleteWechatCredential(appIdOrAlias: string): Promise<void>;
24
37
  }
@@ -25,6 +25,7 @@ export declare class WechatPublisher {
25
25
  uploadImage(file: Blob, filename: string, accessToken: string, appId?: string): Promise<WechatUploadResponse>;
26
26
  publishToDraft(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
27
27
  clearCache(): Promise<void>;
28
+ setExternalToken(appid: string, accessToken: string): Promise<void>;
28
29
  }
29
30
  export * from "./tokenStore.js";
30
31
  export * from "./uploadCacheStore.js";
@@ -17,8 +17,9 @@ export declare class TokenStore {
17
17
  private load;
18
18
  private save;
19
19
  waitForInit(): Promise<void>;
20
- isValid(appid: string): boolean;
21
- getToken(appid: string): string | null;
20
+ isValid(appid: string): Promise<boolean>;
21
+ getToken(appid: string): Promise<string | null>;
22
22
  setToken(appid: string, accessToken: string, expiresIn: number): Promise<void>;
23
+ setExternalToken(appid: string, accessToken: string): Promise<void>;
23
24
  clear(): Promise<void>;
24
25
  }
package/dist/wrapper.js CHANGED
@@ -731,14 +731,14 @@ function ensureSvgPolyfills(window) {
731
731
  value() {
732
732
  const tagName = this.tagName.toLowerCase();
733
733
  if (tagName === "text" || tagName === "tspan") {
734
- const text = this.textContent ?? "";
735
- const width = Math.max(text.length * 8, 16);
734
+ const width = getEstimatedTextWidth(this);
735
+ const height = getEstimatedFontSize(this);
736
736
  return {
737
737
  x: 0,
738
- y: -16,
738
+ y: -height,
739
739
  width,
740
- height: 16,
741
- top: -16,
740
+ height,
741
+ top: -height,
742
742
  right: width,
743
743
  bottom: 0,
744
744
  left: 0,
@@ -809,12 +809,61 @@ function ensureSvgPolyfills(window) {
809
809
  configurable: true,
810
810
  writable: true,
811
811
  value() {
812
- const text = this.textContent ?? "";
813
- return Math.max(text.length * 8, 16);
812
+ return getEstimatedTextWidth(this);
814
813
  }
815
814
  });
816
815
  }
817
816
  }
817
+ function getEstimatedTextWidth(element) {
818
+ const text = element.textContent ?? "";
819
+ const fontSize = getEstimatedFontSize(element);
820
+ const visualLength = Array.from(text).reduce((length, character) => {
821
+ return length + (isWideCharacter(character) ? 2 : 1);
822
+ }, 0);
823
+ return Math.max(visualLength * (fontSize / 2), fontSize);
824
+ }
825
+ function getEstimatedFontSize(element) {
826
+ const inlineFontSize = element.getAttribute("font-size");
827
+ if (inlineFontSize) {
828
+ const parsed = Number.parseFloat(inlineFontSize);
829
+ if (Number.isFinite(parsed)) {
830
+ return parsed;
831
+ }
832
+ }
833
+ const computedFontSize = element.ownerDocument.defaultView?.getComputedStyle(element).fontSize;
834
+ if (computedFontSize) {
835
+ const parsed = Number.parseFloat(computedFontSize);
836
+ if (Number.isFinite(parsed)) {
837
+ return parsed;
838
+ }
839
+ }
840
+ return 16;
841
+ }
842
+ function isWideCharacter(character) {
843
+ return character.codePointAt(0) > 255;
844
+ }
845
+ async function extractAndCleanImages(body) {
846
+ const html = await wenyanCoreInstance.renderMarkdown(body);
847
+ const dom = new JSDOM(`<body><section id="wenyan">${html}</section></body>`);
848
+ const document = dom.window.document;
849
+ const wenyan = document.getElementById("wenyan");
850
+ const images = Array.from(wenyan.querySelectorAll("img"));
851
+ const imagePaths = images.map((img) => img.getAttribute("src")).filter((src) => !!src).map((src) => {
852
+ try {
853
+ return decodeURI(src);
854
+ } catch {
855
+ return src;
856
+ }
857
+ });
858
+ for (const img of images) {
859
+ const parent = img.parentElement;
860
+ img.remove();
861
+ if (parent && parent.tagName === "P" && parent.textContent?.trim() === "") {
862
+ parent.remove();
863
+ }
864
+ }
865
+ return { imagePaths, cleanedHtml: wenyan.outerHTML };
866
+ }
818
867
  const nodeMermaidRenderer = createNodeMermaidRenderer();
819
868
  const wenyanCoreInstance = await createWenyanCore({
820
869
  mermaid: {
@@ -859,6 +908,13 @@ async function renderStyledContent(content, options = {}, coreInstance = wenyanC
859
908
  async function prepareRenderContext(inputContent, options, getInputContent) {
860
909
  const { content, absoluteDirPath } = await getInputContent(inputContent, options.file);
861
910
  const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
911
+ if (preHandlerContent.type === "image" && !preHandlerContent.image_list) {
912
+ const { imagePaths, cleanedHtml } = await extractAndCleanImages(preHandlerContent.content);
913
+ if (imagePaths.length > 0) {
914
+ preHandlerContent.image_list = imagePaths;
915
+ preHandlerContent.content = cleanedHtml;
916
+ }
917
+ }
862
918
  if (preHandlerContent.image_list && preHandlerContent.image_list.length > 0) {
863
919
  return { gzhContent: preHandlerContent, absoluteDirPath };
864
920
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "3.0.7",
3
+ "version": "3.0.9",
4
4
  "description": "Core library for Wenyan markdown rendering & publishing",
5
5
  "author": "Lei <caol64@gmail.com> (https://github.com/caol64)",
6
6
  "license": "Apache-2.0",