Skip to content

Commit 34509f8

Browse files
committed
マークダウン関連のコードをapp/markdownに移動
1 parent fe7d0e8 commit 34509f8

File tree

9 files changed

+210
-190
lines changed

9 files changed

+210
-190
lines changed

app/[lang]/[pageId]/markdown.tsx

Lines changed: 0 additions & 185 deletions
This file was deleted.

app/[lang]/[pageId]/pageContent.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { Fragment, useCallback, useEffect, useRef, useState } from "react";
44
import { ChatForm } from "./chatForm";
5-
import { Heading, StyledMarkdown } from "./markdown";
5+
import { StyledMarkdown } from "@/markdown/markdown";
66
import { useChatHistoryContext } from "./chatHistory";
77
import { useSidebarMdContext } from "@/sidebar";
88
import clsx from "clsx";
@@ -14,6 +14,7 @@ import {
1414
PagePath,
1515
} from "@/lib/docs";
1616
import { ReplacedRange } from "@/markdown/multiHighlight";
17+
import { Heading } from "@/markdown/heading";
1718

1819
/**
1920
* MarkdownSectionに追加で、動的な情報を持たせる

app/markdown/codeBlock.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { EditorComponent } from "@/terminal/editor";
2+
import { ExecFile } from "@/terminal/exec";
3+
import { ReplTerminal } from "@/terminal/repl";
4+
import { langConstants, MarkdownLang } from "@my-code/runtime/languages";
5+
import { JSX, ReactNode } from "react";
6+
import { ExtraProps } from "react-markdown";
7+
import { StyledSyntaxHighlighter } from "./styledSyntaxHighlighter";
8+
9+
export function AutoCodeBlock({
10+
node,
11+
className,
12+
ref,
13+
style,
14+
...props
15+
}: JSX.IntrinsicElements["code"] & ExtraProps) {
16+
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
17+
className || ""
18+
);
19+
if (match) {
20+
const language = langConstants(match[1] as MarkdownLang | undefined);
21+
if (match[2] === "-exec" && match[3]) {
22+
/*
23+
```python-exec:main.py
24+
hello, world!
25+
```
26+
27+
---------------------------
28+
[▶ 実行] `python main.py`
29+
hello, world!
30+
---------------------------
31+
*/
32+
if (language.runtime) {
33+
return (
34+
<ExecFile
35+
language={language}
36+
filenames={match[3].split(",")}
37+
content={String(props.children || "").replace(/\n$/, "")}
38+
/>
39+
);
40+
}
41+
} else if (match[2] === "-repl") {
42+
// repl付きの言語指定
43+
if (!match[3]) {
44+
console.error(
45+
`${match[1]}-repl without terminal id! content: ${String(props.children).slice(0, 20)}...`
46+
);
47+
}
48+
if (language.runtime) {
49+
return (
50+
<ReplTerminal
51+
terminalId={match[3]}
52+
language={language}
53+
initContent={String(props.children || "").replace(/\n$/, "")}
54+
/>
55+
);
56+
}
57+
} else if (match[3]) {
58+
// ファイル名指定がある場合、ファイルエディター
59+
return (
60+
<EditorComponent
61+
language={language}
62+
filename={match[3]}
63+
readonly={match[2] === "-readonly"}
64+
initContent={String(props.children || "").replace(/\n$/, "")}
65+
/>
66+
);
67+
}
68+
return (
69+
<StyledSyntaxHighlighter language={language}>
70+
{String(props.children || "").replace(/\n$/, "")}
71+
</StyledSyntaxHighlighter>
72+
);
73+
} else if (String(props.children).includes("\n")) {
74+
// 言語指定なしコードブロック
75+
return (
76+
<StyledSyntaxHighlighter language={langConstants(undefined)}>
77+
{String(props.children || "").replace(/\n$/, "")}
78+
</StyledSyntaxHighlighter>
79+
);
80+
} else {
81+
// inline
82+
return <InlineCode>{props.children}</InlineCode>;
83+
}
84+
}
85+
86+
export function InlineCode({ children }: { children: ReactNode }) {
87+
return (
88+
<code className="bg-current/10 border border-current/20 px-1 py-0.5 mx-0.5 rounded-md">
89+
{children}
90+
</code>
91+
);
92+
}

