From 00e436c2dae2c603906f48621a1606906ad5cb46 Mon Sep 17 00:00:00 2001
From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
Date: Sat, 9 Aug 2025 22:46:13 +0900
Subject: [PATCH 1/2] =?UTF-8?q?markdown=E3=82=92=E3=82=BB=E3=82=AF?=
=?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=94=E3=81=A8=E3=81=AB=E5=88=86?=
=?UTF-8?q?=E5=89=B2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/[docs_id]/markdown.tsx | 19 ++++++--------
app/[docs_id]/page.tsx | 15 +++++++----
app/[docs_id]/section.tsx | 33 ++++++++++++++++++++++++
app/[docs_id]/splitMarkdown.ts | 46 ++++++++++++++++++++++++++++++++++
4 files changed, 96 insertions(+), 17 deletions(-)
create mode 100644 app/[docs_id]/section.tsx
create mode 100644 app/[docs_id]/splitMarkdown.ts
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..d495520 100644
--- a/app/[docs_id]/page.tsx
+++ b/app/[docs_id]/page.tsx
@@ -1,9 +1,10 @@
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 +15,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,9 +31,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..f3cc467
--- /dev/null
+++ b/app/[docs_id]/section.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+import { ReactNode } from "react";
+import { type MarkdownSection } from "./splitMarkdown";
+import { StyledMarkdown } from "./markdown";
+
+// 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;
+}
From 81a836faddda5917a0163668b7cf159ead5e3f92 Mon Sep 17 00:00:00 2001
From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
Date: Sat, 9 Aug 2025 23:16:06 +0900
Subject: [PATCH 2/2] =?UTF-8?q?ChatForm=E3=82=92section=E3=81=AE=E3=81=BB?=
=?UTF-8?q?=E3=81=86=E3=81=AB=E7=A7=BB=E5=8B=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/[docs_id]/page.tsx | 2 --
app/[docs_id]/section.tsx | 6 ++++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/[docs_id]/page.tsx b/app/[docs_id]/page.tsx
index d495520..cf80393 100644
--- a/app/[docs_id]/page.tsx
+++ b/app/[docs_id]/page.tsx
@@ -1,5 +1,4 @@
import { notFound } from "next/navigation";
-import { ChatForm } from "./chatForm";
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
@@ -38,7 +37,6 @@ export default async function Page({
{splitMdContent.map((section, index) => (
))}
-
);
}
diff --git a/app/[docs_id]/section.tsx b/app/[docs_id]/section.tsx
index f3cc467..9b7f11a 100644
--- a/app/[docs_id]/section.tsx
+++ b/app/[docs_id]/section.tsx
@@ -3,14 +3,16 @@
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}
- >
+
+
);
}