-
Notifications
You must be signed in to change notification settings - Fork 116
Expand file tree
/
Copy pathMarkdownFromRepo.tsx
More file actions
89 lines (77 loc) · 2.27 KB
/
Copy pathMarkdownFromRepo.tsx
File metadata and controls
89 lines (77 loc) · 2.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import './markdownFromRepo.scss';
import DOMPurify from 'dompurify';
import { marked } from 'marked';
import React, { useEffect, useState } from 'react';
interface MarkdownFromRepoProps {
src: string;
className?: string;
errorHref?: string;
}
const FETCH_TIMEOUT_MS = 10_000;
const githubSourceHref = (src: string, errorHref?: string): string => {
if (errorHref) {
return errorHref;
}
const repoPath = src.replace(/^\//, '');
return `https://github.com/OWASP/OpenCRE/blob/main/${repoPath}`;
};
export const MarkdownFromRepo = ({ src, className = '', errorHref }: MarkdownFromRepoProps) => {
const [html, setHtml] = useState<string>('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
setLoading(true);
setError(null);
fetch(src, { signal: controller.signal })
.then((res) => {
if (!res.ok) {
throw new Error(`Failed to load markdown (${res.status})`);
}
return res.text();
})
.then((markdown) => {
const parsed = marked.parse(markdown, { async: false });
const rendered = DOMPurify.sanitize(String(parsed), {
USE_PROFILES: { html: true },
});
setHtml(rendered);
})
.catch((err: Error) => {
if (err.name !== 'AbortError') {
setError(err.message);
}
})
.finally(() => {
window.clearTimeout(timeout);
if (!controller.signal.aborted) {
setLoading(false);
}
});
return () => {
controller.abort();
window.clearTimeout(timeout);
};
}, [src]);
if (loading) {
return <p className="markdown-from-repo__status">Loading…</p>;
}
if (error) {
return (
<div className="markdown-from-repo__error">
<p>{error}</p>
<p>
View the source on{' '}
<a href={githubSourceHref(src, errorHref)} target="_blank" rel="noreferrer">
GitHub
</a>
.
</p>
</div>
);
}
return (
<div className={`markdown-from-repo ${className}`.trim()} dangerouslySetInnerHTML={{ __html: html }} />
);
};