Skip to content

Commit 4ce9abb

Browse files
committed
good job
1 parent bf9bfa9 commit 4ce9abb

File tree

7 files changed

+182
-177
lines changed

7 files changed

+182
-177
lines changed

docusaurus.config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,18 @@ const config: Config = {
9191

9292
onBrokenLinks: "throw",
9393
onBrokenMarkdownLinks: "warn",
94-
94+
stylesheets: [
95+
{
96+
href: "https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css",
97+
type: "text/css",
98+
crossOrigin: "anonymous"
99+
},
100+
{
101+
href: "https://cdn.jsdelivr.net/npm/@callmebill/lxgw-wenkai-web@latest/style.css",
102+
type: "text/css",
103+
crossorigin: "anonymous"
104+
}
105+
],
95106
// Even if you don't use internationalization, you can use this field to set
96107
// useful metadata like html lang. For example, if your site is Chinese, you
97108
// may want to replace "en" with "zh-Hans".

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
"canvas": "^3.1.0",
4545
"canvg": "^4.0.2",
4646
"clsx": "^2.0.0",
47+
"dom-to-image-more": "^3.7.1",
48+
"dom-to-image": "^2.6.0",
4749
"echarts": "^5.5.1",
4850
"file-saver": "^2.0.5",
4951
"heliannuuthus-docusaurus-authors": "file:./plugins/docusaurus-authors",
@@ -88,7 +90,10 @@
8890
"@docusaurus/tsconfig": "3.8.1",
8991
"@docusaurus/types": "3.8.1",
9092
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
93+
"@types/dom-to-image": "^2.6.7",
9194
"@types/file-saver": "^2.0.7",
95+
"@types/react": "^19.0.0",
96+
"@types/react-dom": "^19.0.0",
9297
"prettier": "^3.5.3",
9398
"remark-code-import": "^1.2.0",
9499
"typescript": "~5.7.3"

pnpm-lock.yaml

Lines changed: 117 additions & 79 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/markdown/svgviewer/Viewer.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import "animate.css";
12
import { ConfigProvider, Modal, Tabs, TabsProps } from "antd";
23
import { createStyles } from "antd-style";
3-
import React, { useState } from "react";
4+
import { useState } from "react";
45

56
import ViewerHeader from "./ViewerHeader";
67

@@ -29,28 +30,29 @@ const useStyles = createStyles(({ css, token }) => ({
2930
}));
3031

3132
const Displayer: React.FC<{
32-
ref: React.RefObject<HTMLDivElement>;
33-
children: React.ReactNode;
3433
style?: React.CSSProperties;
3534
className?: string;
36-
}> = ({ children, className, style, ref }) => {
35+
children: React.ReactNode;
36+
}> = ({ children, className, style }) => {
3737
const { styles } = useStyles();
3838
return (
39-
<div
40-
className={`${styles.svgViewerDisplayer} ${className}`}
41-
style={style}
42-
ref={ref}
43-
>
39+
<div className={`${styles.svgViewerDisplayer} ${className}`} style={style}>
4440
{children}
4541
</div>
4642
);
4743
};
4844

49-
const ViewerBody: React.FC<{
45+
type ViewerBodyProps = {
5046
svg: React.ReactNode;
51-
text: React.ReactNode;
47+
text: string;
5248
setIsFullscreen: (isFullscreen: boolean) => void;
53-
}> = ({ svg, text, setIsFullscreen }) => {
49+
};
50+
51+
const ViewerBody: React.FC<ViewerBodyProps> = ({
52+
svg,
53+
text,
54+
setIsFullscreen
55+
}) => {
5456
const { styles } = useStyles();
5557
const [activeTab, setActiveTab] = useState<string>("svg");
5658
const [codeShow, setCodeShow] = useState<boolean>(false);
@@ -102,7 +104,13 @@ const ViewerBody: React.FC<{
102104
);
103105
};
104106

105-
export default ({ svg, text }) => {
107+
export default ({
108+
svg,
109+
text
110+
}: {
111+
svg: React.ReactNode;
112+
text: string;
113+
}) => {
106114
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
107115
return (
108116
<ConfigProvider

src/components/markdown/svgviewer/ViewerHeader.tsx

Lines changed: 25 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ import {
1616
message
1717
} from "antd";
1818
import { createStyles } from "antd-style";
19+
import domtoimage from "dom-to-image-more";
1920
import { saveAs } from "file-saver";
20-
import { useCallback, useMemo, useState } from "react";
21+
import { RefObject, useCallback, useMemo, useState } from "react";
22+
23+
const globalOptions = {
24+
disableEmbedFonts: true
25+
};
2126

2227
type ViewerHeaderProps = {
23-
svg: React.ReactElement;
28+
svg: React.ReactNode;
2429
text: string;
2530
download?: boolean;
2631
zoomIn?: boolean;
@@ -71,86 +76,36 @@ export default ({
7176
const [scale, setScale] = useState<number>(1);
7277
const { styles } = useStyles();
7378

74-
const getCurrentSvgElement = useCallback((): SVGElement | null => {
75-
console.log(svgEl);
76-
if (!svgEl) return null;
77-
return svgEl as unknown as SVGSVGElement;
78-
}, []);
79-
80-
const serializeSvg = useCallback((svgEl: SVGSVGElement): string => {
81-
const serializer = new XMLSerializer();
82-
let svgString = serializer.serializeToString(svgEl);
83-
// Ensure xmlns is present
84-
if (!svgString.includes('xmlns="http://www.w3.org/2000/svg"')) {
85-
svgString = svgString.replace(
86-
"<svg",
87-
'<svg xmlns="http://www.w3.org/2000/svg"'
88-
);
79+
const getCurrentSvgElement = useCallback((): SVGSVGElement | null => {
80+
const svgEle = svg as React.ReactElement<React.SVGProps<SVGSVGElement>>;
81+
if (
82+
!svgEle ||
83+
!svgEle.props ||
84+
!svgEle.props.ref ||
85+
typeof svgEle.props.ref !== "object" ||
86+
!("current" in svgEle.props.ref)
87+
) {
88+
return null;
8989
}
90-
return svgString;
90+
return svgEle.props.ref.current;
9191
}, []);
9292

93-
const svgToPngBlob = useCallback(
94-
async (svgEl: SVGSVGElement): Promise<Blob> => {
95-
const rect = svgEl.getBoundingClientRect();
96-
const viewBox = svgEl.viewBox?.baseVal;
97-
const width = Math.max(
98-
1,
99-
viewBox?.width || svgEl.width?.baseVal?.value || rect.width || 800
100-
);
101-
const height = Math.max(
102-
1,
103-
viewBox?.height || svgEl.height?.baseVal?.value || rect.height || 600
104-
);
105-
const pixelRatio = Math.max(1, window.devicePixelRatio || 1);
106-
107-
const svgString = serializeSvg(svgEl);
108-
const blob = new Blob([svgString], {
109-
type: "image/svg+xml;charset=utf-8"
110-
});
111-
const url = URL.createObjectURL(blob);
112-
try {
113-
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
114-
const image = new Image();
115-
image.crossOrigin = "anonymous";
116-
image.onload = () => resolve(image);
117-
image.onerror = (e) => reject(e);
118-
image.src = url;
119-
});
120-
const canvas = document.createElement("canvas");
121-
canvas.width = Math.round(width * pixelRatio * scale);
122-
canvas.height = Math.round(height * pixelRatio * scale);
123-
const ctx = canvas.getContext("2d");
124-
if (!ctx) throw new Error("CanvasContext is null");
125-
ctx.scale(pixelRatio * scale, pixelRatio * scale);
126-
ctx.drawImage(img, 0, 0, width, height);
127-
return await new Promise<Blob>((resolve, reject) => {
128-
canvas.toBlob(
129-
(b) => (b ? resolve(b) : reject(new Error("toBlob failed"))),
130-
"image/png"
131-
);
132-
});
133-
} finally {
134-
URL.revokeObjectURL(url);
135-
}
136-
},
137-
[scale, serializeSvg]
138-
);
139-
14093
const handleDownload = useCallback(async () => {
14194
const svgEl = getCurrentSvgElement();
14295
if (!svgEl) {
14396
message.warning("未找到可下载的图片");
14497
return;
14598
}
14699
try {
147-
const blob = await svgToPngBlob(svgEl);
100+
const blob = await domtoimage.toBlob(svgEl, globalOptions);
101+
console.log(blob);
148102
saveAs(blob, "svg-viewer.png");
149103
message.success("图片已开始下载");
150104
} catch (e) {
151105
message.error("下载失败");
106+
console.error(e);
152107
}
153-
}, [getCurrentSvgElement, svgToPngBlob]);
108+
}, [getCurrentSvgElement]);
154109

155110
const handleCopyImage = useCallback(async () => {
156111
const svgEl = getCurrentSvgElement();
@@ -159,7 +114,7 @@ export default ({
159114
return;
160115
}
161116
try {
162-
const blob = await svgToPngBlob(svgEl);
117+
const blob = await domtoimage.toPng(svgEl, globalOptions);
163118
if (navigator.clipboard && (navigator.clipboard as any).write) {
164119
await navigator.clipboard.write([
165120
new window.ClipboardItem({ "image/png": blob })
@@ -169,9 +124,10 @@ export default ({
169124
message.warning("当前环境不支持复制图片");
170125
}
171126
} catch (e) {
127+
console.error(e);
172128
message.error("复制失败");
173129
}
174-
}, [getCurrentSvgElement, svgToPngBlob]);
130+
}, [getCurrentSvgElement]);
175131

176132
const handleCopyCode = useCallback(async () => {
177133
try {

src/components/markmap/View.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { createStyles } from "antd-style";
2-
import { schemePaired as color, scaleOrdinal } from "d3";
3-
import type { INode } from "markmap-common";
42
import { Transformer } from "markmap-lib";
5-
import "markmap-toolbar/dist/style.css";
63
import * as markmap from "markmap-view";
74
import type { Markmap as MarkmapType } from "markmap-view";
85
import { defaultOptions } from "markmap-view";
@@ -13,8 +10,6 @@ import { SvgViewer } from "@site/src/components/markdown";
1310

1411
const { Markmap, loadCSS, loadJS } = markmap;
1512

16-
const renderColor = scaleOrdinal(color);
17-
1813
// Single transformer instance to reuse across renders
1914
const transformer = new Transformer();
2015

@@ -59,7 +54,7 @@ export default ((props: MarkmapProps) => {
5954
const { markdown, onReady } = props;
6055
const { styles } = useStyles();
6156

62-
const refSvg = useRef<SVGElement | null>(null);
57+
const refSvg = useRef<SVGSVGElement | null>(null);
6358
const refMm = useRef<MarkmapType | null>(null);
6459
const transformed = useMemo(
6560
() => transformer.transform(markdown),
@@ -73,10 +68,6 @@ export default ((props: MarkmapProps) => {
7368
...defaultOptions,
7469
zoom: true,
7570
duration: 0,
76-
color: (node: INode) => {
77-
const path = node.state?.path || "";
78-
return renderColor(path);
79-
}
8071
});
8172

8273
mm.setData(transformed.root).then(() => {

src/css/custom.css

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
*/
66

77
/* You can override the default Infima variables here. */
8-
9-
@import url("https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css");
10-
@import url("https://cdn.jsdelivr.net/npm/@callmebill/lxgw-wenkai-web@latest/style.css");
11-
128
:root {
139
--ifm-color-primary: #2e8555;
1410
--ifm-color-primary-dark: #29784c;
@@ -173,7 +169,7 @@ table tr:nth-child(2n) {
173169
font-family: "Monaspace Radon Var";
174170
src:
175171
local("Monaspace Radon Var"),
176-
url("https://cdn.jsdelivr.net/gh/githubnext/monaspace@main/fonts/Web Fonts/Variable Web Fonts/Monaspace Radon/MonaspaceRadon Var.woff2")
172+
url("https://cdn.jsdelivr.net/gh/githubnext/monaspace@main/fonts/Web Fonts/Variable Web Fonts/Monaspace Radon/Monaspace Radon Var.woff2")
177173
format("opentype");
178174
font-weight: 100 700;
179175
font-style: italic;

0 commit comments

Comments
 (0)