Skip to content

Commit 113a716

Browse files
committed
Add support for GitHub's admonitions
1 parent d8d3cde commit 113a716

File tree

6 files changed

+144
-2
lines changed

6 files changed

+144
-2
lines changed

frontend/package-lock.json

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

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@tanstack/react-query-devtools": "^5.53.2",
2121
"@types/react": "^18.3.5",
2222
"@types/react-dom": "^18.3.0",
23+
"@types/react-is": "^18.3.0",
2324
"@vitejs/plugin-react": "^4.3.1",
2425
"autoprefixer": "^10.4.20",
2526
"clsx": "^2.1.1",
@@ -37,6 +38,7 @@
3738
"react": "^18.3.1",
3839
"react-dom": "^18.3.1",
3940
"react-helmet-async": "^2.0.5",
41+
"react-is": "^18.3.1",
4042
"react-router-dom": "^6.26.1",
4143
"react-virtuoso": "^4.10.2",
4244
"rehype-raw": "^7.0.0",
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import clsx from "clsx";
2+
import { BlockquoteHTMLAttributes, ReactNode } from "react";
3+
import { isElement } from "react-is";
4+
5+
const WARNING_MARK = "[!WARNING]";
6+
const IMPORTANT_MARK = "[!DANGER]";
7+
const NOTE_MARK = "[!NOTE]";
8+
const TIP_MARK = "[!TIP]";
9+
const CAUTION_MARK = "[!CAUTION]";
10+
11+
function getAdmonitionClassName(prefix: string) {
12+
switch (prefix) {
13+
case NOTE_MARK:
14+
return "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-100";
15+
case CAUTION_MARK:
16+
return "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-100";
17+
case WARNING_MARK:
18+
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-950 dark:text-yellow-100";
19+
case TIP_MARK:
20+
return "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-100";
21+
case IMPORTANT_MARK:
22+
return "bg-purple-100 text-purple-800 dark:bg-purple-950 dark:text-purple-100";
23+
}
24+
}
25+
26+
function getAdmonitionTitle(prefix: string) {
27+
switch (prefix) {
28+
case NOTE_MARK:
29+
return "Note";
30+
case CAUTION_MARK:
31+
return "Caution";
32+
case WARNING_MARK:
33+
return "Warning";
34+
case TIP_MARK:
35+
return "Tip";
36+
case IMPORTANT_MARK:
37+
return "Important";
38+
}
39+
}
40+
41+
function getAdmonitionMatch(children: ReactNode) {
42+
if (!Array.isArray(children)) {
43+
return null;
44+
}
45+
46+
for (let i = 0; i < children.length; i++) {
47+
const child = children[i];
48+
49+
if (!isElement(child)) {
50+
continue;
51+
}
52+
53+
const type = child.props.children;
54+
55+
if (typeof type !== "string") {
56+
continue;
57+
}
58+
59+
switch (type) {
60+
case WARNING_MARK:
61+
case CAUTION_MARK:
62+
case NOTE_MARK:
63+
case TIP_MARK:
64+
case IMPORTANT_MARK:
65+
return {
66+
prefix: type,
67+
content: children.slice(i + 1),
68+
};
69+
}
70+
}
71+
72+
return null;
73+
}
74+
75+
export function MarkdownBlockquote({
76+
children,
77+
}: BlockquoteHTMLAttributes<HTMLQuoteElement>) {
78+
const admonitionMatch = getAdmonitionMatch(children);
79+
80+
if (admonitionMatch) {
81+
const { prefix, content } = admonitionMatch;
82+
const className = getAdmonitionClassName(prefix);
83+
const title = getAdmonitionTitle(prefix);
84+
85+
return (
86+
<div
87+
role="alert"
88+
className={clsx(
89+
"mt-5 px-3 py-2 first:mt-0 [&>h6]:mt-4 [&>p]:mt-2 [li>&:first-child]:mt-0",
90+
className,
91+
)}
92+
>
93+
<strong className="font-semibold">{title}</strong>
94+
{content}
95+
</div>
96+
);
97+
}
98+
99+
return (
100+
<blockquote className="mt-5 flex flex-col border-l-2 border-gray-500 pl-5">
101+
{children}
102+
</blockquote>
103+
);
104+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { HTMLAttributes } from "react";
2+
import { HeadingLink } from "../HeadingLink";
3+
4+
export function MarkdownH4({
5+
children,
6+
id,
7+
}: HTMLAttributes<HTMLHeadingElement>) {
8+
return (
9+
<h6
10+
className="group mt-8 scroll-mt-5 break-words text-lg font-bold first:mt-0"
11+
id={id}
12+
>
13+
{children}
14+
{id && <HeadingLink id={id} label={children as string} />}
15+
</h6>
16+
);
17+
}

frontend/src/components/Markdown/P.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ function getAdmonitionClassName(prefix: string) {
1414
return "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-100";
1515
case WARNING_MARK:
1616
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-950 dark:text-yellow-100";
17-
default:
18-
return "";
1917
}
2018
}
2119

frontend/src/components/Markdown/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { MarkdownTh } from "./Th";
2525
import { MarkdownImg } from "./Img";
2626
import { MarkdownOl } from "./Ol";
2727
import { MarkdownHr } from "./Hr";
28+
import { MarkdownBlockquote } from "./Blockquote";
29+
import { MarkdownH4 } from "./H4";
2830

2931
const production: Options = {
3032
development: false,
@@ -38,6 +40,7 @@ const production: Options = {
3840
h1: MarkdownH1,
3941
h2: MarkdownH2,
4042
h3: MarkdownH3,
43+
h4: MarkdownH4,
4144
p: MarkdownP,
4245
code: MarkdownCode,
4346
pre: MarkdownPre,
@@ -47,6 +50,7 @@ const production: Options = {
4750
img: MarkdownImg,
4851
ol: MarkdownOl,
4952
hr: MarkdownHr,
53+
blockquote: MarkdownBlockquote,
5054
},
5155
};
5256

0 commit comments

Comments
 (0)