Skip to content

Commit e1c7034

Browse files
committed
テーマチェンジ機能の追加
1 parent 1f36931 commit e1c7034

File tree

6 files changed

+208
-154
lines changed

6 files changed

+208
-154
lines changed

app/[docs_id]/markdown.tsx

Lines changed: 158 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -5,166 +5,174 @@ import { PythonEmbeddedTerminal } from "../terminal/python/embedded";
55
import { Heading } from "./section";
66
import { AceLang, EditorComponent } from "../terminal/editor";
77
import { ExecFile, ExecLang } from "../terminal/exec";
8+
import { ChangeTheme } from "./themeToggle";
9+
import { vscDarkPlus, prism } from "react-syntax-highlighter/dist/esm/styles/prism";
10+
11+
812

913
export function StyledMarkdown({ content }: { content: string }) {
10-
return (
11-
<Markdown remarkPlugins={[remarkGfm]} components={components}>
12-
{content}
13-
</Markdown>
14-
);
15-
}
14+
const syntaxtheme = ChangeTheme() === "twilight" ? vscDarkPlus : prism;
1615

17-
// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
18-
const components: Components = {
19-
h1: ({ children }) => <Heading level={1}>{children}</Heading>,
20-
h2: ({ children }) => <Heading level={2}>{children}</Heading>,
21-
h3: ({ children }) => <Heading level={3}>{children}</Heading>,
22-
h4: ({ children }) => <Heading level={4}>{children}</Heading>,
23-
h5: ({ children }) => <Heading level={5}>{children}</Heading>,
24-
h6: ({ children }) => <Heading level={6}>{children}</Heading>,
25-
p: ({ node, ...props }) => <p className="mx-2 my-2" {...props} />,
26-
ul: ({ node, ...props }) => (
27-
<ul className="list-disc list-outside ml-6 my-2" {...props} />
28-
),
29-
ol: ({ node, ...props }) => (
30-
<ol className="list-decimal list-outside ml-6 my-2" {...props} />
31-
),
32-
li: ({ node, ...props }) => <li className="my-1" {...props} />,
33-
a: ({ node, ...props }) => <a className="link link-info" {...props} />,
34-
strong: ({ node, ...props }) => (
35-
<strong className="text-primary" {...props} />
36-
),
37-
table: ({ node, ...props }) => (
38-
<div className="w-max max-w-full overflow-x-auto mx-auto my-2 rounded-lg border border-base-content/5 shadow-sm">
39-
<table className="table w-max" {...props} />
40-
</div>
41-
),
42-
hr: ({ node, ...props }) => <hr className="border-primary my-4" {...props} />,
43-
pre: ({ node, ...props }) => props.children,
44-
code: ({ node, className, ref, style, ...props }) => {
45-
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
46-
className || ""
47-
);
48-
if (match) {
49-
if (match[2] === "-exec" && match[3]) {
50-
/*
51-
```python-exec:main.py
52-
hello, world!
53-
```
54-
55-
---------------------------
56-
[▶ 実行] `python main.py`
16+
// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
17+
const components: Components = {
18+
h1: ({ children }) => <Heading level={1}>{children}</Heading>,
19+
h2: ({ children }) => <Heading level={2}>{children}</Heading>,
20+
h3: ({ children }) => <Heading level={3}>{children}</Heading>,
21+
h4: ({ children }) => <Heading level={4}>{children}</Heading>,
22+
h5: ({ children }) => <Heading level={5}>{children}</Heading>,
23+
h6: ({ children }) => <Heading level={6}>{children}</Heading>,
24+
p: ({ node, ...props }) => <p className="mx-2 my-2" {...props} />,
25+
ul: ({ node, ...props }) => (
26+
<ul className="list-disc list-outside ml-6 my-2" {...props} />
27+
),
28+
ol: ({ node, ...props }) => (
29+
<ol className="list-decimal list-outside ml-6 my-2" {...props} />
30+
),
31+
li: ({ node, ...props }) => <li className="my-1" {...props} />,
32+
a: ({ node, ...props }) => <a className="link link-info" {...props} />,
33+
strong: ({ node, ...props }) => (
34+
<strong className="text-primary" {...props} />
35+
),
36+
table: ({ node, ...props }) => (
37+
<div className="w-max max-w-full overflow-x-auto mx-auto my-2 rounded-lg border border-base-content/5 shadow-sm">
38+
<table className="table w-max" {...props} />
39+
</div>
40+
),
41+
hr: ({ node, ...props }) => <hr className="border-primary my-4" {...props} />,
42+
pre: ({ node, ...props }) => props.children,
43+
code: ({ node, className, ref, style, ...props }) => {
44+
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
45+
className || ""
46+
);
47+
if (match) {
48+
if (match[2] === "-exec" && match[3]) {
49+
/*
50+
```python-exec:main.py
5751
hello, world!
58-
---------------------------
59-
*/
60-
let execLang: ExecLang | undefined = undefined;
61-
switch (match[1]) {
62-
case "python":
63-
execLang = "python";
64-
break;
65-
case "cpp":
66-
case "c++":
67-
execLang = "cpp";
68-
break;
69-
default:
70-
console.warn(`Unsupported language for exec: ${match[1]}`);
71-
break;
72-
}
73-
if (execLang) {
52+
```
53+
54+
---------------------------
55+
[▶ 実行] `python main.py`
56+
hello, world!
57+
---------------------------
58+
*/
59+
let execLang: ExecLang | undefined = undefined;
60+
switch (match[1]) {
61+
case "python":
62+
execLang = "python";
63+
break;
64+
case "cpp":
65+
case "c++":
66+
execLang = "cpp";
67+
break;
68+
default:
69+
console.warn(`Unsupported language for exec: ${match[1]}`);
70+
break;
71+
}
72+
if (execLang) {
73+
return (
74+
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
75+
<ExecFile
76+
language={execLang}
77+
filenames={match[3].split(",")}
78+
content={String(props.children || "").replace(/\n$/, "")}
79+
/>
80+
</div>
81+
);
82+
}
83+
} else if (match[3]) {
84+
// ファイル名指定がある場合、ファイルエディター
85+
let aceLang: AceLang | undefined = undefined;
86+
switch (match[1]) {
87+
case "python":
88+
aceLang = "python";
89+
break;
90+
case "cpp":
91+
case "c++":
92+
aceLang = "c_cpp";
93+
break;
94+
case "json":
95+
aceLang = "json";
96+
break;
97+
case "csv":
98+
aceLang = "csv";
99+
break;
100+
case "text":
101+
case "txt":
102+
aceLang = "text";
103+
break;
104+
default:
105+
console.warn(`Unsupported language for editor: ${match[1]}`);
106+
break;
107+
}
74108
return (
75109
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
76-
<ExecFile
77-
language={execLang}
78-
filenames={match[3].split(",")}
79-
content={String(props.children || "").replace(/\n$/, "")}
110+
<EditorComponent
111+
language={aceLang}
112+
tabSize={4}
113+
filename={match[3]}
114+
readonly={match[2] === "-readonly"}
115+
initContent={String(props.children || "").replace(/\n$/, "")}
80116
/>
81117
</div>
82118
);
83-
}
84-
} else if (match[3]) {
85-
// ファイル名指定がある場合、ファイルエディター
86-
let aceLang: AceLang | undefined = undefined;
87-
switch (match[1]) {
88-
case "python":
89-
aceLang = "python";
90-
break;
91-
case "cpp":
92-
case "c++":
93-
aceLang = "c_cpp";
94-
break;
95-
case "json":
96-
aceLang = "json";
97-
break;
98-
case "csv":
99-
aceLang = "csv";
100-
break;
101-
case "text":
102-
case "txt":
103-
aceLang = "text";
104-
break;
105-
default:
106-
console.warn(`Unsupported language for editor: ${match[1]}`);
107-
break;
119+
} else if (match[2] === "-repl") {
120+
// repl付きの言語指定
121+
// 現状はPythonのみ対応
122+
switch (match[1]) {
123+
case "python":
124+
return (
125+
<div className="bg-base-300 border border-primary border-2 shadow-md m-2 p-4 pr-1 rounded-lg">
126+
<PythonEmbeddedTerminal
127+
content={String(props.children || "").replace(/\n$/, "")}
128+
/>
129+
</div>
130+
);
131+
default:
132+
console.warn(`Unsupported language for repl: ${match[1]}`);
133+
break;
134+
}
108135
}
109136
return (
110-
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
111-
<EditorComponent
112-
language={aceLang}
113-
tabSize={4}
114-
filename={match[3]}
115-
readonly={match[2] === "-readonly"}
116-
initContent={String(props.children || "").replace(/\n$/, "")}
117-
/>
118-
</div>
137+
<SyntaxHighlighter
138+
language={match[1]}
139+
PreTag="div"
140+
className="border border-base-300 mx-2 my-2 rounded-lg text-sm! m-2! p-4!"
141+
style={syntaxtheme}
142+
{...props}
143+
>
144+
{String(props.children || "").replace(/\n$/, "")}
145+
</SyntaxHighlighter>
146+
);
147+
} else if (String(props.children).includes("\n")) {
148+
// 言語指定なしコードブロック
149+
return (
150+
<SyntaxHighlighter
151+
PreTag="div"
152+
className="border border-base-300 mx-2 my-2 rounded-lg text-sm! m-2! p-4!"
153+
style={syntaxtheme}
154+
{...props}
155+
>
156+
{String(props.children || "").replace(/\n$/, "")}
157+
</SyntaxHighlighter>
158+
);
159+
} else {
160+
// inline
161+
return (
162+
<code
163+
className="bg-base-200/60 border border-base-300 px-1 py-0.5 rounded text-sm "
164+
{...props}
165+
/>
119166
);
120-
} else if (match[2] === "-repl") {
121-
// repl付きの言語指定
122-
// 現状はPythonのみ対応
123-
switch (match[1]) {
124-
case "python":
125-
return (
126-
<div className="bg-base-300 border border-primary border-2 shadow-md m-2 p-4 pr-1 rounded-lg">
127-
<PythonEmbeddedTerminal
128-
content={String(props.children || "").replace(/\n$/, "")}
129-
/>
130-
</div>
131-
);
132-
default:
133-
console.warn(`Unsupported language for repl: ${match[1]}`);
134-
break;
135-
}
136167
}
137-
return (
138-
<SyntaxHighlighter
139-
language={match[1]}
140-
PreTag="div"
141-
className="border border-base-300 mx-2 my-2 rounded-lg text-sm! m-2! p-4!"
142-
// style={todo dark theme?}
143-
{...props}
144-
>
145-
{String(props.children || "").replace(/\n$/, "")}
146-
</SyntaxHighlighter>
147-
);
148-
} else if (String(props.children).includes("\n")) {
149-
// 言語指定なしコードブロック
150-
return (
151-
<SyntaxHighlighter
152-
PreTag="div"
153-
className="border border-base-300 mx-2 my-2 rounded-lg text-sm! m-2! p-4!"
154-
// style={todo dark theme?}
155-
{...props}
156-
>
157-
{String(props.children || "").replace(/\n$/, "")}
158-
</SyntaxHighlighter>
159-
);
160-
} else {
161-
// inline
162-
return (
163-
<code
164-
className="bg-base-200/60 border border-base-300 px-1 py-0.5 rounded text-sm "
165-
{...props}
166-
/>
167-
);
168-
}
169-
},
170-
};
168+
},
169+
};
170+
171+
return (
172+
<Markdown remarkPlugins={[remarkGfm]} components={components}>
173+
{content}
174+
</Markdown>
175+
);
176+
}
177+
178+

app/[docs_id]/themeToggle.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
import { useState, useEffect } from "react";
3+
4+
export function ChangeTheme(){
5+
const [theme, setTheme] = useState("tomorrow");
6+
useEffect(() => {
7+
const updateTheme = () => {
8+
const theme = document.documentElement.getAttribute("data-theme");
9+
setTheme(theme === "dark" ? "twilight" : "tomorrow");
10+
};
11+
12+
const observer = new MutationObserver(updateTheme);
13+
observer.observe(document.documentElement, {
14+
attributes: true,
15+
attributeFilter: ["data-theme"],
16+
});
17+
18+
updateTheme(); // 初回実行
19+
20+
return () => observer.disconnect();
21+
}, []);
22+
return theme;
23+
24+
};
25+
export function ThemeToggle() {
26+
return (
27+
<input
28+
type="checkbox"
29+
className="toggle theme-controller"
30+
style={{ marginLeft: "1em" }}
31+
onChange={(e) => {
32+
const isDark = e.target.checked;
33+
const theme = isDark ? "dark" : "light";
34+
document.documentElement.setAttribute("data-theme", theme);
35+
}}
36+
/>
37+
);
38+
}

app/globals.css

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
@import "tailwindcss";
2-
@plugin "daisyui";
2+
@plugin "daisyui"
3+
{
4+
themes: light --default, dark --prefersdark;
5+
};
6+
7+
38

49
/* inconsolata-latin-wght-normal */
510
@font-face {

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default function RootLayout({
1616
children,
1717
}: Readonly<{ children: ReactNode }>) {
1818
return (
19-
<html lang="ja">
19+
<html lang="ja" data-theme="light">
2020
<body className="w-screen h-screen">
2121
<div className="drawer lg:drawer-open">
2222
<input id="drawer-toggle" type="checkbox" className="drawer-toggle" />

0 commit comments

Comments
 (0)