Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions app/[docs_id]/markdown.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -12,18 +13,12 @@ export function StyledMarkdown({ content }: { content: string }) {

// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
const components: Components = {
h1: ({ node, ...props }) => (
<h1 className="text-2xl font-bold my-4" {...props} />
),
h2: ({ node, ...props }) => (
<h2 className="text-xl font-bold mt-4 mb-3 " {...props} />
),
h3: ({ node, ...props }) => (
<h3 className="text-lg font-bold mt-4 mb-2" {...props} />
),
h4: ({ node, ...props }) => (
<h4 className="text-base font-bold mt-3 mb-2" {...props} />
),
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} />
Expand Down
17 changes: 10 additions & 7 deletions app/[docs_id]/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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());
Expand All @@ -30,10 +30,13 @@ export default async function Page({
notFound();
}

const splitMdContent: MarkdownSection[] = await splitMarkdown(mdContent);

return (
<div className="p-4">
<StyledMarkdown content={mdContent} />
<ChatForm />
{splitMdContent.map((section, index) => (
<Section key={index} section={section} />
))}
</div>
);
}
35 changes: 35 additions & 0 deletions app/[docs_id]/section.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<Heading level={section.level}>{section.title}</Heading>
<StyledMarkdown content={section.content} />
<ChatForm />
</div>
);
}

export function Heading({ level, children }: { level: number; children: ReactNode }) {
switch (level) {
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>;
}
}
46 changes: 46 additions & 0 deletions app/[docs_id]/splitMarkdown.ts
Original file line number Diff line number Diff line change
@@ -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<MarkdownSection[]> {
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;
}