app/markdown/heading.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ReactNode } from "react";
2+
3+
export function Heading({
4+
level,
5+
children,
6+
}: {
7+
level: number;
8+
children: ReactNode;
9+
}) {
10+
switch (level) {
11+
case 0:
12+
return null;
13+
case 1:
14+
return <h1 className="text-2xl font-bold my-4">{children}</h1>;
15+
case 2:
16+
return <h2 className="text-xl font-bold mt-4 mb-3 ">{children}</h2>;
17+
case 3:
18+
return <h3 className="text-lg font-bold mt-4 mb-2">{children}</h3>;
19+
case 4:
20+
return <h4 className="text-base font-bold mt-3 mb-2">{children}</h4>;
21+
case 5:
22+
// TODO: これ以下は4との差がない (全体的に大きくする必要がある?)
23+
return <h5 className="text-base font-bold mt-3 mb-2">{children}</h5>;
24+
case 6:
25+
return <h6 className="text-base font-bold mt-3 mb-2">{children}</h6>;
26+
}
27+
}

app/markdown/markdown.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Markdown, { Components } from "react-markdown";
2+
import remarkGfm from "remark-gfm";
3+
import removeComments from "remark-remove-comments";
4+
import remarkCjkFriendly from "remark-cjk-friendly";
5+
import {
6+
MultiHighlightTag,
7+
remarkMultiHighlight,
8+
ReplacedRange,
9+
} from "./multiHighlight";
10+
import { Heading } from "./heading";
11+
import { AutoCodeBlock } from "./codeBlock";
12+
13+
export function StyledMarkdown(props: {
14+
content: string;
15+
replacedRange?: ReplacedRange[];
16+
}) {
17+
return (
18+
<Markdown
19+
remarkPlugins={[
20+
remarkGfm,
21+
removeComments,
22+
remarkCjkFriendly,
23+
[remarkMultiHighlight, props.replacedRange],
24+
]}
25+
components={components}
26+
>
27+
{props.content}
28+
</Markdown>
29+
);
30+
}
31+
32+
// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
33+
const components: Components = {
34+
h1: ({ children }) => <Heading level={1}>{children}</Heading>,
35+
h2: ({ children }) => <Heading level={2}>{children}</Heading>,
36+
h3: ({ children }) => <Heading level={3}>{children}</Heading>,
37+
h4: ({ children }) => <Heading level={4}>{children}</Heading>,
38+
h5: ({ children }) => <Heading level={5}>{children}</Heading>,
39+
h6: ({ children }) => <Heading level={6}>{children}</Heading>,
40+
p: ({ node, ...props }) => <p className="mx-2 my-2" {...props} />,
41+
ul: ({ node, ...props }) => (
42+
<ul className="list-disc list-outside ml-6 my-2" {...props} />
43+
),
44+
ol: ({ node, ...props }) => (
45+
<ol className="list-decimal list-outside ml-6 my-2" {...props} />
46+
),
47+
li: ({ node, ...props }) => <li className="my-1" {...props} />,
48+
a: ({ node, ...props }) => <a className="link link-info" {...props} />,
49+
strong: ({ node, ...props }) => (
50+
<strong className="text-primary" {...props} />
51+
),
52+
table: ({ node, ...props }) => (
53+
<div className="w-max max-w-full overflow-x-auto mx-auto my-2 rounded-box border border-current/20 shadow-sm">
54+
<table className="table w-max" {...props} />
55+
</div>
56+
),
57+
hr: ({ node, ...props }) => <hr className="border-accent my-4" {...props} />,
58+
pre: ({ node, ...props }) => props.children,
59+
code: AutoCodeBlock,
60+
ins: MultiHighlightTag,
61+
};

0 commit comments

Comments
 (0)