-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmarkdown.tsx
More file actions
166 lines (161 loc) · 5.39 KB
/
markdown.tsx
File metadata and controls
166 lines (161 loc) · 5.39 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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import Markdown, { Components, ExtraProps } from "react-markdown";
import remarkGfm from "remark-gfm";
import removeComments from "remark-remove-comments";
import remarkCjkFriendly from "remark-cjk-friendly";
import { EditorComponent } from "@/terminal/editor";
import { ExecFile } from "@/terminal/exec";
import { JSX, ReactNode } from "react";
import { langConstants, MarkdownLang } from "@my-code/runtime/languages";
import { ReplTerminal } from "@/terminal/repl";
import { StyledSyntaxHighlighter } from "./styledSyntaxHighlighter";
export function StyledMarkdown({ content }: { content: string }) {
return (
<Markdown
remarkPlugins={[remarkGfm, removeComments, remarkCjkFriendly]}
components={components}
>
{content}
</Markdown>
);
}
// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
const components: Components = {
h1: ({ children }) => <Heading level={1}>{children}</Heading>,
h2: ({ children }) => <Heading level={2}>{children}</Heading>,
h3: ({ children }) => <Heading level={3}>{children}</Heading>,
h4: ({ children }) => <Heading level={4}>{children}</Heading>,
h5: ({ children }) => <Heading level={5}>{children}</Heading>,
h6: ({ children }) => <Heading level={6}>{children}</Heading>,
p: ({ node, ...props }) => <p className="mx-2 my-2" {...props} />,
ul: ({ node, ...props }) => (
<ul className="list-disc list-outside ml-6 my-2" {...props} />
),
ol: ({ node, ...props }) => (
<ol className="list-decimal list-outside ml-6 my-2" {...props} />
),
li: ({ node, ...props }) => <li className="my-1" {...props} />,
a: ({ node, ...props }) => <a className="link link-info" {...props} />,
strong: ({ node, ...props }) => (
<strong className="text-primary" {...props} />
),
table: ({ node, ...props }) => (
<div className="w-max max-w-full overflow-x-auto mx-auto my-2 rounded-box border border-current/20 shadow-sm">
<table className="table w-max" {...props} />
</div>
),
hr: ({ node, ...props }) => <hr className="border-accent my-4" {...props} />,
pre: ({ node, ...props }) => props.children,
code: ({ node, className, ref, style, ...props }) => (
<CodeComponent {...{ node, className, ref, style, ...props }} />
),
};
export function Heading({
level,
children,
}: {
level: number;
children: ReactNode;
}) {
switch (level) {
case 0:
return null;
case 1:
return <h1 className="text-2xl font-bold my-4">{children}</h1>;
case 2:
return <h2 className="text-xl font-bold mt-4 mb-3 ">{children}</h2>;
case 3:
return <h3 className="text-lg font-bold mt-4 mb-2">{children}</h3>;
case 4:
return <h4 className="text-base font-bold mt-3 mb-2">{children}</h4>;
case 5:
// TODO: これ以下は4との差がない (全体的に大きくする必要がある?)
return <h5 className="text-base font-bold mt-3 mb-2">{children}</h5>;
case 6:
return <h6 className="text-base font-bold mt-3 mb-2">{children}</h6>;
}
}
function CodeComponent({
node,
className,
ref,
style,
...props
}: JSX.IntrinsicElements["code"] & ExtraProps) {
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
className || ""
);
if (match) {
const language = langConstants(match[1] as MarkdownLang | undefined);
if (match[2] === "-exec" && match[3]) {
/*
```python-exec:main.py
hello, world!
```
↓
---------------------------
[▶ 実行] `python main.py`
hello, world!
---------------------------
*/
if (language.runtime) {
return (
<ExecFile
language={language}
filenames={match[3].split(",")}
content={String(props.children || "").replace(/\n$/, "")}
/>
);
}
} else if (match[2] === "-repl") {
// repl付きの言語指定
if (!match[3]) {
console.error(
`${match[1]}-repl without terminal id! content: ${String(props.children).slice(0, 20)}...`
);
}
if (language.runtime) {
return (
<ReplTerminal
terminalId={match[3]}
language={language}
initContent={String(props.children || "").replace(/\n$/, "")}
/>
);
}
} else if (match[3]) {
// ファイル名指定がある場合、ファイルエディター
return (
<EditorComponent
language={language}
filename={match[3]}
readonly={match[2] === "-readonly"}
initContent={String(props.children || "").replace(/\n$/, "")}
/>
);
}
return (
<StyledSyntaxHighlighter language={language}>
{String(props.children || "").replace(/\n$/, "")}
</StyledSyntaxHighlighter>
);
} else if (String(props.children).includes("\n")) {
// 言語指定なしコードブロック
return (
<StyledSyntaxHighlighter language={langConstants(undefined)}>
{String(props.children || "").replace(/\n$/, "")}
</StyledSyntaxHighlighter>
);
} else {
// inline
return (
<InlineCode>{String(props.children || "").replace(/\n$/, "")}</InlineCode>
);
}
}
export function InlineCode({ children }: { children: ReactNode }) {
return (
<code className="bg-current/10 border border-current/20 px-1 py-0.5 mx-0.5 rounded-md">
{children}
</code>
);
}