@wenyan-md/core 3.0.5 → 3.0.7

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
@@ -194,9 +194,9 @@ function renderListStyleFootnotes(footnotes) {
194
194
  }
195
195
  async function handleFrontMatter(markdown) {
196
196
  const { attributes, body } = fm(markdown);
197
- const result = { body: body || "" };
197
+ const result = { content: body || "" };
198
198
  let head = "";
199
- const { title, description, cover, author, source_url } = attributes;
199
+ const { title, description, cover, author, source_url, need_open_comment, only_fans_can_comment, image_list } = attributes;
200
200
  if (title) {
201
201
  result.title = title;
202
202
  }
@@ -208,7 +208,7 @@ async function handleFrontMatter(markdown) {
208
208
  result.cover = cover;
209
209
  }
210
210
  if (head) {
211
- result.body = head + result.body;
211
+ result.content = head + result.content;
212
212
  }
213
213
  if (author) {
214
214
  result.author = author;
@@ -216,6 +216,15 @@ async function handleFrontMatter(markdown) {
216
216
  if (source_url) {
217
217
  result.source_url = source_url;
218
218
  }
219
+ if (need_open_comment !== void 0) {
220
+ result.need_open_comment = need_open_comment;
221
+ }
222
+ if (only_fans_can_comment !== void 0) {
223
+ result.only_fans_can_comment = only_fans_can_comment;
224
+ }
225
+ if (image_list) {
226
+ result.image_list = image_list;
227
+ }
219
228
  return result;
220
229
  }
221
230
  const parseOptions = {
@@ -425,6 +434,28 @@ function createMarkedClient() {
425
434
  md.use(highlightExtension);
426
435
  md.use({
427
436
  extensions: [
437
+ // 宽松 link/image tokenizer,允许 URL 中包含空格(不要求用 <> 包裹)
438
+ {
439
+ name: "looseLink",
440
+ level: "inline",
441
+ start(src) {
442
+ return src.match(/!?\[/)?.index;
443
+ },
444
+ tokenizer(src) {
445
+ const rule = /^(!?)\[([^\]]*)\]\(([^)]+)\)/;
446
+ const match = rule.exec(src);
447
+ if (!match) return;
448
+ const isImage = !!match[1];
449
+ return {
450
+ type: isImage ? "image" : "link",
451
+ raw: match[0],
452
+ text: match[2],
453
+ href: match[3],
454
+ tokens: this.lexer.inlineTokens(match[2])
455
+ };
456
+ }
457
+ },
458
+ // 自定义图片语法扩展 ![](){...}
428
459
  {
429
460
  name: "attributeImage",
430
461
  level: "inline",
@@ -453,7 +484,8 @@ function createMarkedClient() {
453
484
  renderer(token) {
454
485
  const attrs = stringToMap(token.attrs);
455
486
  const styleStr = Array.from(attrs).map(([k, v]) => /^\d+$/.test(v) ? `${k}:${v}px` : `${k}:${v}`).join("; ");
456
- return `<img src="${token.href}" alt="${token.text || ""}" title="${token.text || ""}" style="${styleStr}">`;
487
+ const href = normalizeHref(token.href);
488
+ return `<img src="${href}" alt="${token.text || ""}" title="${token.text || ""}" style="${styleStr}">`;
457
489
  }
458
490
  }
459
491
  ]
@@ -481,7 +513,12 @@ function createMarkedClient() {
481
513
  },
482
514
  // 重写普通图片 (处理标准 Markdown 图片)
483
515
  image(token) {
484
- return `<img src="${token.href}" alt="${token.text || ""}" title="${token.title || token.text || ""}">`;
516
+ const href = normalizeHref(token.href);
517
+ return `<img src="${href}" alt="${token.text || ""}" title="${token.title || token.text || ""}">`;
518
+ },
519
+ link(token) {
520
+ const href = normalizeHref(token.href);
521
+ return `<a href="${href}">${this.parser.parseInline(token.tokens)}</a>`;
485
522
  }
486
523
  }
487
524
  });
@@ -494,10 +531,21 @@ function createMarkedClient() {
494
531
  */
495
532
  async parse(markdown) {
496
533
  await configure();
497
- return md.parse(markdown);
534
+ return await md.parse(markdown);
498
535
  }
499
536
  };
500
537
  }
538
+ function normalizeHref(href) {
539
+ href = href.trim();
540
+ if (href.startsWith("<") && href.endsWith(">")) {
541
+ href = href.slice(1, -1);
542
+ }
543
+ try {
544
+ return encodeURI(href);
545
+ } catch {
546
+ return href;
547
+ }
548
+ }
501
549
  let htmlHandlerRegistered = false;
502
550
  function createMathJaxParser(options = {}) {
503
551
  const adaptor = liteAdaptor();
@@ -556,6 +604,36 @@ function createMathJaxParser(options = {}) {
556
604
  }
557
605
  };
558
606
  }
607
+ function createMermaidParser(options) {
608
+ const resolvedOptions = resolveMermaidOptions(options);
609
+ return {
610
+ async parser(html) {
611
+ if (!resolvedOptions.enabled || !containsMermaidCodeBlock(html)) {
612
+ return html;
613
+ }
614
+ if (!resolvedOptions.renderer) {
615
+ throw new Error("Mermaid 渲染已启用,但未配置 renderer");
616
+ }
617
+ return await resolvedOptions.renderer.renderHtml(html);
618
+ }
619
+ };
620
+ }
621
+ function resolveMermaidOptions(options) {
622
+ if (options === void 0) {
623
+ return { enabled: false };
624
+ }
625
+ if (typeof options === "boolean") {
626
+ return { enabled: options };
627
+ }
628
+ return {
629
+ enabled: options.enabled ?? true,
630
+ renderer: options.renderer
631
+ };
632
+ }
633
+ function containsMermaidCodeBlock(html) {
634
+ return html.includes("language-mermaid");
635
+ }
636
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
559
637
  function wechatPostRender(element) {
560
638
  const mathElements = element.querySelectorAll("mjx-container");
561
639
  mathElements.forEach((mathContainer) => {
@@ -574,6 +652,13 @@ function wechatPostRender(element) {
574
652
  }
575
653
  }
576
654
  });
655
+ const mermaidSvgs = element.querySelectorAll('[data-wenyan-diagram="mermaid"] svg');
656
+ mermaidSvgs.forEach((svg) => {
657
+ svg.style.maxWidth = "100%";
658
+ svg.style.height = "auto";
659
+ inlineMermaidSvgStyles(svg);
660
+ flattenMermaidMarkers(svg);
661
+ });
577
662
  const codeElements = element.querySelectorAll("pre code");
578
663
  codeElements.forEach((codeEl) => {
579
664
  codeEl.innerHTML = codeEl.innerHTML.replace(/\n/g, "<br>").replace(/(>[^<]+)|(^[^<]+)/g, (str) => str.replace(/\s/g, "&nbsp;"));
@@ -590,6 +675,268 @@ function wechatPostRender(element) {
590
675
  element.style.color = "rgb(0, 0, 0)";
591
676
  element.style.caretColor = "rgb(0, 0, 0)";
592
677
  }
678
+ function inlineMermaidSvgStyles(svg) {
679
+ const styleElements = Array.from(svg.querySelectorAll("style"));
680
+ styleElements.forEach((styleElement) => {
681
+ applyInlineStylesFromCss(svg, styleElement.textContent ?? "");
682
+ styleElement.remove();
683
+ });
684
+ }
685
+ function flattenMermaidMarkers(svg) {
686
+ const convertedMarkerIds = /* @__PURE__ */ new Set();
687
+ const markedElements = Array.from(svg.querySelectorAll("[marker-start], [marker-end]"));
688
+ markedElements.forEach((markedElement) => {
689
+ convertMarkerReference(svg, markedElement, "start", convertedMarkerIds);
690
+ convertMarkerReference(svg, markedElement, "end", convertedMarkerIds);
691
+ });
692
+ convertedMarkerIds.forEach((markerId) => {
693
+ findMarkerById(svg, markerId)?.remove();
694
+ });
695
+ svg.querySelectorAll("defs").forEach((defs) => {
696
+ if (!defs.children.length) {
697
+ defs.remove();
698
+ }
699
+ });
700
+ }
701
+ function convertMarkerReference(svg, sourceElement, position, convertedMarkerIds) {
702
+ const markerAttribute = `marker-${position}`;
703
+ const markerValue = sourceElement.getAttribute(markerAttribute);
704
+ if (!markerValue) {
705
+ return;
706
+ }
707
+ const markerId = extractMarkerId(markerValue);
708
+ if (!markerId) {
709
+ return;
710
+ }
711
+ const marker = findMarkerById(svg, markerId);
712
+ if (!marker) {
713
+ return;
714
+ }
715
+ const flattenedMarker = createFlattenedMarker(svg, sourceElement, marker, position);
716
+ if (!flattenedMarker) {
717
+ return;
718
+ }
719
+ sourceElement.parentNode?.appendChild(flattenedMarker);
720
+ sourceElement.removeAttribute(markerAttribute);
721
+ convertedMarkerIds.add(markerId);
722
+ }
723
+ function createFlattenedMarker(svg, sourceElement, marker, position) {
724
+ const terminalPoints = getTerminalPoints(sourceElement, position);
725
+ if (!terminalPoints) {
726
+ return null;
727
+ }
728
+ const ownerDocument = svg.ownerDocument;
729
+ const markerGroup = ownerDocument.createElementNS(SVG_NAMESPACE, "g");
730
+ const markerStyle = marker.getAttribute("style");
731
+ const angle = getMarkerAngleDegrees(position, terminalPoints.anchor, terminalPoints.reference, marker);
732
+ markerGroup.setAttribute("data-wenyan-marker", position);
733
+ markerGroup.setAttribute("transform", buildMarkerTransform(marker, terminalPoints.anchor, angle, position));
734
+ if (markerStyle) {
735
+ markerGroup.setAttribute("style", markerStyle);
736
+ }
737
+ Array.from(marker.children).forEach((child) => {
738
+ markerGroup.appendChild(child.cloneNode(true));
739
+ });
740
+ return markerGroup;
741
+ }
742
+ function getTerminalPoints(sourceElement, position) {
743
+ const points = getEdgePoints(sourceElement);
744
+ if (points.length < 2) {
745
+ return null;
746
+ }
747
+ if (position === "end") {
748
+ const anchor2 = points.at(-1);
749
+ if (!anchor2) {
750
+ return null;
751
+ }
752
+ const reference2 = findDistinctPoint(points, points.length - 2, -1, anchor2);
753
+ return reference2 ? { anchor: anchor2, reference: reference2 } : null;
754
+ }
755
+ const anchor = points[0];
756
+ if (!anchor) {
757
+ return null;
758
+ }
759
+ const reference = findDistinctPoint(points, 1, 1, anchor);
760
+ return reference ? { anchor, reference } : null;
761
+ }
762
+ function getEdgePoints(sourceElement) {
763
+ const encodedPoints = sourceElement.getAttribute("data-points");
764
+ if (encodedPoints) {
765
+ const decodedPoints = decodeMermaidPoints(sourceElement, encodedPoints);
766
+ if (decodedPoints.length >= 2) {
767
+ return decodedPoints;
768
+ }
769
+ }
770
+ const polyPoints = sourceElement.getAttribute("points");
771
+ if (polyPoints) {
772
+ return parseCoordinatePairs(polyPoints);
773
+ }
774
+ const pathData = sourceElement.getAttribute("d");
775
+ if (pathData) {
776
+ return parseCoordinatePairs(pathData);
777
+ }
778
+ return [];
779
+ }
780
+ function decodeMermaidPoints(sourceElement, encodedPoints) {
781
+ const defaultView = sourceElement.ownerDocument.defaultView;
782
+ try {
783
+ const decoded = defaultView?.atob?.(encodedPoints) ?? (typeof atob === "function" ? atob(encodedPoints) : "");
784
+ const parsed = JSON.parse(decoded);
785
+ return parsed.filter((point) => typeof point.x === "number" && typeof point.y === "number").map((point) => ({
786
+ x: point.x,
787
+ y: point.y
788
+ }));
789
+ } catch {
790
+ return [];
791
+ }
792
+ }
793
+ function parseCoordinatePairs(value) {
794
+ const matches = Array.from(value.matchAll(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi), (match) => Number(match[0]));
795
+ const points = [];
796
+ for (let index = 0; index + 1 < matches.length; index += 2) {
797
+ points.push({
798
+ x: matches[index],
799
+ y: matches[index + 1]
800
+ });
801
+ }
802
+ return points;
803
+ }
804
+ function findDistinctPoint(points, startIndex, step, anchor) {
805
+ for (let index = startIndex; index >= 0 && index < points.length; index += step) {
806
+ const point = points[index];
807
+ if (!point) {
808
+ continue;
809
+ }
810
+ if (point.x !== anchor.x || point.y !== anchor.y) {
811
+ return point;
812
+ }
813
+ }
814
+ return null;
815
+ }
816
+ function getMarkerAngleDegrees(position, anchor, reference, marker) {
817
+ const baseAngle = position === "end" ? Math.atan2(anchor.y - reference.y, anchor.x - reference.x) : Math.atan2(reference.y - anchor.y, reference.x - anchor.x);
818
+ const orient = marker.getAttribute("orient")?.trim();
819
+ let angle = baseAngle * 180 / Math.PI;
820
+ if (orient === "auto-start-reverse" && position === "start") {
821
+ angle += 180;
822
+ } else if (orient && orient !== "auto") {
823
+ const offset = Number.parseFloat(orient);
824
+ if (Number.isFinite(offset)) {
825
+ angle += offset;
826
+ }
827
+ }
828
+ return angle;
829
+ }
830
+ function buildMarkerTransform(marker, anchor, angle, position) {
831
+ const viewBox = parseViewBox(marker.getAttribute("viewBox"));
832
+ const markerWidth = parseNumericAttribute(marker.getAttribute("markerWidth"), viewBox?.width ?? 1);
833
+ const markerHeight = parseNumericAttribute(marker.getAttribute("markerHeight"), viewBox?.height ?? 1);
834
+ const viewBoxWidth = viewBox?.width ?? markerWidth;
835
+ const viewBoxHeight = viewBox?.height ?? markerHeight;
836
+ const scaleX = viewBoxWidth === 0 ? 1 : markerWidth / viewBoxWidth;
837
+ const scaleY = viewBoxHeight === 0 ? 1 : markerHeight / viewBoxHeight;
838
+ const offsetX = parseNumericAttribute(marker.getAttribute("refX")) + (viewBox?.minX ?? 0);
839
+ const offsetY = parseNumericAttribute(marker.getAttribute("refY")) + (viewBox?.minY ?? 0);
840
+ const adjustedAnchor = adjustMarkerAnchor(anchor, angle, scaleX, viewBox, offsetX, position);
841
+ return [
842
+ `translate(${adjustedAnchor.x} ${adjustedAnchor.y})`,
843
+ `rotate(${angle})`,
844
+ `scale(${scaleX} ${scaleY})`,
845
+ `translate(${-offsetX} ${-offsetY})`
846
+ ].join(" ");
847
+ }
848
+ function adjustMarkerAnchor(anchor, angle, scaleX, viewBox, refX, position) {
849
+ const minX = viewBox?.minX ?? 0;
850
+ const maxX = viewBox ? viewBox.minX + viewBox.width : refX;
851
+ const overlap = position === "start" ? Math.max(0, refX - minX) : Math.max(0, maxX - refX);
852
+ if (overlap === 0) {
853
+ return anchor;
854
+ }
855
+ const distance = overlap * scaleX;
856
+ const radians = angle * Math.PI / 180;
857
+ const direction = position === "start" ? 1 : -1;
858
+ return {
859
+ x: anchor.x + Math.cos(radians) * distance * direction,
860
+ y: anchor.y + Math.sin(radians) * distance * direction
861
+ };
862
+ }
863
+ function parseViewBox(viewBoxValue) {
864
+ if (!viewBoxValue) {
865
+ return null;
866
+ }
867
+ const values = viewBoxValue.trim().split(/[\s,]+/).map((value) => Number.parseFloat(value));
868
+ if (values.length !== 4 || values.some((value) => !Number.isFinite(value))) {
869
+ return null;
870
+ }
871
+ return {
872
+ minX: values[0],
873
+ minY: values[1],
874
+ width: values[2],
875
+ height: values[3]
876
+ };
877
+ }
878
+ function parseNumericAttribute(value, fallback = 0) {
879
+ if (!value) {
880
+ return fallback;
881
+ }
882
+ const parsed = Number.parseFloat(value);
883
+ return Number.isFinite(parsed) ? parsed : fallback;
884
+ }
885
+ function extractMarkerId(markerValue) {
886
+ const match = markerValue.match(/url\(#([^)]+)\)/);
887
+ return match?.[1] ?? null;
888
+ }
889
+ function findMarkerById(svg, markerId) {
890
+ return Array.from(svg.querySelectorAll("marker")).find((marker) => marker.getAttribute("id") === markerId);
891
+ }
892
+ function applyInlineStylesFromCss(svg, cssText) {
893
+ const ast = csstree.parse(cssText, parseOptions);
894
+ csstree.walk(ast, {
895
+ visit: "Rule",
896
+ enter(node) {
897
+ if (node.prelude.type !== "SelectorList") return;
898
+ const declarations = node.block.children.toArray().filter((decl) => decl.type === "Declaration");
899
+ if (declarations.length === 0) return;
900
+ node.prelude.children.forEach((selectorNode) => {
901
+ const selector = csstree.generate(selectorNode).trim();
902
+ if (!selector || selector.includes("::")) return;
903
+ const targets = resolveInlineTargets(svg, selector);
904
+ targets.forEach((target) => {
905
+ declarations.forEach((decl) => {
906
+ const value = csstree.generate(decl.value);
907
+ const priority = decl.important ? "important" : "";
908
+ target.style.setProperty(decl.property, value, priority);
909
+ });
910
+ });
911
+ });
912
+ }
913
+ });
914
+ }
915
+ function resolveInlineTargets(svg, selector) {
916
+ if (selector === ":root") {
917
+ return [svg];
918
+ }
919
+ if (selector.includes(":")) {
920
+ return [];
921
+ }
922
+ const targets = /* @__PURE__ */ new Set();
923
+ if (svg.matches(selector)) {
924
+ targets.add(svg);
925
+ }
926
+ svg.querySelectorAll(selector).forEach((node) => {
927
+ if (isInlineStyleTarget(node)) {
928
+ targets.add(node);
929
+ }
930
+ });
931
+ return Array.from(targets);
932
+ }
933
+ function isInlineStyleTarget(node) {
934
+ const defaultView = node.ownerDocument.defaultView;
935
+ if (!defaultView) {
936
+ return false;
937
+ }
938
+ return node instanceof defaultView.SVGElement || node instanceof defaultView.HTMLElement;
939
+ }
593
940
  const themeModifier = createCssModifier({});
594
941
  function renderTheme(wenyanElement, themeCss) {
595
942
  const modifiedCss = themeModifier(themeCss);
@@ -1160,10 +1507,104 @@ function getContentForToutiao(wenyanElement) {
1160
1507
  });
1161
1508
  return wenyanElement.outerHTML;
1162
1509
  }
1510
+ const DEFAULT_MERMAID_CONFIG = {
1511
+ htmlLabels: false,
1512
+ flowchart: {
1513
+ htmlLabels: false
1514
+ }
1515
+ };
1516
+ async function replaceMermaidCodeBlocks(root, renderDiagram) {
1517
+ const codeBlocks = Array.from(root.querySelectorAll("pre > code.language-mermaid"));
1518
+ if (codeBlocks.length === 0) {
1519
+ return false;
1520
+ }
1521
+ const ownerDocument = getOwnerDocument(root);
1522
+ for (const [index, codeBlock] of codeBlocks.entries()) {
1523
+ const pre = codeBlock.parentElement;
1524
+ const parent = pre?.parentElement;
1525
+ if (!pre || !parent) {
1526
+ continue;
1527
+ }
1528
+ const code = codeBlock.textContent ?? "";
1529
+ const svg = await renderDiagram({
1530
+ id: `wenyan-mermaid-${index + 1}`,
1531
+ code
1532
+ });
1533
+ const figure = ownerDocument.createElement("figure");
1534
+ figure.setAttribute("data-wenyan-diagram", "mermaid");
1535
+ figure.innerHTML = svg.trim();
1536
+ pre.replaceWith(figure);
1537
+ }
1538
+ return true;
1539
+ }
1540
+ function createBrowserMermaidRenderer(options = {}) {
1541
+ let mermaidModulePromise = null;
1542
+ return {
1543
+ async renderHtml(html) {
1544
+ if (typeof DOMParser === "undefined") {
1545
+ throw new Error("当前环境不支持浏览器 Mermaid 渲染");
1546
+ }
1547
+ const parser = new DOMParser();
1548
+ const document = parser.parseFromString(`<body>${html}</body>`, "text/html");
1549
+ const root = document.body;
1550
+ const mermaid = await getMermaidModule();
1551
+ mermaid.initialize(createMermaidConfig(options.mermaidConfig));
1552
+ try {
1553
+ await replaceMermaidCodeBlocks(root, async ({ id, code }) => {
1554
+ const { svg } = await mermaid.render(id, code);
1555
+ return svg;
1556
+ });
1557
+ } catch (error) {
1558
+ throw createMermaidRenderError(error);
1559
+ }
1560
+ return root.innerHTML;
1561
+ }
1562
+ };
1563
+ async function getMermaidModule() {
1564
+ if (!mermaidModulePromise) {
1565
+ mermaidModulePromise = import("mermaid");
1566
+ }
1567
+ const module = await mermaidModulePromise;
1568
+ return module.default;
1569
+ }
1570
+ }
1571
+ function createMermaidRenderError(error) {
1572
+ const message = error instanceof Error ? error.message : String(error);
1573
+ return new Error(`Mermaid 图表渲染失败: ${message}`);
1574
+ }
1575
+ function createMermaidConfig(overrides = {}) {
1576
+ const flowchartOverrides = getRecord(overrides.flowchart);
1577
+ return {
1578
+ ...DEFAULT_MERMAID_CONFIG,
1579
+ startOnLoad: false,
1580
+ securityLevel: "strict",
1581
+ ...overrides,
1582
+ flowchart: {
1583
+ ...getRecord(DEFAULT_MERMAID_CONFIG.flowchart),
1584
+ ...flowchartOverrides
1585
+ }
1586
+ };
1587
+ }
1588
+ function getOwnerDocument(root) {
1589
+ if ("createElement" in root) {
1590
+ return root;
1591
+ }
1592
+ if ("ownerDocument" in root && root.ownerDocument) {
1593
+ return root.ownerDocument;
1594
+ }
1595
+ throw new Error("无法获取 Mermaid 渲染所需的 Document");
1596
+ }
1597
+ function getRecord(value) {
1598
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1599
+ return value;
1600
+ }
1601
+ return {};
1602
+ }
1163
1603
  async function createWenyanCore(options = {}) {
1164
- const { isConvertMathJax = true, isWechat = true } = options;
1604
+ const { isConvertMathJax = true, isWechat = true, mermaid } = options;
1165
1605
  const markedClient = createMarkedClient();
1166
1606
  const mathJaxParser = createMathJaxParser();
1607
+ const mermaidParser = createMermaidParser(mermaid);
1167
1608
  registerAllBuiltInThemes();
1168
1609
  registerBuiltInHlThemes();
1169
1610
  registerBuiltInMacStyle();
@@ -1172,11 +1613,11 @@ async function createWenyanCore(options = {}) {
1172
1613
  return await handleFrontMatter(markdown);
1173
1614
  },
1174
1615
  async renderMarkdown(markdown) {
1175
- const html = await markedClient.parse(markdown);
1616
+ let html = await markedClient.parse(markdown);
1176
1617
  if (isConvertMathJax) {
1177
- return mathJaxParser.parser(html);
1618
+ html = mathJaxParser.parser(html);
1178
1619
  }
1179
- return html;
1620
+ return await mermaidParser.parser(html);
1180
1621
  },
1181
1622
  async applyStylesWithTheme(wenyanElement, options2 = {}) {
1182
1623
  const {
@@ -1279,7 +1720,10 @@ const DEFAULT_CSS_UPDATES = {
1279
1720
  };
1280
1721
  export {
1281
1722
  addFootnotes,
1723
+ createBrowserMermaidRenderer,
1282
1724
  createCssModifier,
1725
+ createMermaidConfig,
1726
+ createMermaidRenderError,
1283
1727
  createWenyanCore,
1284
1728
  getAllGzhThemes,
1285
1729
  getAllHlThemes,
@@ -1296,6 +1740,7 @@ export {
1296
1740
  registerHlTheme,
1297
1741
  registerMacStyle,
1298
1742
  registerTheme,
1743
+ replaceMermaidCodeBlocks,
1299
1744
  sansSerif,
1300
1745
  serif
1301
1746
  };
@@ -1,7 +1,9 @@
1
- import { FrontMatterResult } from "./parser/frontMatterParser.js";
1
+ import { type FrontMatterResult } from "./parser/frontMatterParser.js";
2
+ import type { MermaidOptions } from "./mermaid.js";
2
3
  export interface WenyanOptions {
3
4
  isConvertMathJax?: boolean;
4
5
  isWechat?: boolean;
6
+ mermaid?: boolean | MermaidOptions;
5
7
  }
6
8
  export interface ApplyStylesOptions {
7
9
  themeId?: string;
@@ -31,4 +33,6 @@ export * from "./platform/medium.js";
31
33
  export * from "./platform/zhihu.js";
32
34
  export * from "./platform/toutiao.js";
33
35
  export { addFootnotes } from "./renderer/footnotesRender.js";
36
+ export * from "./mermaid.js";
34
37
  export type WenyanCoreInstance = Awaited<ReturnType<typeof createWenyanCore>>;
38
+ export type { FrontMatterResult } from "./parser/frontMatterParser.js";
@@ -0,0 +1,19 @@
1
+ export interface MermaidDiagram {
2
+ id: string;
3
+ code: string;
4
+ }
5
+ export type MermaidDiagramRenderer = (diagram: MermaidDiagram) => Promise<string>;
6
+ export interface MermaidRenderer {
7
+ renderHtml(html: string): Promise<string>;
8
+ }
9
+ export interface MermaidOptions {
10
+ enabled?: boolean;
11
+ renderer?: MermaidRenderer;
12
+ }
13
+ export interface MermaidRendererFactoryOptions {
14
+ mermaidConfig?: Record<string, unknown>;
15
+ }
16
+ export declare function replaceMermaidCodeBlocks(root: ParentNode, renderDiagram: MermaidDiagramRenderer): Promise<boolean>;
17
+ export declare function createBrowserMermaidRenderer(options?: MermaidRendererFactoryOptions): MermaidRenderer;
18
+ export declare function createMermaidRenderError(error: unknown): Error;
19
+ export declare function createMermaidConfig(overrides?: Record<string, unknown>): Record<string, unknown>;
@@ -1,9 +1,12 @@
1
1
  export interface FrontMatterResult {
2
- body: string;
2
+ content: string;
3
3
  title?: string;
4
- cover?: string;
5
4
  description?: string;
5
+ cover?: string;
6
6
  author?: string;
7
7
  source_url?: string;
8
+ need_open_comment?: boolean;
9
+ only_fans_can_comment?: boolean;
10
+ image_list?: string[];
8
11
  }
9
12
  export declare function handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
@@ -0,0 +1,9 @@
1
+ import type { MermaidOptions, MermaidRenderer } from "../mermaid.js";
2
+ export interface ResolvedMermaidOptions {
3
+ enabled: boolean;
4
+ renderer?: MermaidRenderer;
5
+ }
6
+ export declare function createMermaidParser(options?: boolean | MermaidOptions): {
7
+ parser(html: string): Promise<string>;
8
+ };
9
+ export declare function resolveMermaidOptions(options?: boolean | MermaidOptions): ResolvedMermaidOptions;
@@ -13,3 +13,4 @@ export declare function uploadStyledContent(gzhContent: StyledContent, serverUrl
13
13
  export declare function requestServerPublish(mdFileId: string, serverUrl: string, headers: Record<string, string>, options: ClientPublishOptions): Promise<string>;
14
14
  export declare function uploadLocalImages(content: string, serverUrl: string, headers: Record<string, string>, relativePath?: string): Promise<string>;
15
15
  export declare function uploadCover(serverUrl: string, headers: Record<string, string>, cover?: string, relativePath?: string): Promise<string | undefined>;
16
+ export declare function uploadImageList(serverUrl: string, headers: Record<string, string>, imageList?: string[], relativePath?: string): Promise<string[]>;
@@ -0,0 +1,2 @@
1
+ import { type MermaidRenderer, type MermaidRendererFactoryOptions } from "../core/mermaid.js";
2
+ export declare function createNodeMermaidRenderer(options?: MermaidRendererFactoryOptions): MermaidRenderer;
@@ -1,5 +1,5 @@
1
- import { WechatPublishResponse } from "../wechat.js";
2
- import { ArticleOptions, WechatPublisher } from "../publish.js";
1
+ import type { WechatPublishResponse } from "../wechat.js";
2
+ import { ArticleOptions, ImageTextArticleOptions, WechatPublisher } from "../publish.js";
3
3
  import { CredentialStore } from "../credentialStore.js";
4
4
  export declare const wechatPublisher: WechatPublisher;
5
5
  export declare const credentialStore: CredentialStore;
@@ -9,5 +9,9 @@ interface PublishOptions {
9
9
  relativePath?: string;
10
10
  }
11
11
  export declare function publishToWechatDraft(articleOptions: ArticleOptions, publishOptions?: PublishOptions): Promise<WechatPublishResponse>;
12
+ /**
13
+ * @deprecated use publishToWechatDraft instead
14
+ */
12
15
  export declare function publishToDraft(title: string, content: string, cover?: string, options?: PublishOptions): Promise<WechatPublishResponse>;
16
+ export declare function publishImageTextToWechatDraft(articleOptions: ImageTextArticleOptions, publishOptions?: PublishOptions): Promise<WechatPublishResponse>;
13
17
  export {};
@@ -1,5 +1,4 @@
1
- import { ApplyStylesOptions } from "../core/index.js";
2
- import { GetInputContentFn, RenderContext, RenderOptions, StyledContent } from "./types.js";
3
- export declare function renderWithTheme(markdownContent: string, options: RenderOptions): Promise<StyledContent>;
4
- export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
1
+ import { ApplyStylesOptions, WenyanCoreInstance } from "../core/index.js";
2
+ import { GetInputContentFn, RenderContext, RenderOptions } from "./types.js";
3
+ export declare function renderStyledContent(content: string, options?: ApplyStylesOptions, coreInstance?: WenyanCoreInstance): Promise<string>;
5
4
  export declare function prepareRenderContext(inputContent: string | undefined, options: RenderOptions, getInputContent: GetInputContentFn): Promise<RenderContext>;
@@ -1,3 +1,4 @@
1
+ import { FrontMatterResult } from "../core/parser/frontMatterParser.js";
1
2
  export interface RenderOptions {
2
3
  file?: string;
3
4
  theme?: string;
@@ -5,6 +6,7 @@ export interface RenderOptions {
5
6
  highlight: string;
6
7
  macStyle: boolean;
7
8
  footnote: boolean;
9
+ mermaid?: boolean;
8
10
  }
9
11
  export interface PublishOptions extends RenderOptions {
10
12
  appId?: string;
@@ -18,14 +20,7 @@ export interface RenderContext {
18
20
  gzhContent: StyledContent;
19
21
  absoluteDirPath: string | undefined;
20
22
  }
21
- export interface StyledContent {
22
- content: string;
23
- title?: string;
24
- cover?: string;
25
- description?: string;
26
- author?: string;
27
- source_url?: string;
28
- }
23
+ export type StyledContent = FrontMatterResult;
29
24
  export type GetInputContentFn = (inputContent?: string, filePath?: string) => Promise<{
30
25
  content: string;
31
26
  absoluteDirPath?: string;
@@ -1,5 +1,4 @@
1
- import { ClientPublishOptions, GetInputContentFn, PublishOptions, StyledContent } from "./types.js";
2
- export declare function getGzhContent(content: string, themeId: string, hlThemeId: string, isMacStyle?: boolean, isAddFootnote?: boolean): Promise<StyledContent>;
1
+ import { ClientPublishOptions, GetInputContentFn, PublishOptions } from "./types.js";
3
2
  export declare function renderAndPublish(inputContent: string | undefined, options: PublishOptions, getInputContent: GetInputContentFn): Promise<string>;
4
3
  export declare function renderAndPublishToServer(inputContent: string | undefined, options: ClientPublishOptions, getInputContent: GetInputContentFn): Promise<string>;
5
4
  export * from "./configStore.js";
@@ -8,6 +8,11 @@ export interface ArticleOptions {
8
8
  cover?: string;
9
9
  author?: string;
10
10
  source_url?: string;
11
+ need_open_comment?: boolean;
12
+ only_fans_can_comment?: boolean;
13
+ }
14
+ export interface ImageTextArticleOptions extends ArticleOptions {
15
+ images: string[];
11
16
  }
12
17
  export declare class WechatPublisher {
13
18
  private tokenStore;
@@ -1,10 +1,28 @@
1
1
  import type { HttpAdapter } from "./http.js";
2
+ export interface ImageCropPercent {
3
+ ratio: string;
4
+ x1: number;
5
+ y1: number;
6
+ x2: number;
7
+ y2: number;
8
+ }
9
+ export interface ImageListItem {
10
+ image_media_id: string;
11
+ crop_percent_list?: ImageCropPercent[];
12
+ }
13
+ export interface ImageInfo {
14
+ image_list: ImageListItem[];
15
+ }
2
16
  export interface WechatPublishOptions {
3
17
  title: string;
4
18
  author?: string;
5
19
  content: string;
6
20
  thumb_media_id: string;
7
21
  content_source_url?: string;
22
+ article_type?: "news" | "newspic";
23
+ image_info?: ImageInfo;
24
+ need_open_comment?: 0 | 1;
25
+ only_fans_can_comment?: 0 | 1;
8
26
  }
9
27
  export interface WechatErrorResponse {
10
28
  errcode: number;
package/dist/wrapper.js CHANGED
@@ -10,7 +10,7 @@ import { FormDataEncoder } from "form-data-encoder";
10
10
  import { FormData } from "formdata-node";
11
11
  import { Readable } from "node:stream";
12
12
  import { defaultTokenCache, defaultCredential, WechatPublisher, CredentialStore } from "./publish.js";
13
- import { createWenyanCore, getAllGzhThemes } from "./core.js";
13
+ import { replaceMermaidCodeBlocks, createMermaidRenderError, createMermaidConfig, createWenyanCore, getAllGzhThemes } from "./core.js";
14
14
  const configDir = (() => {
15
15
  if (process.env.XDG_CONFIG_HOME) {
16
16
  return path.join(process.env.XDG_CONFIG_HOME, "wenyan-md");
@@ -327,6 +327,19 @@ async function uploadCover(serverUrl, headers, cover, relativePath) {
327
327
  }
328
328
  return cover;
329
329
  }
330
+ async function uploadImageList(serverUrl, headers, imageList, relativePath) {
331
+ if (imageList && imageList.length > 0) {
332
+ const uploadPromises = imageList.map(async (image) => {
333
+ if (needUpload(image)) {
334
+ const newImageUrl = await uploadLocalImage(image, serverUrl, headers, relativePath);
335
+ return newImageUrl || image;
336
+ }
337
+ return image;
338
+ });
339
+ return await Promise.all(uploadPromises);
340
+ }
341
+ return imageList || [];
342
+ }
330
343
  const nodeHttpAdapter = {
331
344
  fetch,
332
345
  createMultipart(field, file, filename) {
@@ -463,7 +476,7 @@ async function uploadImages(content, accessToken, relativePath, appId) {
463
476
  return { html: updatedHtml, firstImageId };
464
477
  }
465
478
  async function publishToWechatDraft(articleOptions, publishOptions = {}) {
466
- const { title, content, cover, author, source_url } = articleOptions;
479
+ const { title, content, cover, author, source_url, need_open_comment, only_fans_can_comment } = articleOptions;
467
480
  const { appId, appSecret, relativePath } = publishOptions;
468
481
  const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
469
482
  const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
@@ -498,7 +511,9 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
498
511
  content: html,
499
512
  thumb_media_id: thumbMediaId,
500
513
  author,
501
- content_source_url: source_url
514
+ content_source_url: source_url,
515
+ need_open_comment: need_open_comment ? 1 : 0,
516
+ only_fans_can_comment: only_fans_can_comment ? 1 : 0
502
517
  });
503
518
  if (data.media_id) {
504
519
  return data;
@@ -526,6 +541,52 @@ async function getAppIdAndSecret(appId, appSecret) {
526
541
  }
527
542
  throw new Error(`未能找到 AppID 为 "${appId}" 的公众号凭据,请检查配置文件。`);
528
543
  }
544
+ async function publishImageTextToWechatDraft(articleOptions, publishOptions = {}) {
545
+ const { title, content, images, cover, author, need_open_comment, only_fans_can_comment } = articleOptions;
546
+ const { appId, appSecret, relativePath } = publishOptions;
547
+ const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
548
+ if (!images || images.length === 0) {
549
+ throw new Error("图片消息至少需要一张图片");
550
+ }
551
+ const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
552
+ const imageInfoList = [];
553
+ for (const img of images) {
554
+ const resp = await uploadImage(img, accessToken, void 0, relativePath);
555
+ imageInfoList.push({ image_media_id: resp.media_id });
556
+ }
557
+ let thumbMediaId = "";
558
+ if (cover) {
559
+ const cachedThumbMediaId = mediaIdMapping.get(cover);
560
+ if (cachedThumbMediaId) {
561
+ thumbMediaId = cachedThumbMediaId;
562
+ } else {
563
+ const resp = await uploadImage(cover, accessToken, "cover.jpg", relativePath);
564
+ thumbMediaId = resp.media_id;
565
+ }
566
+ } else {
567
+ thumbMediaId = imageInfoList[0].image_media_id;
568
+ }
569
+ if (!thumbMediaId) {
570
+ throw new Error("未能获取封面图的 media_id");
571
+ }
572
+ const data = await wechatPublisher.publishToDraft(
573
+ accessToken,
574
+ {
575
+ title,
576
+ content,
577
+ thumb_media_id: thumbMediaId,
578
+ author,
579
+ article_type: "newspic",
580
+ image_info: { image_list: imageInfoList },
581
+ need_open_comment: need_open_comment ? 1 : 0,
582
+ only_fans_can_comment: only_fans_can_comment ? 1 : 0
583
+ }
584
+ );
585
+ if (data.media_id) {
586
+ return data;
587
+ }
588
+ throw new Error(`上传图片消息到公众号草稿失败: ${JSON.stringify(data)}`);
589
+ }
529
590
  const defaultConfig = {};
530
591
  const configPath = path.join(configDir, "config.json");
531
592
  class ConfigStore {
@@ -599,7 +660,169 @@ class ConfigStore {
599
660
  }
600
661
  }
601
662
  const configStore = new ConfigStore();
602
- const wenyanCoreInstance = await createWenyanCore();
663
+ function createNodeMermaidRenderer(options = {}) {
664
+ const dom = new JSDOM("<body></body>", { pretendToBeVisual: true });
665
+ const runtimeWindow = dom.window;
666
+ let renderQueue = Promise.resolve();
667
+ let mermaidPromise = null;
668
+ installMermaidGlobals(runtimeWindow);
669
+ return {
670
+ async renderHtml(html) {
671
+ const task = renderQueue.then(async () => {
672
+ const mermaid = await getMermaid();
673
+ dom.window.document.body.innerHTML = html;
674
+ try {
675
+ await replaceMermaidCodeBlocks(dom.window.document.body, async ({ id, code }) => {
676
+ const { svg } = await mermaid.render(id, code);
677
+ return svg;
678
+ });
679
+ return dom.window.document.body.innerHTML;
680
+ } catch (error) {
681
+ throw createMermaidRenderError(error);
682
+ } finally {
683
+ dom.window.document.body.innerHTML = "";
684
+ }
685
+ });
686
+ renderQueue = task.then(
687
+ () => void 0,
688
+ () => void 0
689
+ );
690
+ return await task;
691
+ }
692
+ };
693
+ async function getMermaid() {
694
+ if (!mermaidPromise) {
695
+ mermaidPromise = import("mermaid").then((module) => module.default);
696
+ }
697
+ const mermaid = await mermaidPromise;
698
+ mermaid.initialize(createMermaidConfig(options.mermaidConfig));
699
+ return mermaid;
700
+ }
701
+ }
702
+ function installMermaidGlobals(window) {
703
+ ensureSvgPolyfills(window);
704
+ const globals = [
705
+ ["window", window],
706
+ ["document", window.document],
707
+ ["navigator", window.navigator],
708
+ ["Element", window.Element],
709
+ ["HTMLElement", window.HTMLElement],
710
+ ["SVGElement", window.SVGElement],
711
+ ["Node", window.Node],
712
+ ["Document", window.Document],
713
+ ["DOMParser", window.DOMParser],
714
+ ["XMLSerializer", window.XMLSerializer],
715
+ ["getComputedStyle", window.getComputedStyle.bind(window)]
716
+ ];
717
+ for (const [key, value] of globals) {
718
+ Object.defineProperty(globalThis, key, {
719
+ configurable: true,
720
+ writable: true,
721
+ value
722
+ });
723
+ }
724
+ }
725
+ function ensureSvgPolyfills(window) {
726
+ const svgElementPrototype = window.SVGElement.prototype;
727
+ if (!svgElementPrototype.getBBox) {
728
+ Object.defineProperty(svgElementPrototype, "getBBox", {
729
+ configurable: true,
730
+ writable: true,
731
+ value() {
732
+ const tagName = this.tagName.toLowerCase();
733
+ if (tagName === "text" || tagName === "tspan") {
734
+ const text = this.textContent ?? "";
735
+ const width = Math.max(text.length * 8, 16);
736
+ return {
737
+ x: 0,
738
+ y: -16,
739
+ width,
740
+ height: 16,
741
+ top: -16,
742
+ right: width,
743
+ bottom: 0,
744
+ left: 0,
745
+ toJSON() {
746
+ return this;
747
+ }
748
+ };
749
+ }
750
+ if (tagName === "style" || tagName === "defs" || tagName === "marker") {
751
+ return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0, toJSON() {
752
+ return this;
753
+ } };
754
+ }
755
+ if (tagName === "rect" || tagName === "image") {
756
+ const w = parseFloat(this.getAttribute("width") || "0");
757
+ const h = parseFloat(this.getAttribute("height") || "0");
758
+ const x = parseFloat(this.getAttribute("x") || "0");
759
+ const y = parseFloat(this.getAttribute("y") || "0");
760
+ return { x, y, width: w, height: h, top: y, right: x + w, bottom: y + h, left: x, toJSON() {
761
+ return this;
762
+ } };
763
+ }
764
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
765
+ let hasChildren = false;
766
+ for (let i = 0; i < this.children.length; i++) {
767
+ const child = this.children[i];
768
+ if (!child.getBBox) continue;
769
+ const childBox = child.getBBox();
770
+ if (childBox.width === 0 && childBox.height === 0) continue;
771
+ let tx = 0, ty = 0;
772
+ const transform = child.getAttribute("transform");
773
+ if (transform) {
774
+ const match = transform.match(/translate\(([-0-9.]+)(?:[,\s]+([-0-9.]+))?\)/);
775
+ if (match) {
776
+ tx = parseFloat(match[1]);
777
+ ty = match[2] ? parseFloat(match[2]) : 0;
778
+ }
779
+ }
780
+ minX = Math.min(minX, childBox.x + tx);
781
+ minY = Math.min(minY, childBox.y + ty);
782
+ maxX = Math.max(maxX, childBox.x + childBox.width + tx);
783
+ maxY = Math.max(maxY, childBox.y + childBox.height + ty);
784
+ hasChildren = true;
785
+ }
786
+ if (!hasChildren) {
787
+ return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0, toJSON() {
788
+ return this;
789
+ } };
790
+ }
791
+ return {
792
+ x: minX,
793
+ y: minY,
794
+ width: maxX - minX,
795
+ height: maxY - minY,
796
+ top: minY,
797
+ right: maxX,
798
+ bottom: maxY,
799
+ left: minX,
800
+ toJSON() {
801
+ return this;
802
+ }
803
+ };
804
+ }
805
+ });
806
+ }
807
+ if (!svgElementPrototype.getComputedTextLength) {
808
+ Object.defineProperty(svgElementPrototype, "getComputedTextLength", {
809
+ configurable: true,
810
+ writable: true,
811
+ value() {
812
+ const text = this.textContent ?? "";
813
+ return Math.max(text.length * 8, 16);
814
+ }
815
+ });
816
+ }
817
+ }
818
+ const nodeMermaidRenderer = createNodeMermaidRenderer();
819
+ const wenyanCoreInstance = await createWenyanCore({
820
+ mermaid: {
821
+ enabled: true,
822
+ renderer: nodeMermaidRenderer
823
+ }
824
+ });
825
+ const wenyanCoreInstanceWithoutMermaid = await createWenyanCore();
603
826
  async function renderWithTheme(markdownContent, options) {
604
827
  if (!markdownContent) {
605
828
  throw new Error("No content provided for rendering.");
@@ -615,35 +838,33 @@ async function renderWithTheme(markdownContent, options) {
615
838
  if (!handledCustomTheme && !theme) {
616
839
  throw new Error(`theme "${theme}" not found.`);
617
840
  }
841
+ const coreInstance = options.mermaid === false ? wenyanCoreInstanceWithoutMermaid : wenyanCoreInstance;
618
842
  const gzhContent = await renderStyledContent(markdownContent, {
619
843
  themeId: theme,
620
844
  hlThemeId: highlight,
621
845
  isMacStyle: macStyle,
622
846
  isAddFootnote: footnote,
623
847
  themeCss: handledCustomTheme
624
- });
848
+ }, coreInstance);
625
849
  return gzhContent;
626
850
  }
627
- async function renderStyledContent(content, options = {}) {
628
- const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
629
- const html = await wenyanCoreInstance.renderMarkdown(preHandlerContent.body);
851
+ async function renderStyledContent(content, options = {}, coreInstance = wenyanCoreInstance) {
852
+ const html = await coreInstance.renderMarkdown(content);
630
853
  const dom = new JSDOM(`<body><section id="wenyan">${html}</section></body>`);
631
854
  const document = dom.window.document;
632
855
  const wenyan = document.getElementById("wenyan");
633
- const result = await wenyanCoreInstance.applyStylesWithTheme(wenyan, options);
634
- return {
635
- content: result,
636
- title: preHandlerContent.title,
637
- cover: preHandlerContent.cover,
638
- description: preHandlerContent.description,
639
- author: preHandlerContent.author,
640
- source_url: preHandlerContent.source_url
641
- };
856
+ const result = await coreInstance.applyStylesWithTheme(wenyan, options);
857
+ return result;
642
858
  }
643
859
  async function prepareRenderContext(inputContent, options, getInputContent) {
644
860
  const { content, absoluteDirPath } = await getInputContent(inputContent, options.file);
645
- const gzhContent = await renderWithTheme(content, options);
646
- return { gzhContent, absoluteDirPath };
861
+ const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
862
+ if (preHandlerContent.image_list && preHandlerContent.image_list.length > 0) {
863
+ return { gzhContent: preHandlerContent, absoluteDirPath };
864
+ }
865
+ const styledContent = await renderWithTheme(preHandlerContent.content, options);
866
+ preHandlerContent.content = styledContent;
867
+ return { gzhContent: preHandlerContent, absoluteDirPath };
647
868
  }
648
869
  async function listThemes() {
649
870
  const themes = getAllGzhThemes();
@@ -705,30 +926,34 @@ async function checkCustomThemeExists(themeId) {
705
926
  const customThemes = await configStore.getThemes();
706
927
  return customThemes.some((theme) => theme.id === themeId);
707
928
  }
708
- async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isAddFootnote = true) {
709
- return await renderStyledContent(content, {
710
- themeId,
711
- hlThemeId,
712
- isMacStyle,
713
- isAddFootnote
714
- });
715
- }
716
929
  async function renderAndPublish(inputContent, options, getInputContent) {
717
930
  const { gzhContent, absoluteDirPath } = await prepareRenderContext(inputContent, options, getInputContent);
718
931
  if (!gzhContent.title) throw new Error("未能找到文章标题");
719
- const data = await publishToWechatDraft(
720
- {
721
- title: gzhContent.title,
722
- content: gzhContent.content,
723
- cover: gzhContent.cover,
724
- author: gzhContent.author,
725
- source_url: gzhContent.source_url
726
- },
727
- {
728
- appId: options.appId,
729
- relativePath: absoluteDirPath
730
- }
731
- );
932
+ let data;
933
+ if (gzhContent.image_list && gzhContent.image_list.length > 0) {
934
+ data = await publishImageTextToWechatDraft(
935
+ {
936
+ ...gzhContent,
937
+ title: gzhContent.title,
938
+ images: gzhContent.image_list
939
+ },
940
+ {
941
+ appId: options.appId,
942
+ relativePath: absoluteDirPath
943
+ }
944
+ );
945
+ } else {
946
+ data = await publishToWechatDraft(
947
+ {
948
+ ...gzhContent,
949
+ title: gzhContent.title
950
+ },
951
+ {
952
+ appId: options.appId,
953
+ relativePath: absoluteDirPath
954
+ }
955
+ );
956
+ }
732
957
  if (data.media_id) {
733
958
  return data.media_id;
734
959
  } else {
@@ -743,7 +968,11 @@ async function renderAndPublishToServer(inputContent, options, getInputContent)
743
968
  await verifyAuth(serverUrl, headers);
744
969
  const { gzhContent, absoluteDirPath } = await prepareRenderContext(inputContent, options, getInputContent);
745
970
  if (!gzhContent.title) throw new Error("未能找到文章标题");
746
- gzhContent.content = await uploadLocalImages(gzhContent.content, serverUrl, headers, absoluteDirPath);
971
+ if (gzhContent.image_list && gzhContent.image_list.length > 0) {
972
+ gzhContent.image_list = await uploadImageList(serverUrl, headers, gzhContent.image_list, absoluteDirPath);
973
+ } else {
974
+ gzhContent.content = await uploadLocalImages(gzhContent.content, serverUrl, headers, absoluteDirPath);
975
+ }
747
976
  gzhContent.cover = await uploadCover(serverUrl, headers, gzhContent.cover, absoluteDirPath);
748
977
  const mdFileId = await uploadStyledContent(gzhContent, serverUrl, headers);
749
978
  return await requestServerPublish(mdFileId, serverUrl, headers, options);
@@ -755,7 +984,6 @@ export {
755
984
  configStore,
756
985
  credentialStore,
757
986
  ensureDir,
758
- getGzhContent,
759
987
  getNormalizeFilePath,
760
988
  isAbsolutePath,
761
989
  listThemes,
@@ -763,6 +991,7 @@ export {
763
991
  md5FromFile,
764
992
  normalizePath,
765
993
  prepareRenderContext,
994
+ publishImageTextToWechatDraft,
766
995
  publishToDraft,
767
996
  publishToWechatDraft,
768
997
  readBinaryFile,
@@ -771,7 +1000,6 @@ export {
771
1000
  renderAndPublish,
772
1001
  renderAndPublishToServer,
773
1002
  renderStyledContent,
774
- renderWithTheme,
775
1003
  safeReadJson,
776
1004
  safeWriteJson,
777
1005
  wechatPublisher
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "3.0.5",
3
+ "version": "3.0.7",
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",
@@ -65,7 +65,8 @@
65
65
  "highlight.js": "11.10.0",
66
66
  "marked": "^15.0.12",
67
67
  "marked-highlight": "^2.2.1",
68
- "mathjax-full": "3.2.2"
68
+ "mathjax-full": "3.2.2",
69
+ "mermaid": "11.14.0"
69
70
  },
70
71
  "peerDependencies": {
71
72
  "form-data-encoder": "^4.1.0",