Skip to content

Commit 1f3d95c

Browse files
committed
feat: support gfm alerts
Signed-off-by: Innei <[email protected]>
1 parent fe01bb5 commit 1f3d95c

File tree

6 files changed

+114
-19
lines changed

6 files changed

+114
-19
lines changed

src/components/icons/status.tsx

+8-18
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,16 @@ export function ClaritySuccessLine(props: SVGProps<SVGSVGElement>) {
4444

4545
export function IonInformation(props: SVGProps<SVGSVGElement>) {
4646
return (
47-
<svg width="1em" height="1em" viewBox="0 0 512 512" {...props}>
48-
<path
49-
fill="none"
50-
stroke="currentColor"
51-
strokeLinecap="round"
52-
strokeLinejoin="round"
53-
strokeWidth="40"
54-
d="M196 220h64v172"
55-
/>
56-
<path
57-
fill="none"
58-
stroke="currentColor"
59-
strokeLinecap="round"
60-
strokeMiterlimit="10"
61-
strokeWidth="40"
62-
d="M187 396h138"
63-
/>
47+
<svg
48+
xmlns="http://www.w3.org/2000/svg"
49+
width="1em"
50+
height="1em"
51+
viewBox="0 0 256 256"
52+
{...props}
53+
>
6454
<path
6555
fill="currentColor"
66-
d="M256 160a32 32 0 1 1 32-32a32 32 0 0 1-32 32Z"
56+
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm0 192a88 88 0 1 1 88-88a88.1 88.1 0 0 1-88 88Zm16-40a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8Zm-32-92a12 12 0 1 1 12 12a12 12 0 0 1-12-12Z"
6757
/>
6858
</svg>
6959
)

src/components/ui/link/MLink.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ export const MLink: FC<{
8484

8585
const showRichLink = !!parsedType && !!parsedName
8686

87-
console.log(text || parsedName, 'text || parsedName')
8887
return (
8988
<FloatPopover
9089
as="span"

src/components/ui/markdown/Markdown.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Gallery } from '../gallery'
1919
import { LinkCard } from '../link-card'
2020
import { MLink } from '../link/MLink'
2121
import styles from './markdown.module.css'
22+
import { AlertsRule } from './parsers/alert'
2223
import { ContainerRule } from './parsers/container'
2324
import { InsertRule } from './parsers/ins'
2425
import { KateXRule } from './parsers/katex'
@@ -249,6 +250,7 @@ export const Markdown: FC<MdProps & MarkdownToJSX.Options & PropsWithChildren> =
249250
ins: InsertRule,
250251
kateX: KateXRule,
251252
container: ContainerRule,
253+
alerts: AlertsRule,
252254

253255
...additionalParserRules,
254256
},

src/components/ui/markdown/customize.md

+11
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,14 @@ Inline https://github.com/Innei
198198
```
199199

200200
[Innei 太菜了]{GH@Innei}
201+
202+
## Alerts
203+
204+
> [!NOTE]
205+
> Highlights information that users should take into account, even when skimming.
206+
207+
> [!IMPORTANT]
208+
> Crucial information necessary for users to succeed.
209+
210+
> [!WARNING]
211+
> Critical content demanding immediate user attention due to potential risks.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import clsx from 'clsx'
2+
import { blockRegex, Priority } from 'markdown-to-jsx'
3+
import type { MarkdownToJSX } from 'markdown-to-jsx'
4+
5+
import {
6+
FluentShieldError20Regular,
7+
FluentWarning28Regular,
8+
IonInformation,
9+
} from '~/components/icons/status'
10+
11+
import { Markdown } from '../Markdown'
12+
13+
const textColorMap = {
14+
NOTE: 'text-always-blue-500 dark:text-always-blue-400',
15+
IMPORTANT: 'text-accent',
16+
WARNING: 'text-amber-500 dark:text-amber-400',
17+
} as any
18+
19+
const borderColorMap = {
20+
NOTE: 'border-always-blue-500 dark:border-always-blue-400',
21+
IMPORTANT: 'border-accent',
22+
WARNING: 'border-amber-500 dark:border-amber-400',
23+
} as any
24+
25+
const typedIconMap = {
26+
NOTE: IonInformation,
27+
IMPORTANT: FluentWarning28Regular,
28+
WARNING: FluentShieldError20Regular,
29+
} as any
30+
31+
/**
32+
*
33+
* > [!NOTE]
34+
* > Highlights information that users should take into account, even when skimming.
35+
*/
36+
const ALERT_BLOCKQUOTE_R =
37+
// /^( *> *\[!(NOTE|IMPORTANT|WARNING)\] *\n(?: *>.*(?:\n|$))+)/m
38+
// /^( *> *\[!(?:(?<type>NOTE|IMPORTANT|WARNING))\](?<body>.*(\n *>.*?)*))(?=\n *> *\[(NOTE|IMPORTANT|WARNING)\]|$)/
39+
// /^(> *\[!(?<type>NOTE|IMPORTANT|WARNING)\](?<body>(?:\n?.*?)*))((?:\n{2,})|$)/
40+
// /^*> *\[!(?<type>NOTE|IMPORTANT|WARNING)\](?<body>(?:\n? *>.*?)*?)(?=\n{2,}|$)/
41+
// /^( *> *\[!(?<type>NOTE|IMPORTANT|WARNING)\].*?(?:\n(?! *> *\[(NOTE|IMPORTANT|WARNING)\]).*?)*)/
42+
43+
// /^(> \[!(?<type>NOTE|IMPORTANT|WARNING)\])(?<body>(?:\n? *>.*?(?!\n *> *\[(?:NOTE|IMPORTANT|WARNING)\]))*)/
44+
/^(> \[!(?<type>NOTE|IMPORTANT|WARNING)\].*?)(?<body>(?:\n *>.*?)*)(?=\n{2,}|$)/
45+
46+
export const AlertsRule: MarkdownToJSX.Rule = {
47+
match: blockRegex(ALERT_BLOCKQUOTE_R),
48+
order: Priority.HIGH,
49+
parse(capture) {
50+
return {
51+
raw: capture[0],
52+
parsed: {
53+
...capture.groups,
54+
},
55+
}
56+
},
57+
react(node, output, state) {
58+
const { type, body } = node.parsed
59+
const bodyClean = body.replace(/^> */gm, '')
60+
61+
const typePrefix = type[0] + type.toLowerCase().slice(1)
62+
63+
const Icon = typedIconMap[type] || typedIconMap.info
64+
return (
65+
<blockquote className={clsx(borderColorMap[type], 'not-italic')}>
66+
<span
67+
className={clsx(
68+
'text-semibold mb-1 inline-flex items-center',
69+
textColorMap[type],
70+
)}
71+
>
72+
<Icon
73+
className={clsx(
74+
`flex-shrink-0 text-3xl md:mr-2 md:self-start md:text-left`,
75+
typedIconMap[type] || typedIconMap.info,
76+
)}
77+
/>
78+
79+
{typePrefix}
80+
</span>
81+
<br />
82+
83+
<Markdown
84+
allowsScript
85+
className="not-prose w-full [&>p:first-child]:mt-0"
86+
>
87+
{bodyClean}
88+
</Markdown>
89+
</blockquote>
90+
)
91+
},
92+
}

src/components/ui/markdown/parsers/container.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const shouldCatchContainerName = [
2020
'warning',
2121
'note',
2222
].join('|')
23+
2324
export const ContainerRule: MarkdownToJSX.Rule = {
2425
match: blockRegex(
2526
new RegExp(

0 commit comments

Comments
 (0)