@wenyan-md/core 3.0.6 → 3.0.8
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 +456 -7
- package/dist/publish.js +60 -22
- package/dist/types/core/index.d.ts +6 -2
- package/dist/types/core/mermaid.d.ts +19 -0
- package/dist/types/core/parser/mermaidParser.d.ts +9 -0
- package/dist/types/credentialStore.d.ts +18 -5
- package/dist/types/node/mermaidRenderer.d.ts +2 -0
- package/dist/types/node/render.d.ts +2 -2
- package/dist/types/node/types.d.ts +3 -11
- package/dist/types/publish.d.ts +1 -0
- package/dist/types/tokenStore.d.ts +3 -2
- package/dist/wrapper.js +169 -6
- package/package.json +3 -2
package/dist/core.js
CHANGED
|
@@ -434,6 +434,40 @@ function createMarkedClient() {
|
|
|
434
434
|
md.use(highlightExtension);
|
|
435
435
|
md.use({
|
|
436
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
|
+
const text = match[2];
|
|
450
|
+
const inner = match[3].trim();
|
|
451
|
+
let href = "";
|
|
452
|
+
let title = "";
|
|
453
|
+
const titleMatch = inner.match(/(.*?)\s+["']([^"']*)["']$/);
|
|
454
|
+
if (titleMatch) {
|
|
455
|
+
href = titleMatch[1].trim();
|
|
456
|
+
title = titleMatch[2];
|
|
457
|
+
} else {
|
|
458
|
+
href = inner;
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
type: isImage ? "image" : "link",
|
|
462
|
+
raw: match[0],
|
|
463
|
+
text,
|
|
464
|
+
href,
|
|
465
|
+
title,
|
|
466
|
+
tokens: this.lexer.inlineTokens(text)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
// 自定义图片语法扩展 ![](){...}
|
|
437
471
|
{
|
|
438
472
|
name: "attributeImage",
|
|
439
473
|
level: "inline",
|
|
@@ -462,7 +496,8 @@ function createMarkedClient() {
|
|
|
462
496
|
renderer(token) {
|
|
463
497
|
const attrs = stringToMap(token.attrs);
|
|
464
498
|
const styleStr = Array.from(attrs).map(([k, v]) => /^\d+$/.test(v) ? `${k}:${v}px` : `${k}:${v}`).join("; ");
|
|
465
|
-
|
|
499
|
+
const href = normalizeHref(token.href);
|
|
500
|
+
return `<img src="${href}" alt="${token.text || ""}" title="${token.text || ""}" style="${styleStr}">`;
|
|
466
501
|
}
|
|
467
502
|
}
|
|
468
503
|
]
|
|
@@ -490,7 +525,12 @@ function createMarkedClient() {
|
|
|
490
525
|
},
|
|
491
526
|
// 重写普通图片 (处理标准 Markdown 图片)
|
|
492
527
|
image(token) {
|
|
493
|
-
|
|
528
|
+
const href = normalizeHref(token.href);
|
|
529
|
+
return `<img src="${href}" alt="${token.text || ""}" title="${token.title || token.text || ""}">`;
|
|
530
|
+
},
|
|
531
|
+
link(token) {
|
|
532
|
+
const href = normalizeHref(token.href);
|
|
533
|
+
return `<a href="${href}">${this.parser.parseInline(token.tokens)}</a>`;
|
|
494
534
|
}
|
|
495
535
|
}
|
|
496
536
|
});
|
|
@@ -503,10 +543,21 @@ function createMarkedClient() {
|
|
|
503
543
|
*/
|
|
504
544
|
async parse(markdown) {
|
|
505
545
|
await configure();
|
|
506
|
-
return md.parse(markdown);
|
|
546
|
+
return await md.parse(markdown);
|
|
507
547
|
}
|
|
508
548
|
};
|
|
509
549
|
}
|
|
550
|
+
function normalizeHref(href) {
|
|
551
|
+
href = href.trim();
|
|
552
|
+
if (href.startsWith("<") && href.endsWith(">")) {
|
|
553
|
+
href = href.slice(1, -1);
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
return encodeURI(href);
|
|
557
|
+
} catch {
|
|
558
|
+
return href;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
510
561
|
let htmlHandlerRegistered = false;
|
|
511
562
|
function createMathJaxParser(options = {}) {
|
|
512
563
|
const adaptor = liteAdaptor();
|
|
@@ -565,6 +616,36 @@ function createMathJaxParser(options = {}) {
|
|
|
565
616
|
}
|
|
566
617
|
};
|
|
567
618
|
}
|
|
619
|
+
function createMermaidParser(options) {
|
|
620
|
+
const resolvedOptions = resolveMermaidOptions(options);
|
|
621
|
+
return {
|
|
622
|
+
async parser(html) {
|
|
623
|
+
if (!resolvedOptions.enabled || !containsMermaidCodeBlock(html)) {
|
|
624
|
+
return html;
|
|
625
|
+
}
|
|
626
|
+
if (!resolvedOptions.renderer) {
|
|
627
|
+
throw new Error("Mermaid 渲染已启用,但未配置 renderer");
|
|
628
|
+
}
|
|
629
|
+
return await resolvedOptions.renderer.renderHtml(html);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function resolveMermaidOptions(options) {
|
|
634
|
+
if (options === void 0) {
|
|
635
|
+
return { enabled: false };
|
|
636
|
+
}
|
|
637
|
+
if (typeof options === "boolean") {
|
|
638
|
+
return { enabled: options };
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
enabled: options.enabled ?? true,
|
|
642
|
+
renderer: options.renderer
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function containsMermaidCodeBlock(html) {
|
|
646
|
+
return html.includes("language-mermaid");
|
|
647
|
+
}
|
|
648
|
+
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
568
649
|
function wechatPostRender(element) {
|
|
569
650
|
const mathElements = element.querySelectorAll("mjx-container");
|
|
570
651
|
mathElements.forEach((mathContainer) => {
|
|
@@ -583,6 +664,13 @@ function wechatPostRender(element) {
|
|
|
583
664
|
}
|
|
584
665
|
}
|
|
585
666
|
});
|
|
667
|
+
const mermaidSvgs = element.querySelectorAll('[data-wenyan-diagram="mermaid"] svg');
|
|
668
|
+
mermaidSvgs.forEach((svg) => {
|
|
669
|
+
svg.style.maxWidth = "100%";
|
|
670
|
+
svg.style.height = "auto";
|
|
671
|
+
inlineMermaidSvgStyles(svg);
|
|
672
|
+
flattenMermaidMarkers(svg);
|
|
673
|
+
});
|
|
586
674
|
const codeElements = element.querySelectorAll("pre code");
|
|
587
675
|
codeElements.forEach((codeEl) => {
|
|
588
676
|
codeEl.innerHTML = codeEl.innerHTML.replace(/\n/g, "<br>").replace(/(>[^<]+)|(^[^<]+)/g, (str) => str.replace(/\s/g, " "));
|
|
@@ -599,6 +687,268 @@ function wechatPostRender(element) {
|
|
|
599
687
|
element.style.color = "rgb(0, 0, 0)";
|
|
600
688
|
element.style.caretColor = "rgb(0, 0, 0)";
|
|
601
689
|
}
|
|
690
|
+
function inlineMermaidSvgStyles(svg) {
|
|
691
|
+
const styleElements = Array.from(svg.querySelectorAll("style"));
|
|
692
|
+
styleElements.forEach((styleElement) => {
|
|
693
|
+
applyInlineStylesFromCss(svg, styleElement.textContent ?? "");
|
|
694
|
+
styleElement.remove();
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
function flattenMermaidMarkers(svg) {
|
|
698
|
+
const convertedMarkerIds = /* @__PURE__ */ new Set();
|
|
699
|
+
const markedElements = Array.from(svg.querySelectorAll("[marker-start], [marker-end]"));
|
|
700
|
+
markedElements.forEach((markedElement) => {
|
|
701
|
+
convertMarkerReference(svg, markedElement, "start", convertedMarkerIds);
|
|
702
|
+
convertMarkerReference(svg, markedElement, "end", convertedMarkerIds);
|
|
703
|
+
});
|
|
704
|
+
convertedMarkerIds.forEach((markerId) => {
|
|
705
|
+
findMarkerById(svg, markerId)?.remove();
|
|
706
|
+
});
|
|
707
|
+
svg.querySelectorAll("defs").forEach((defs) => {
|
|
708
|
+
if (!defs.children.length) {
|
|
709
|
+
defs.remove();
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
function convertMarkerReference(svg, sourceElement, position, convertedMarkerIds) {
|
|
714
|
+
const markerAttribute = `marker-${position}`;
|
|
715
|
+
const markerValue = sourceElement.getAttribute(markerAttribute);
|
|
716
|
+
if (!markerValue) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const markerId = extractMarkerId(markerValue);
|
|
720
|
+
if (!markerId) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const marker = findMarkerById(svg, markerId);
|
|
724
|
+
if (!marker) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const flattenedMarker = createFlattenedMarker(svg, sourceElement, marker, position);
|
|
728
|
+
if (!flattenedMarker) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
sourceElement.parentNode?.appendChild(flattenedMarker);
|
|
732
|
+
sourceElement.removeAttribute(markerAttribute);
|
|
733
|
+
convertedMarkerIds.add(markerId);
|
|
734
|
+
}
|
|
735
|
+
function createFlattenedMarker(svg, sourceElement, marker, position) {
|
|
736
|
+
const terminalPoints = getTerminalPoints(sourceElement, position);
|
|
737
|
+
if (!terminalPoints) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
const ownerDocument = svg.ownerDocument;
|
|
741
|
+
const markerGroup = ownerDocument.createElementNS(SVG_NAMESPACE, "g");
|
|
742
|
+
const markerStyle = marker.getAttribute("style");
|
|
743
|
+
const angle = getMarkerAngleDegrees(position, terminalPoints.anchor, terminalPoints.reference, marker);
|
|
744
|
+
markerGroup.setAttribute("data-wenyan-marker", position);
|
|
745
|
+
markerGroup.setAttribute("transform", buildMarkerTransform(marker, terminalPoints.anchor, angle, position));
|
|
746
|
+
if (markerStyle) {
|
|
747
|
+
markerGroup.setAttribute("style", markerStyle);
|
|
748
|
+
}
|
|
749
|
+
Array.from(marker.children).forEach((child) => {
|
|
750
|
+
markerGroup.appendChild(child.cloneNode(true));
|
|
751
|
+
});
|
|
752
|
+
return markerGroup;
|
|
753
|
+
}
|
|
754
|
+
function getTerminalPoints(sourceElement, position) {
|
|
755
|
+
const points = getEdgePoints(sourceElement);
|
|
756
|
+
if (points.length < 2) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
if (position === "end") {
|
|
760
|
+
const anchor2 = points.at(-1);
|
|
761
|
+
if (!anchor2) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
const reference2 = findDistinctPoint(points, points.length - 2, -1, anchor2);
|
|
765
|
+
return reference2 ? { anchor: anchor2, reference: reference2 } : null;
|
|
766
|
+
}
|
|
767
|
+
const anchor = points[0];
|
|
768
|
+
if (!anchor) {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
const reference = findDistinctPoint(points, 1, 1, anchor);
|
|
772
|
+
return reference ? { anchor, reference } : null;
|
|
773
|
+
}
|
|
774
|
+
function getEdgePoints(sourceElement) {
|
|
775
|
+
const encodedPoints = sourceElement.getAttribute("data-points");
|
|
776
|
+
if (encodedPoints) {
|
|
777
|
+
const decodedPoints = decodeMermaidPoints(sourceElement, encodedPoints);
|
|
778
|
+
if (decodedPoints.length >= 2) {
|
|
779
|
+
return decodedPoints;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const polyPoints = sourceElement.getAttribute("points");
|
|
783
|
+
if (polyPoints) {
|
|
784
|
+
return parseCoordinatePairs(polyPoints);
|
|
785
|
+
}
|
|
786
|
+
const pathData = sourceElement.getAttribute("d");
|
|
787
|
+
if (pathData) {
|
|
788
|
+
return parseCoordinatePairs(pathData);
|
|
789
|
+
}
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
function decodeMermaidPoints(sourceElement, encodedPoints) {
|
|
793
|
+
const defaultView = sourceElement.ownerDocument.defaultView;
|
|
794
|
+
try {
|
|
795
|
+
const decoded = defaultView?.atob?.(encodedPoints) ?? (typeof atob === "function" ? atob(encodedPoints) : "");
|
|
796
|
+
const parsed = JSON.parse(decoded);
|
|
797
|
+
return parsed.filter((point) => typeof point.x === "number" && typeof point.y === "number").map((point) => ({
|
|
798
|
+
x: point.x,
|
|
799
|
+
y: point.y
|
|
800
|
+
}));
|
|
801
|
+
} catch {
|
|
802
|
+
return [];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function parseCoordinatePairs(value) {
|
|
806
|
+
const matches = Array.from(value.matchAll(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi), (match) => Number(match[0]));
|
|
807
|
+
const points = [];
|
|
808
|
+
for (let index = 0; index + 1 < matches.length; index += 2) {
|
|
809
|
+
points.push({
|
|
810
|
+
x: matches[index],
|
|
811
|
+
y: matches[index + 1]
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
return points;
|
|
815
|
+
}
|
|
816
|
+
function findDistinctPoint(points, startIndex, step, anchor) {
|
|
817
|
+
for (let index = startIndex; index >= 0 && index < points.length; index += step) {
|
|
818
|
+
const point = points[index];
|
|
819
|
+
if (!point) {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
if (point.x !== anchor.x || point.y !== anchor.y) {
|
|
823
|
+
return point;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
function getMarkerAngleDegrees(position, anchor, reference, marker) {
|
|
829
|
+
const baseAngle = position === "end" ? Math.atan2(anchor.y - reference.y, anchor.x - reference.x) : Math.atan2(reference.y - anchor.y, reference.x - anchor.x);
|
|
830
|
+
const orient = marker.getAttribute("orient")?.trim();
|
|
831
|
+
let angle = baseAngle * 180 / Math.PI;
|
|
832
|
+
if (orient === "auto-start-reverse" && position === "start") {
|
|
833
|
+
angle += 180;
|
|
834
|
+
} else if (orient && orient !== "auto") {
|
|
835
|
+
const offset = Number.parseFloat(orient);
|
|
836
|
+
if (Number.isFinite(offset)) {
|
|
837
|
+
angle += offset;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return angle;
|
|
841
|
+
}
|
|
842
|
+
function buildMarkerTransform(marker, anchor, angle, position) {
|
|
843
|
+
const viewBox = parseViewBox(marker.getAttribute("viewBox"));
|
|
844
|
+
const markerWidth = parseNumericAttribute(marker.getAttribute("markerWidth"), viewBox?.width ?? 1);
|
|
845
|
+
const markerHeight = parseNumericAttribute(marker.getAttribute("markerHeight"), viewBox?.height ?? 1);
|
|
846
|
+
const viewBoxWidth = viewBox?.width ?? markerWidth;
|
|
847
|
+
const viewBoxHeight = viewBox?.height ?? markerHeight;
|
|
848
|
+
const scaleX = viewBoxWidth === 0 ? 1 : markerWidth / viewBoxWidth;
|
|
849
|
+
const scaleY = viewBoxHeight === 0 ? 1 : markerHeight / viewBoxHeight;
|
|
850
|
+
const offsetX = parseNumericAttribute(marker.getAttribute("refX")) + (viewBox?.minX ?? 0);
|
|
851
|
+
const offsetY = parseNumericAttribute(marker.getAttribute("refY")) + (viewBox?.minY ?? 0);
|
|
852
|
+
const adjustedAnchor = adjustMarkerAnchor(anchor, angle, scaleX, viewBox, offsetX, position);
|
|
853
|
+
return [
|
|
854
|
+
`translate(${adjustedAnchor.x} ${adjustedAnchor.y})`,
|
|
855
|
+
`rotate(${angle})`,
|
|
856
|
+
`scale(${scaleX} ${scaleY})`,
|
|
857
|
+
`translate(${-offsetX} ${-offsetY})`
|
|
858
|
+
].join(" ");
|
|
859
|
+
}
|
|
860
|
+
function adjustMarkerAnchor(anchor, angle, scaleX, viewBox, refX, position) {
|
|
861
|
+
const minX = viewBox?.minX ?? 0;
|
|
862
|
+
const maxX = viewBox ? viewBox.minX + viewBox.width : refX;
|
|
863
|
+
const overlap = position === "start" ? Math.max(0, refX - minX) : Math.max(0, maxX - refX);
|
|
864
|
+
if (overlap === 0) {
|
|
865
|
+
return anchor;
|
|
866
|
+
}
|
|
867
|
+
const distance = overlap * scaleX;
|
|
868
|
+
const radians = angle * Math.PI / 180;
|
|
869
|
+
const direction = position === "start" ? 1 : -1;
|
|
870
|
+
return {
|
|
871
|
+
x: anchor.x + Math.cos(radians) * distance * direction,
|
|
872
|
+
y: anchor.y + Math.sin(radians) * distance * direction
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function parseViewBox(viewBoxValue) {
|
|
876
|
+
if (!viewBoxValue) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
const values = viewBoxValue.trim().split(/[\s,]+/).map((value) => Number.parseFloat(value));
|
|
880
|
+
if (values.length !== 4 || values.some((value) => !Number.isFinite(value))) {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
minX: values[0],
|
|
885
|
+
minY: values[1],
|
|
886
|
+
width: values[2],
|
|
887
|
+
height: values[3]
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function parseNumericAttribute(value, fallback = 0) {
|
|
891
|
+
if (!value) {
|
|
892
|
+
return fallback;
|
|
893
|
+
}
|
|
894
|
+
const parsed = Number.parseFloat(value);
|
|
895
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
896
|
+
}
|
|
897
|
+
function extractMarkerId(markerValue) {
|
|
898
|
+
const match = markerValue.match(/url\(#([^)]+)\)/);
|
|
899
|
+
return match?.[1] ?? null;
|
|
900
|
+
}
|
|
901
|
+
function findMarkerById(svg, markerId) {
|
|
902
|
+
return Array.from(svg.querySelectorAll("marker")).find((marker) => marker.getAttribute("id") === markerId);
|
|
903
|
+
}
|
|
904
|
+
function applyInlineStylesFromCss(svg, cssText) {
|
|
905
|
+
const ast = csstree.parse(cssText, parseOptions);
|
|
906
|
+
csstree.walk(ast, {
|
|
907
|
+
visit: "Rule",
|
|
908
|
+
enter(node) {
|
|
909
|
+
if (node.prelude.type !== "SelectorList") return;
|
|
910
|
+
const declarations = node.block.children.toArray().filter((decl) => decl.type === "Declaration");
|
|
911
|
+
if (declarations.length === 0) return;
|
|
912
|
+
node.prelude.children.forEach((selectorNode) => {
|
|
913
|
+
const selector = csstree.generate(selectorNode).trim();
|
|
914
|
+
if (!selector || selector.includes("::")) return;
|
|
915
|
+
const targets = resolveInlineTargets(svg, selector);
|
|
916
|
+
targets.forEach((target) => {
|
|
917
|
+
declarations.forEach((decl) => {
|
|
918
|
+
const value = csstree.generate(decl.value);
|
|
919
|
+
const priority = decl.important ? "important" : "";
|
|
920
|
+
target.style.setProperty(decl.property, value, priority);
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
function resolveInlineTargets(svg, selector) {
|
|
928
|
+
if (selector === ":root") {
|
|
929
|
+
return [svg];
|
|
930
|
+
}
|
|
931
|
+
if (selector.includes(":")) {
|
|
932
|
+
return [];
|
|
933
|
+
}
|
|
934
|
+
const targets = /* @__PURE__ */ new Set();
|
|
935
|
+
if (svg.matches(selector)) {
|
|
936
|
+
targets.add(svg);
|
|
937
|
+
}
|
|
938
|
+
svg.querySelectorAll(selector).forEach((node) => {
|
|
939
|
+
if (isInlineStyleTarget(node)) {
|
|
940
|
+
targets.add(node);
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
return Array.from(targets);
|
|
944
|
+
}
|
|
945
|
+
function isInlineStyleTarget(node) {
|
|
946
|
+
const defaultView = node.ownerDocument.defaultView;
|
|
947
|
+
if (!defaultView) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
return node instanceof defaultView.SVGElement || node instanceof defaultView.HTMLElement;
|
|
951
|
+
}
|
|
602
952
|
const themeModifier = createCssModifier({});
|
|
603
953
|
function renderTheme(wenyanElement, themeCss) {
|
|
604
954
|
const modifiedCss = themeModifier(themeCss);
|
|
@@ -1169,10 +1519,105 @@ function getContentForToutiao(wenyanElement) {
|
|
|
1169
1519
|
});
|
|
1170
1520
|
return wenyanElement.outerHTML;
|
|
1171
1521
|
}
|
|
1522
|
+
const DEFAULT_MERMAID_CONFIG = {
|
|
1523
|
+
htmlLabels: false,
|
|
1524
|
+
flowchart: {
|
|
1525
|
+
htmlLabels: false
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
async function replaceMermaidCodeBlocks(root, renderDiagram) {
|
|
1529
|
+
const codeBlocks = Array.from(root.querySelectorAll("pre > code.language-mermaid"));
|
|
1530
|
+
if (codeBlocks.length === 0) {
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
const ownerDocument = getOwnerDocument(root);
|
|
1534
|
+
for (const [_, codeBlock] of codeBlocks.entries()) {
|
|
1535
|
+
const pre = codeBlock.parentElement;
|
|
1536
|
+
const parent = pre?.parentElement;
|
|
1537
|
+
if (!pre || !parent) {
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const code = codeBlock.textContent ?? "";
|
|
1541
|
+
const svg = await renderDiagram({
|
|
1542
|
+
id: `wenyan-mermaid-${crypto.randomUUID()}`,
|
|
1543
|
+
code
|
|
1544
|
+
});
|
|
1545
|
+
const figure = ownerDocument.createElement("figure");
|
|
1546
|
+
figure.setAttribute("data-wenyan-diagram", "mermaid");
|
|
1547
|
+
figure.innerHTML = svg.trim();
|
|
1548
|
+
pre.replaceWith(figure);
|
|
1549
|
+
}
|
|
1550
|
+
return true;
|
|
1551
|
+
}
|
|
1552
|
+
function createBrowserMermaidRenderer(options = {}) {
|
|
1553
|
+
let mermaidModulePromise = null;
|
|
1554
|
+
return {
|
|
1555
|
+
async renderHtml(html) {
|
|
1556
|
+
if (typeof DOMParser === "undefined") {
|
|
1557
|
+
throw new Error("当前环境不支持浏览器 Mermaid 渲染");
|
|
1558
|
+
}
|
|
1559
|
+
const parser = new DOMParser();
|
|
1560
|
+
const document = parser.parseFromString(`<body>${html}</body>`, "text/html");
|
|
1561
|
+
const root = document.body;
|
|
1562
|
+
const mermaid = await getMermaidModule();
|
|
1563
|
+
try {
|
|
1564
|
+
await replaceMermaidCodeBlocks(root, async ({ id, code }) => {
|
|
1565
|
+
const { svg } = await mermaid.render(id, code);
|
|
1566
|
+
return svg;
|
|
1567
|
+
});
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
throw createMermaidRenderError(error);
|
|
1570
|
+
}
|
|
1571
|
+
return root.innerHTML;
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
async function getMermaidModule() {
|
|
1575
|
+
if (!mermaidModulePromise) {
|
|
1576
|
+
mermaidModulePromise = import("mermaid");
|
|
1577
|
+
}
|
|
1578
|
+
const module = await mermaidModulePromise;
|
|
1579
|
+
const mermaid = module.default;
|
|
1580
|
+
mermaid.initialize(createMermaidConfig(options.mermaidConfig));
|
|
1581
|
+
return mermaid;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function createMermaidRenderError(error) {
|
|
1585
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1586
|
+
return new Error(`Mermaid 图表渲染失败: ${message}`);
|
|
1587
|
+
}
|
|
1588
|
+
function createMermaidConfig(overrides = {}) {
|
|
1589
|
+
const flowchartOverrides = getRecord(overrides.flowchart);
|
|
1590
|
+
return {
|
|
1591
|
+
...DEFAULT_MERMAID_CONFIG,
|
|
1592
|
+
startOnLoad: false,
|
|
1593
|
+
securityLevel: "strict",
|
|
1594
|
+
...overrides,
|
|
1595
|
+
flowchart: {
|
|
1596
|
+
...getRecord(DEFAULT_MERMAID_CONFIG.flowchart),
|
|
1597
|
+
...flowchartOverrides
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function getOwnerDocument(root) {
|
|
1602
|
+
if ("createElement" in root) {
|
|
1603
|
+
return root;
|
|
1604
|
+
}
|
|
1605
|
+
if ("ownerDocument" in root && root.ownerDocument) {
|
|
1606
|
+
return root.ownerDocument;
|
|
1607
|
+
}
|
|
1608
|
+
throw new Error("无法获取 Mermaid 渲染所需的 Document");
|
|
1609
|
+
}
|
|
1610
|
+
function getRecord(value) {
|
|
1611
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1612
|
+
return value;
|
|
1613
|
+
}
|
|
1614
|
+
return {};
|
|
1615
|
+
}
|
|
1172
1616
|
async function createWenyanCore(options = {}) {
|
|
1173
|
-
const { isConvertMathJax = true, isWechat = true } = options;
|
|
1617
|
+
const { isConvertMathJax = true, isWechat = true, mermaid } = options;
|
|
1174
1618
|
const markedClient = createMarkedClient();
|
|
1175
1619
|
const mathJaxParser = createMathJaxParser();
|
|
1620
|
+
const mermaidParser = createMermaidParser(mermaid);
|
|
1176
1621
|
registerAllBuiltInThemes();
|
|
1177
1622
|
registerBuiltInHlThemes();
|
|
1178
1623
|
registerBuiltInMacStyle();
|
|
@@ -1181,11 +1626,11 @@ async function createWenyanCore(options = {}) {
|
|
|
1181
1626
|
return await handleFrontMatter(markdown);
|
|
1182
1627
|
},
|
|
1183
1628
|
async renderMarkdown(markdown) {
|
|
1184
|
-
|
|
1629
|
+
let html = await markedClient.parse(markdown);
|
|
1185
1630
|
if (isConvertMathJax) {
|
|
1186
|
-
|
|
1631
|
+
html = mathJaxParser.parser(html);
|
|
1187
1632
|
}
|
|
1188
|
-
return html;
|
|
1633
|
+
return await mermaidParser.parser(html);
|
|
1189
1634
|
},
|
|
1190
1635
|
async applyStylesWithTheme(wenyanElement, options2 = {}) {
|
|
1191
1636
|
const {
|
|
@@ -1288,7 +1733,10 @@ const DEFAULT_CSS_UPDATES = {
|
|
|
1288
1733
|
};
|
|
1289
1734
|
export {
|
|
1290
1735
|
addFootnotes,
|
|
1736
|
+
createBrowserMermaidRenderer,
|
|
1291
1737
|
createCssModifier,
|
|
1738
|
+
createMermaidConfig,
|
|
1739
|
+
createMermaidRenderError,
|
|
1292
1740
|
createWenyanCore,
|
|
1293
1741
|
getAllGzhThemes,
|
|
1294
1742
|
getAllHlThemes,
|
|
@@ -1305,6 +1753,7 @@ export {
|
|
|
1305
1753
|
registerHlTheme,
|
|
1306
1754
|
registerMacStyle,
|
|
1307
1755
|
registerTheme,
|
|
1756
|
+
replaceMermaidCodeBlocks,
|
|
1308
1757
|
sansSerif,
|
|
1309
1758
|
serif
|
|
1310
1759
|
};
|
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
|
-
|
|
149
|
+
/**
|
|
150
|
+
* 获取微信凭据 (通过 appId 或 alias)
|
|
151
|
+
*/
|
|
152
|
+
async getWechatCredential(appIdOrAlias) {
|
|
137
153
|
await this.initPromise;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
170
|
+
/**
|
|
171
|
+
* 保存或更新微信凭据
|
|
172
|
+
*/
|
|
173
|
+
async saveWechatCredential(appId, appSecret, alias) {
|
|
148
174
|
await this.initPromise;
|
|
149
175
|
this.credential.wechat ??= {};
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
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;
|
|
@@ -12,7 +14,7 @@ export interface ApplyStylesOptions {
|
|
|
12
14
|
isAddFootnote?: boolean;
|
|
13
15
|
}
|
|
14
16
|
export declare function createWenyanCore(options?: WenyanOptions): Promise<{
|
|
15
|
-
handleFrontMatter(markdown: string): Promise<
|
|
17
|
+
handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
|
|
16
18
|
renderMarkdown(markdown: string): Promise<string>;
|
|
17
19
|
applyStylesWithTheme(wenyanElement: HTMLElement, options?: ApplyStylesOptions): Promise<string>;
|
|
18
20
|
applyStylesWithResolvedCss(wenyanElement: HTMLElement, options: {
|
|
@@ -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>;
|
|
@@ -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;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
export interface WechatCredentialItem {
|
|
2
|
+
appSecret: string;
|
|
3
|
+
alias?: string;
|
|
4
|
+
}
|
|
1
5
|
export interface WenyanCredential {
|
|
2
|
-
wechat?: Record<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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApplyStylesOptions } from "../core/index.js";
|
|
1
|
+
import { ApplyStylesOptions, WenyanCoreInstance } from "../core/index.js";
|
|
2
2
|
import { GetInputContentFn, RenderContext, RenderOptions } from "./types.js";
|
|
3
|
-
export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<string>;
|
|
3
|
+
export declare function renderStyledContent(content: string, options?: ApplyStylesOptions, coreInstance?: WenyanCoreInstance): Promise<string>;
|
|
4
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,17 +20,7 @@ export interface RenderContext {
|
|
|
18
20
|
gzhContent: StyledContent;
|
|
19
21
|
absoluteDirPath: string | undefined;
|
|
20
22
|
}
|
|
21
|
-
export
|
|
22
|
-
content: string;
|
|
23
|
-
title?: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
cover?: string;
|
|
26
|
-
author?: string;
|
|
27
|
-
source_url?: string;
|
|
28
|
-
need_open_comment?: boolean;
|
|
29
|
-
only_fans_can_comment?: boolean;
|
|
30
|
-
image_list?: string[];
|
|
31
|
-
}
|
|
23
|
+
export type StyledContent = FrontMatterResult;
|
|
32
24
|
export type GetInputContentFn = (inputContent?: string, filePath?: string) => Promise<{
|
|
33
25
|
content: string;
|
|
34
26
|
absoluteDirPath?: string;
|
package/dist/types/publish.d.ts
CHANGED
|
@@ -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
|
@@ -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");
|
|
@@ -660,7 +660,169 @@ class ConfigStore {
|
|
|
660
660
|
}
|
|
661
661
|
}
|
|
662
662
|
const configStore = new ConfigStore();
|
|
663
|
-
|
|
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();
|
|
664
826
|
async function renderWithTheme(markdownContent, options) {
|
|
665
827
|
if (!markdownContent) {
|
|
666
828
|
throw new Error("No content provided for rendering.");
|
|
@@ -676,21 +838,22 @@ async function renderWithTheme(markdownContent, options) {
|
|
|
676
838
|
if (!handledCustomTheme && !theme) {
|
|
677
839
|
throw new Error(`theme "${theme}" not found.`);
|
|
678
840
|
}
|
|
841
|
+
const coreInstance = options.mermaid === false ? wenyanCoreInstanceWithoutMermaid : wenyanCoreInstance;
|
|
679
842
|
const gzhContent = await renderStyledContent(markdownContent, {
|
|
680
843
|
themeId: theme,
|
|
681
844
|
hlThemeId: highlight,
|
|
682
845
|
isMacStyle: macStyle,
|
|
683
846
|
isAddFootnote: footnote,
|
|
684
847
|
themeCss: handledCustomTheme
|
|
685
|
-
});
|
|
848
|
+
}, coreInstance);
|
|
686
849
|
return gzhContent;
|
|
687
850
|
}
|
|
688
|
-
async function renderStyledContent(content, options = {}) {
|
|
689
|
-
const html = await
|
|
851
|
+
async function renderStyledContent(content, options = {}, coreInstance = wenyanCoreInstance) {
|
|
852
|
+
const html = await coreInstance.renderMarkdown(content);
|
|
690
853
|
const dom = new JSDOM(`<body><section id="wenyan">${html}</section></body>`);
|
|
691
854
|
const document = dom.window.document;
|
|
692
855
|
const wenyan = document.getElementById("wenyan");
|
|
693
|
-
const result = await
|
|
856
|
+
const result = await coreInstance.applyStylesWithTheme(wenyan, options);
|
|
694
857
|
return result;
|
|
695
858
|
}
|
|
696
859
|
async function prepareRenderContext(inputContent, options, getInputContent) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenyan-md/core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.8",
|
|
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",
|