Skip to content

Commit 8bdb978

Browse files
committed
MarkdownComponentの型指定修正&SyntaxHighlighterとその言語指定をまとめた
1 parent 9ac5b21 commit 8bdb978

File tree

4 files changed

+136
-45
lines changed

4 files changed

+136
-45
lines changed

app/[docs_id]/markdown.tsx

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
1-
import Markdown, { Components } from "react-markdown";
1+
import Markdown, { Components, ExtraProps } from "react-markdown";
22
import remarkGfm from "remark-gfm";
33
import { EditorComponent, getAceLang } from "../terminal/editor";
44
import { ExecFile } from "../terminal/exec";
5-
import { useChangeTheme } from "./themeToggle";
6-
import {
7-
tomorrow,
8-
tomorrowNight,
9-
} from "react-syntax-highlighter/dist/esm/styles/hljs";
10-
import { ReactNode } from "react";
5+
import { JSX, ReactNode } from "react";
116
import { getRuntimeLang } from "@/terminal/runtime";
127
import { ReplTerminal } from "@/terminal/repl";
13-
import dynamic from "next/dynamic";
14-
// SyntaxHighlighterはファイルサイズがでかいので & HydrationErrorを起こすので、SSRを無効化する
15-
const SyntaxHighlighter = dynamic(() => import("react-syntax-highlighter"), {
16-
ssr: false,
17-
});
8+
import {
9+
getSyntaxHighlighterLang,
10+
MarkdownLang,
11+
StyledSyntaxHighlighter,
12+
} from "./styledSyntaxHighlighter";
1813

