mirror of
https://github.com/vee1e/isscope.git
synced 2026-09-01 10:48:36 +00:00
fix: sanitize markdown renderer
This commit is contained in:
parent
c0dcc9497b
commit
bed7f35777
3 changed files with 60 additions and 41 deletions
5
bun.lock
5
bun.lock
|
|
@ -13,6 +13,7 @@
|
|||
"react-dom": "^19.2.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
|
|
@ -472,6 +473,8 @@
|
|||
|
||||
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
|
||||
|
||||
"hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="],
|
||||
|
|
@ -690,6 +693,8 @@
|
|||
|
||||
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
|
||||
|
||||
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"react-dom": "^19.2.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { CSSProperties } from 'react';
|
||||
import ReactMarkdown, { type Components } from 'react-markdown';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
|
||||
interface MarkdownRendererProps {
|
||||
content: string;
|
||||
|
|
@ -8,54 +9,66 @@ interface MarkdownRendererProps {
|
|||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
function withoutNode<T extends { node?: unknown }>(props: T) {
|
||||
const rest = { ...props };
|
||||
delete rest.node;
|
||||
return rest;
|
||||
}
|
||||
const rehypePlugins = [rehypeRaw, rehypeSanitize];
|
||||
|
||||
const markdownComponents: Components = {
|
||||
img: ({ ...props }) => (
|
||||
<img
|
||||
{...withoutNode(props)}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--border-subtle)',
|
||||
}}
|
||||
/>
|
||||
),
|
||||
a: ({ ...props }) => (
|
||||
<a
|
||||
{...withoutNode(props)}
|
||||
style={{ color: 'var(--text)', textDecoration: 'underline' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
/>
|
||||
),
|
||||
p: ({ ...props }) => (
|
||||
<p {...withoutNode(props)} style={{ marginTop: '0.5em', marginBottom: '0.5em' }} />
|
||||
),
|
||||
pre: ({ ...props }) => (
|
||||
<pre
|
||||
{...withoutNode(props)}
|
||||
style={{
|
||||
background: 'var(--bg-secondary)',
|
||||
padding: '8px',
|
||||
borderRadius: '4px',
|
||||
overflowX: 'auto',
|
||||
}}
|
||||
/>
|
||||
),
|
||||
code: ({ ...props }) => (
|
||||
<code {...withoutNode(props)} style={{ fontFamily: 'var(--font-mono)', fontSize: '11px' }} />
|
||||
),
|
||||
img: ({ node, ...props }) => {
|
||||
void node;
|
||||
|
||||
return (
|
||||
<img
|
||||
{...props}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--border-subtle)',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
a: ({ node, ...props }) => {
|
||||
void node;
|
||||
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
style={{ color: 'var(--text)', textDecoration: 'underline' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
/>
|
||||
);
|
||||
},
|
||||
p: ({ node, ...props }) => {
|
||||
void node;
|
||||
|
||||
return <p {...props} style={{ marginTop: '0.5em', marginBottom: '0.5em' }} />;
|
||||
},
|
||||
pre: ({ node, ...props }) => {
|
||||
void node;
|
||||
|
||||
return (
|
||||
<pre
|
||||
{...props}
|
||||
style={{
|
||||
background: 'var(--bg-secondary)',
|
||||
padding: '8px',
|
||||
borderRadius: '4px',
|
||||
overflowX: 'auto',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
code: ({ node, ...props }) => {
|
||||
void node;
|
||||
|
||||
return <code {...props} style={{ fontFamily: 'var(--font-mono)', fontSize: '11px' }} />;
|
||||
},
|
||||
};
|
||||
|
||||
export function MarkdownRenderer({ content, className, style }: MarkdownRendererProps) {
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<ReactMarkdown rehypePlugins={[rehypeRaw]} components={markdownComponents}>
|
||||
<ReactMarkdown rehypePlugins={rehypePlugins} components={markdownComponents}>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue