diff --git a/app/[docs_id]/markdown.tsx b/app/[docs_id]/markdown.tsx
index 800366a..71b3611 100644
--- a/app/[docs_id]/markdown.tsx
+++ b/app/[docs_id]/markdown.tsx
@@ -1,6 +1,7 @@
import Markdown, { Components } from "react-markdown";
import remarkGfm from "remark-gfm";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { Heading } from "./section";
export function StyledMarkdown({ content }: { content: string }) {
return (
@@ -12,18 +13,12 @@ export function StyledMarkdown({ content }: { content: string }) {
// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
const components: Components = {
- h1: ({ node, ...props }) => (
-
- ),
- h2: ({ node, ...props }) => (
-
- ),
- h3: ({ node, ...props }) => (
-
- ),
- h4: ({ node, ...props }) => (
-
- ),
+ h1: ({ children }) => {children},
+ h2: ({ children }) => {children},
+ h3: ({ children }) => {children},
+ h4: ({ children }) => {children},
+ h5: ({ children }) => {children},
+ h6: ({ children }) => {children},
p: ({ node, ...props }) => ,
ul: ({ node, ...props }) => (
diff --git a/app/[docs_id]/page.tsx b/app/[docs_id]/page.tsx
index 8a2d4ac..cf80393 100644
--- a/app/[docs_id]/page.tsx
+++ b/app/[docs_id]/page.tsx
@@ -1,9 +1,9 @@
import { notFound } from "next/navigation";
-import { ChatForm } from "./chatForm";
-import { StyledMarkdown } from "./markdown";
import { getCloudflareContext } from "@opennextjs/cloudflare";
-import { readFile } from "node:fs/promises";
+import { readFile } from "node:fs/promises";
import { join } from "node:path";
+import { MarkdownSection, splitMarkdown } from "./splitMarkdown";
+import { Section } from "./section";
export default async function Page({
params,
@@ -14,13 +14,13 @@ export default async function Page({
let mdContent: string;
try {
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
mdContent = await readFile(
join(process.cwd(), "public", "docs", `${docs_id}.md`),
"utf-8"
);
} else {
- const cfAssets = getCloudflareContext().env.ASSETS;
+ const cfAssets = getCloudflareContext().env.ASSETS;
mdContent = await cfAssets!
.fetch(`https://assets.local/docs/${docs_id}.md`)
.then((res) => res.text());
@@ -30,10 +30,13 @@ export default async function Page({
notFound();
}
+ const splitMdContent: MarkdownSection[] = await splitMarkdown(mdContent);
+
return (
-
-
+ {splitMdContent.map((section, index) => (
+
+ ))}
);
}
diff --git a/app/[docs_id]/section.tsx b/app/[docs_id]/section.tsx
new file mode 100644
index 0000000..9b7f11a
--- /dev/null
+++ b/app/[docs_id]/section.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import { ReactNode } from "react";
+import { type MarkdownSection } from "./splitMarkdown";
+import { StyledMarkdown } from "./markdown";
+import { ChatForm } from "./chatForm";
+
+// 1つのセクションのタイトルと内容を表示する。内容はMarkdownとしてレンダリングする
+export function Section({ section }: { section: MarkdownSection }) {
+ return (
+
+ {section.title}
+
+
+
+ );
+}
+
+export function Heading({ level, children }: { level: number; children: ReactNode }) {
+ switch (level) {
+ case 1:
+ return {children}
;
+ case 2:
+ return {children}
;
+ case 3:
+ return {children}
;
+ case 4:
+ return {children}
;
+ case 5:
+ // TODO: これ以下は4との差がない (全体的に大きくする必要がある?)
+ return {children}
;
+ case 6:
+ return {children}
;
+ }
+}
diff --git a/app/[docs_id]/splitMarkdown.ts b/app/[docs_id]/splitMarkdown.ts
new file mode 100644
index 0000000..104e2e2
--- /dev/null
+++ b/app/[docs_id]/splitMarkdown.ts
@@ -0,0 +1,46 @@
+"use server";
+
+import { unified } from "unified";
+import remarkParse from "remark-parse";
+import remarkGfm from "remark-gfm";
+
+export interface MarkdownSection {
+ level: number;
+ title: string;
+ content: string;
+}
+/**
+ * Markdownコンテンツを見出しごとに分割し、
+ * 見出しのレベルとタイトル、内容を含むオブジェクトの配列を返す。
+ */
+export async function splitMarkdown(
+ content: string
+): Promise {
+ const tree = unified().use(remarkParse).use(remarkGfm).parse(content);
+ // console.log(tree.children.map(({ type, position }) => ({ type, position: JSON.stringify(position) })));
+ const headingNodes = tree.children.filter((node) => node.type === "heading");
+ const splitContent = content.split("\n");
+ const sections: MarkdownSection[] = [];
+ for (let i = 0; i < headingNodes.length; i++) {
+ const startLine = headingNodes.at(i)?.position?.start.line;
+ if (startLine === undefined) {
+ continue;
+ }
+ let endLine: number | undefined = undefined;
+ for (let j = i + 1; j < headingNodes.length; j++) {
+ if (headingNodes.at(j)?.position?.start.line !== undefined) {
+ endLine = headingNodes.at(j)!.position!.start.line;
+ break;
+ }
+ }
+ sections.push({
+ title: splitContent[startLine - 1].replace(/#+\s*/, "").trim(),
+ content: splitContent
+ .slice(startLine - 1 + 1, endLine ? endLine - 1 : undefined)
+ .join("\n")
+ .trim(),
+ level: headingNodes.at(i)!.depth,
+ });
+ }
+ return sections;
+}