1914
export function StyledMarkdown({ content }: { content: string }) {
2015
return (
@@ -86,20 +81,12 @@ function CodeComponent({
8681
ref,
8782
style,
8883
...props
89-
}: {
90-
node: unknown;
91-
className?: string;
92-
ref?: unknown;
93-
style?: unknown;
94-
[key: string]: unknown;
95-
}) {
96-
const theme = useChangeTheme();
97-
const codetheme = theme === "tomorrow" ? tomorrow : tomorrowNight;
84+
}: JSX.IntrinsicElements["code"] & ExtraProps) {
9885
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
9986
className || ""
10087
);
10188
if (match) {
102-
const runtimeLang = getRuntimeLang(match[1]);
89+
const runtimeLang = getRuntimeLang(match[1] as MarkdownLang | undefined);
10390
if (match[2] === "-exec" && match[3]) {
10491
/*
10592
```python-exec:main.py
@@ -138,7 +125,7 @@ function CodeComponent({
138125
}
139126
} else if (match[3]) {
140127
// ファイル名指定がある場合、ファイルエディター
141-
const aceLang = getAceLang(match[1]);
128+
const aceLang = getAceLang(match[1] as MarkdownLang | undefined);
142129
return (
143130
<EditorComponent
144131
language={aceLang}
@@ -148,28 +135,20 @@ function CodeComponent({
148135
/>
149136
);
150137
}
138+
const syntaxHighlighterLang = getSyntaxHighlighterLang(
139+
match[1] as MarkdownLang | undefined
140+
);
151141
return (
152-
<SyntaxHighlighter
153-
language={match[1]}
154-
PreTag="div"
155-
className="border-2 border-current/20 mx-2 my-2 rounded-box p-4! bg-base-300! text-base-content!"
156-
style={codetheme}
157-
{...props}
158-
>
142+
<StyledSyntaxHighlighter language={syntaxHighlighterLang}>
159143
{String(props.children || "").replace(/\n$/, "")}
160-
</SyntaxHighlighter>
144+
</StyledSyntaxHighlighter>
161145
);
162146
} else if (String(props.children).includes("\n")) {
163147
// 言語指定なしコードブロック
164148
return (
165-
<SyntaxHighlighter
166-
PreTag="div"
167-
className="border-2 border-current/20 mx-2 my-2 rounded-box p-4! bg-base-300! text-base-content!"
168-
style={codetheme}
169-
{...props}
170-
>
149+
<StyledSyntaxHighlighter language={undefined}>
171150
{String(props.children || "").replace(/\n$/, "")}
172-
</SyntaxHighlighter>
151+
</StyledSyntaxHighlighter>
173152
);
174153
} else {
175154
// inline
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useChangeTheme } from "./themeToggle";
2+
import {
3+
tomorrow,
4+
tomorrowNight,
5+
} from "react-syntax-highlighter/dist/esm/styles/hljs";
6+
import dynamic from "next/dynamic";
7+
8+
// SyntaxHighlighterはファイルサイズがでかいので & HydrationErrorを起こすので、SSRを無効化する
9+
const SyntaxHighlighter = dynamic(() => import("react-syntax-highlighter"), {
10+
ssr: false,
11+
});
12+
13+
// Markdownで指定される可能性のある言語名を列挙
14+
export type MarkdownLang =
15+
| "python"
16+
| "py"
17+
| "ruby"
18+
| "rb"
19+
| "cpp"
20+
| "c++"
21+
| "javascript"
22+
| "js"
23+
| "typescript"
24+
| "ts"
25+
| "bash"
26+
| "sh"
27+
| "json"
28+
| "csv"
29+
| "text"
30+
| "txt";
31+
32+
// react-syntax-highliter (hljs版) が対応している言語
33+
// https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD を参照
34+
export type SyntaxHighlighterLang =
35+
| "python"
36+
| "ruby"
37+
| "c"
38+
| "cpp"
39+
| "javascript"
40+
| "typescript"
41+
| "bash"
42+
| "json";
43+
export function getSyntaxHighlighterLang(
44+
lang: MarkdownLang | undefined
45+
): SyntaxHighlighterLang | undefined {
46+
switch (lang) {
47+
case "python":
48+
case "py":
49+
return "python";
50+
case "ruby":
51+
case "rb":
52+
return "ruby";
53+
case "cpp":
54+
case "c++":
55+
return "cpp";
56+
case "javascript":
57+
case "js":
58+
return "javascript";
59+
case "typescript":
60+
case "ts":
61+
return "typescript";
62+
case "bash":
63+
case "sh":
64+
return "bash";
65+
case "json":
66+
return "json";
67+
case "csv": // not supported
68+
case "text":
69+
case "txt":
70+
case undefined:
71+
return undefined;
72+
default:
73+
lang satisfies never;
74+
console.warn(`Language not listed in MarkdownLang: ${lang}`);
75+
return undefined;
76+
}
77+
}
78+
export function StyledSyntaxHighlighter(props: {
79+
children: string;
80+
language: SyntaxHighlighterLang | undefined;
81+
}) {
82+
const theme = useChangeTheme();
83+
const codetheme = theme === "tomorrow" ? tomorrow : tomorrowNight;
84+
return (
85+
<SyntaxHighlighter
86+
language={props.language}
87+
PreTag="div"
88+
className="border-2 border-current/20 mx-2 my-2 rounded-box p-4! bg-base-300! text-base-content!"
89+
style={codetheme}
90+
>
91+
{props.children}
92+
</SyntaxHighlighter>
93+
);
94+
}

app/terminal/editor.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import clsx from "clsx";
2727
import { useChangeTheme } from "../[docs_id]/themeToggle";
2828
import { useEmbedContext } from "./embedContext";
2929
import { langConstants } from "./runtime";
30+
import { MarkdownLang } from "@/[docs_id]/styledSyntaxHighlighter";
3031
// snippetを有効化するにはsnippetもimportする必要がある: import "ace-builds/src-min-noconflict/snippets/python";
3132

3233
// mode-xxxx.js のファイル名と、AceEditorの mode プロパティの値が対応する
@@ -39,7 +40,7 @@ export type AceLang =
3940
| "json"
4041
| "csv"
4142
| "text";
42-
export function getAceLang(lang: string | undefined): AceLang {
43+
export function getAceLang(lang: MarkdownLang | undefined): AceLang {
4344
// Markdownで指定される可能性のある言語名からAceLangを取得
4445
switch (lang) {
4546
case "python":
@@ -61,13 +62,16 @@ export function getAceLang(lang: string | undefined): AceLang {
6162
return "json";
6263
case "csv":
6364
return "csv";
65+
case "sh":
66+
case "bash":
6467
case "text":
6568
case "txt":
69+
case undefined:
70+
console.warn(`Ace editor mode not implemented for language: ${lang}`);
6671
return "text";
6772
default:
68-
console.warn(
69-
`Unsupported language for ace editor: ${lang}, fallback to text mode.`
70-
);
73+
lang satisfies never;
74+
console.warn(`Language not listed in MarkdownLang: ${lang}`);
7175
return "text";
7276
}
7377
}

app/terminal/runtime.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RubyContext, useRuby } from "./worker/ruby";
88
import { JSEvalContext, useJSEval } from "./worker/jsEval";
99
import { WorkerProvider } from "./worker/runtime";
1010
import { TypeScriptProvider, useTypeScript } from "./typescript/runtime";
11+
import { MarkdownLang } from "@/[docs_id]/styledSyntaxHighlighter";
1112

1213
/**
1314
* Common runtime context interface for different languages
@@ -24,7 +25,10 @@ export interface RuntimeContext {
2425
checkSyntax?: (code: string) => Promise<SyntaxStatus>;
2526
splitReplExamples?: (content: string) => ReplCommand[];
2627
// file
27-
runFiles: (filenames: string[], files: Readonly<Record<string, string>>) => Promise<ReplOutput[]>;
28+
runFiles: (
29+
filenames: string[],
30+
files: Readonly<Record<string, string>>
31+
) => Promise<ReplOutput[]>;
2832
getCommandlineStr?: (filenames: string[]) => string;
2933
}
3034
export interface LangConstants {
@@ -41,7 +45,7 @@ export type RuntimeLang =
4145
| "typescript";
4246

4347
export function getRuntimeLang(
44-
lang: string | undefined
48+
lang: MarkdownLang | undefined
4549
): RuntimeLang | undefined {
4650
// markdownで指定される可能性のある言語名からRuntimeLangを取得
4751
switch (lang) {
@@ -60,8 +64,18 @@ export function getRuntimeLang(
6064
case "typescript":
6165
case "ts":
6266
return "typescript";
67+
case "bash":
68+
case "sh":
69+
case "json":
70+
case "csv":
71+
case "text":
72+
case "txt":
73+
case undefined:
74+
// unsupported languages
75+
return undefined;
6376
default:
64-
console.warn(`Unsupported language for runtime: ${lang}`);
77+
lang satisfies never;
78+
console.warn(`Language not listed in MarkdownLang: ${lang}`);
6579
return undefined;
6680
}
6781
}

0 commit comments

Comments
 (0)