Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

File tree

4 files changed

+203
-1
lines changed

4 files changed

+203
-1
lines changed

‎site/src/components/Markdown/Markdown.stories.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,24 @@ export const WithTable: Story = {
7474
| cell 1 | cell 2 | 3 | 4 | `,
7575
},
7676
};
77+
78+
exportconstGFMAlerts:Story={
79+
args:{
80+
children:`
81+
> [!NOTE]
82+
> Useful information that users should know, even when skimming content.
83+
84+
> [!TIP]
85+
> Helpful advice for doing things better or more easily.
86+
87+
> [!IMPORTANT]
88+
> Key information users need to know to achieve their goal.
89+
90+
> [!WARNING]
91+
> Urgent info that needs immediate user attention to avoid problems.
92+
93+
> [!CAUTION]
94+
> Advises about risks or negative outcomes of certain actions.
95+
`,
96+
},
97+
};

‎site/src/components/Markdown/Markdown.tsx

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ import {
88
TableRow,
99
}from"components/Table/Table";
1010
importisEqualfrom"lodash/isEqual";
11-
import{typeFC,memo}from"react";
11+
import{
12+
typeFC,
13+
typeHTMLProps,
14+
typeReactElement,
15+
typeReactNode,
16+
isValidElement,
17+
memo,
18+
}from"react";
1219
importReactMarkdown,{typeOptions}from"react-markdown";
1320
import{PrismasSyntaxHighlighter}from"react-syntax-highlighter";
1421
import{dracula}from"react-syntax-highlighter/dist/cjs/styles/prism";
1522
importgfmfrom"remark-gfm";
1623
importcolorsfrom"theme/tailwindColors";
24+
import{cn}from"utils/cn";
1725

1826
interfaceMarkdownProps{
1927
/**
@@ -114,6 +122,30 @@ export const Markdown: FC<MarkdownProps> = (props) => {
114122
return<TableCell>{children}</TableCell>;
115123
},
116124

125+
/**
126+
* 2025-02-10 - The RemarkGFM plugin that we use currently doesn't have
127+
* support for special alert messages like this:
128+
* ```
129+
* > [!IMPORTANT]
130+
* > This module will only work with Git versions >=2.34, and...
131+
* ```
132+
* Have to intercept all blockquotes and see if their content is
133+
* formatted like an alert.
134+
*/
135+
blockquote:(parseProps)=>{
136+
const{node:_node, children, ...renderProps}=parseProps;
137+
constalertContent=parseChildrenAsAlertContent(children);
138+
if(alertContent===null){
139+
return<blockquote{...renderProps}>{children}</blockquote>;
140+
}
141+
142+
return(
143+
<MarkdownGfmAlertalertType={alertContent.type}{...renderProps}>
144+
{alertContent.children}
145+
</MarkdownGfmAlert>
146+
);
147+
},
148+
117149
...components,
118150
}}
119151
>
@@ -197,6 +229,149 @@ export const InlineMarkdown: FC<InlineMarkdownProps> = (props) => {
197229
exportconstMemoizedMarkdown=memo(Markdown,isEqual);
198230
exportconstMemoizedInlineMarkdown=memo(InlineMarkdown,isEqual);
199231

232+
constgithubFlavoredMarkdownAlertTypes=[
233+
"tip",
234+
"note",
235+
"important",
236+
"warning",
237+
"caution",
238+
];
239+
240+
typeAlertContent=Readonly<{
241+
type:string;
242+
children:readonlyReactNode[];
243+
}>;
244+
245+
functionparseChildrenAsAlertContent(
246+
jsxChildren:ReactNode,
247+
):AlertContent|null{
248+
// Have no idea why the plugin parses the data by mixing node types
249+
// like this. Have to do a good bit of nested filtering.
250+
if(!Array.isArray(jsxChildren)){
251+
returnnull;
252+
}
253+
254+
constmainParentNode=jsxChildren.find((node):node isReactElement=>
255+
isValidElement(node),
256+
);
257+
letparentChildren=mainParentNode?.props.children;
258+
if(typeofparentChildren==="string"){
259+
// Children will only be an array if the parsed text contains other
260+
// content that can be turned into HTML. If there aren't any, you
261+
// just get one big string
262+
parentChildren=parentChildren.split("\n");
263+
}
264+
if(!Array.isArray(parentChildren)){
265+
returnnull;
266+
}
267+
268+
constoutputContent=parentChildren
269+
.filter((el)=>{
270+
if(isValidElement(el)){
271+
returntrue;
272+
}
273+
returntypeofel==="string"&&el!=="\n";
274+
})
275+
.map((el)=>{
276+
if(!isValidElement(el)){
277+
returnel;
278+
}
279+
if(el.type!=="a"){
280+
returnel;
281+
}
282+
283+
constrecastProps=el.propsasRecord<string,unknown>&{
284+
children?:ReactNode;
285+
};
286+
if(recastProps.target==="_blank"){
287+
returnel;
288+
}
289+
290+
return{
291+
...el,
292+
props:{
293+
...recastProps,
294+
target:"_blank",
295+
children:(
296+
<>
297+
{recastProps.children}
298+
<spanclassName="sr-only"> (link opens in new tab)</span>
299+
</>
300+
),
301+
},
302+
};
303+
});
304+
const[firstEl, ...remainingChildren]=outputContent;
305+
if(typeoffirstEl!=="string"){
306+
returnnull;
307+
}
308+
309+
constalertType=firstEl
310+
.trim()
311+
.toLowerCase()
312+
.replace("!","")
313+
.replace("[","")
314+
.replace("]","");
315+
if(!githubFlavoredMarkdownAlertTypes.includes(alertType)){
316+
returnnull;
317+
}
318+
319+
consthasLeadingLinebreak=
320+
isValidElement(remainingChildren[0])&&remainingChildren[0].type==="br";
321+
if(hasLeadingLinebreak){
322+
remainingChildren.shift();
323+
}
324+
325+
return{
326+
type:alertType,
327+
children:remainingChildren,
328+
};
329+
}
330+
331+
typeMarkdownGfmAlertProps=Readonly<
332+
HTMLProps<HTMLElement>&{
333+
alertType:string;
334+
}
335+
>;
336+
337+
constMarkdownGfmAlert:FC<MarkdownGfmAlertProps>=({
338+
alertType,
339+
children,
340+
...delegatedProps
341+
})=>{
342+
return(
343+
<divclassName="pb-6">
344+
<aside
345+
{...delegatedProps}
346+
className={cn(
347+
"border-0 border-l-4 border-solid border-border p-4 text-white",
348+
"[&_p]:m-0 [&_p]:mb-2",
349+
350+
alertType==="important"&&
351+
"border-highlight-purple [&_p:first-child]:text-highlight-purple",
352+
353+
alertType==="warning"&&
354+
"border-border-warning [&_p:first-child]:text-border-warning",
355+
356+
alertType==="note"&&
357+
"border-highlight-sky [&_p:first-child]:text-highlight-sky",
358+
359+
alertType==="tip"&&
360+
"border-highlight-green [&_p:first-child]:text-highlight-green",
361+
362+
alertType==="caution"&&
363+
"border-highlight-red [&_p:first-child]:text-highlight-red",
364+
)}
365+
>
366+
<pclassName="font-bold">
367+
{alertType[0]?.toUpperCase()+alertType.slice(1).toLowerCase()}
368+
</p>
369+
{children}
370+
</aside>
371+
</div>
372+
);
373+
};
374+
200375
constmarkdownStyles:Interpolation<Theme>=(theme:Theme)=>({
201376
fontSize:16,
202377
lineHeight:"24px",

‎site/src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
--surface-orange:34100%92%;
3030
--surface-sky:20194%86%;
3131
--surface-red:093%94%;
32+
--surface-purple:25191%95%;
3233
--border-default:2406%90%;
3334
--border-success:14276%36%;
3435
--border-warning:30.66,97.16%,72.35%;
@@ -41,6 +42,7 @@
4142
--highlight-green:14364%24%;
4243
--highlight-grey:2405%65%;
4344
--highlight-sky:20190%27%;
45+
--highlight-red:074%42%;
4446
--border:2405.9%90%;
4547
--input:2405.9%90%;
4648
--ring:24010%3.9%;
@@ -69,6 +71,7 @@
6971
--surface-orange:1381%15%;
7072
--surface-sky:20480%16%;
7173
--surface-red:075%15%;
74+
--surface-purple:26173%23%;
7275
--border-default:2404%16%;
7376
--border-success:14276%36%;
7477
--border-warning:30.66,97.16%,72.35%;
@@ -80,6 +83,7 @@
8083
--highlight-green:14179%85%;
8184
--highlight-grey:2404%46%;
8285
--highlight-sky:19893%60%;
86+
--highlight-red:091%71%;
8387
--border:2403.7%15.9%;
8488
--input:2403.7%15.9%;
8589
--ring:2404.9%83.9%;

‎site/tailwind.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ module.exports = {
5353
orange:"hsl(var(--surface-orange))",
5454
sky:"hsl(var(--surface-sky))",
5555
red:"hsl(var(--surface-red))",
56+
purple:"hsl(var(--surface-purple))",
5657
},
5758
border:{
5859
DEFAULT:"hsl(var(--border-default))",
@@ -69,6 +70,7 @@ module.exports = {
6970
green:"hsl(var(--highlight-green))",
7071
grey:"hsl(var(--highlight-grey))",
7172
sky:"hsl(var(--highlight-sky))",
73+
red:"hsl(var(--highlight-red))",
7274
},
7375
},
7476
keyframes:{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp