Skip to content

Commit b840a71

Browse files
cursoragentaeplay
andcommitted
Refactor: Fetch markdown content instead of converting HTML
Co-authored-by: anselm <[email protected]>
1 parent d71b1a6 commit b840a71

File tree

2 files changed

+94
-168
lines changed

2 files changed

+94
-168
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { mdxToMd } from "@/generate-docs/utils/mdx-processor.mjs";
2+
import fs from "fs";
3+
import path from "path";
4+
import { NextRequest, NextResponse } from "next/server";
5+
6+
type Params = {
7+
framework: string;
8+
slug?: string[];
9+
};
10+
11+
const DOCS_DIR = path.join(process.cwd(), "content", "docs");
12+
13+
/**
14+
* Get the file path for an MDX file given framework and slug
15+
*/
16+
function getMdxFilePath(framework: string, slug?: string[]): string | null {
17+
const slugPath = slug?.join("/");
18+
19+
// First try framework-specific MDX
20+
if (slugPath) {
21+
const frameworkPath = path.join(DOCS_DIR, slugPath, `${framework}.mdx`);
22+
if (fs.existsSync(frameworkPath)) {
23+
return frameworkPath;
24+
}
25+
}
26+
27+
// Fallback to generic MDX
28+
if (slugPath) {
29+
const genericPath = path.join(DOCS_DIR, `${slugPath}.mdx`);
30+
if (fs.existsSync(genericPath)) {
31+
return genericPath;
32+
}
33+
}
34+
35+
// Top-level index fallback
36+
const indexPath = path.join(DOCS_DIR, "index.mdx");
37+
if (fs.existsSync(indexPath)) {
38+
return indexPath;
39+
}
40+
41+
return null;
42+
}
43+
44+
export async function GET(
45+
request: NextRequest,
46+
{ params }: { params: Promise<Params> }
47+
) {
48+
const awaitedParams = await params;
49+
const framework = awaitedParams.framework;
50+
const slug = awaitedParams.slug ?? [];
51+
52+
// Check if this is a .md request by checking the URL pathname
53+
const url = new URL(request.url);
54+
if (!url.pathname.endsWith(".md")) {
55+
// Not a .md request - return 404 so page.tsx can handle it
56+
return new NextResponse(null, { status: 404 });
57+
}
58+
59+
// Remove .md from the last slug segment
60+
let cleanSlug = slug;
61+
if (slug.length > 0) {
62+
const lastSegment = slug[slug.length - 1];
63+
if (lastSegment?.endsWith(".md")) {
64+
cleanSlug = [...slug.slice(0, -1), lastSegment.replace(/\.md$/, "")];
65+
}
66+
}
67+
68+
const filePath = getMdxFilePath(framework, cleanSlug);
69+
if (!filePath) {
70+
return new NextResponse("Document not found", { status: 404 });
71+
}
72+
73+
try {
74+
const markdown = await mdxToMd(filePath, framework);
75+
return new NextResponse(markdown, {
76+
headers: {
77+
"Content-Type": "text/markdown; charset=utf-8",
78+
},
79+
});
80+
} catch (error) {
81+
console.error("Error converting MDX to Markdown:", error);
82+
return new NextResponse("Error generating markdown", { status: 500 });
83+
}
84+
}

homepage/homepage/components/docs/CopyAsMarkdownButton.tsx

Lines changed: 10 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -3,163 +3,12 @@
33
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
44
import { clsx } from "clsx";
55
import { useEffect, useState } from "react";
6-
7-
/**
8-
* Simple HTML to Markdown converter for common elements
9-
*/
10-
function htmlToMarkdown(html: string): string {
11-
// Create a temporary container to parse HTML
12-
const tempDiv = document.createElement("div");
13-
tempDiv.innerHTML = html;
14-
15-
function convertNode(node: Node): string {
16-
if (node.nodeType === Node.TEXT_NODE) {
17-
return node.textContent || "";
18-
}
19-
20-
if (node.nodeType !== Node.ELEMENT_NODE) {
21-
return "";
22-
}
23-
24-
const element = node as Element;
25-
const tagName = element.tagName.toLowerCase();
26-
const children = Array.from(element.childNodes)
27-
.map(convertNode)
28-
.join("")
29-
.trim();
30-
31-
switch (tagName) {
32-
case "h1":
33-
return `# ${children}\n\n`;
34-
case "h2":
35-
return `## ${children}\n\n`;
36-
case "h3":
37-
return `### ${children}\n\n`;
38-
case "h4":
39-
return `#### ${children}\n\n`;
40-
case "h5":
41-
return `##### ${children}\n\n`;
42-
case "h6":
43-
return `###### ${children}\n\n`;
44-
case "p":
45-
return `${children}\n\n`;
46-
case "strong":
47-
case "b":
48-
return `**${children}**`;
49-
case "em":
50-
case "i":
51-
return `*${children}*`;
52-
case "code":
53-
// Check if parent is pre (code block) or inline
54-
if (element.parentElement?.tagName.toLowerCase() === "pre") {
55-
return children;
56-
}
57-
return `\`${children}\``;
58-
case "pre":
59-
const codeElement = element.querySelector("code");
60-
const language = codeElement?.className
61-
?.replace(/language-/, "")
62-
.replace(/hljs\s+/, "") || "";
63-
const codeContent = codeElement
64-
? Array.from(codeElement.childNodes)
65-
.map(convertNode)
66-
.join("")
67-
: children;
68-
return `\`\`\`${language}\n${codeContent}\n\`\`\`\n\n`;
69-
case "ul":
70-
return `${children}\n`;
71-
case "ol":
72-
return `${children}\n`;
73-
case "li":
74-
const parent = element.parentElement;
75-
const isOrdered = parent?.tagName.toLowerCase() === "ol";
76-
const index = parent
77-
? Array.from(parent.children).indexOf(element) + 1
78-
: 1;
79-
const prefix = isOrdered ? `${index}. ` : "- ";
80-
return `${prefix}${children}\n`;
81-
case "a":
82-
const href = element.getAttribute("href") || "";
83-
return `[${children}](${href})`;
84-
case "blockquote":
85-
return `> ${children.split("\n").join("\n> ")}\n\n`;
86-
case "hr":
87-
return `---\n\n`;
88-
case "br":
89-
return "\n";
90-
case "table":
91-
const thead = element.querySelector("thead");
92-
const tbody = element.querySelector("tbody");
93-
let result = "";
94-
95-
// Process header row
96-
if (thead) {
97-
const headerRow = thead.querySelector("tr");
98-
if (headerRow) {
99-
const headerCells = Array.from(headerRow.children)
100-
.map((cell) => {
101-
const cellContent = Array.from(cell.childNodes)
102-
.map(convertNode)
103-
.join("")
104-
.trim();
105-
return cellContent;
106-
})
107-
.join(" | ");
108-
result += `| ${headerCells} |\n`;
109-
// Add separator row
110-
const cellCount = headerRow.children.length;
111-
result += `| ${Array(cellCount).fill("---").join(" | ")} |\n`;
112-
}
113-
}
114-
115-
// Process body rows
116-
if (tbody) {
117-
const rows = tbody.querySelectorAll("tr");
118-
rows.forEach((row) => {
119-
const cells = Array.from(row.children)
120-
.map((cell) => {
121-
const cellContent = Array.from(cell.childNodes)
122-
.map(convertNode)
123-
.join("")
124-
.trim();
125-
return cellContent;
126-
})
127-
.join(" | ");
128-
result += `| ${cells} |\n`;
129-
});
130-
}
131-
132-
return result + "\n";
133-
case "thead":
134-
case "tbody":
135-
// Handled in table
136-
return children;
137-
case "tr":
138-
// Handled in table/thead/tbody
139-
return children;
140-
case "th":
141-
case "td":
142-
// Handled in tr
143-
return children;
144-
case "img":
145-
const src = element.getAttribute("src") || "";
146-
const alt = element.getAttribute("alt") || "";
147-
return `![${alt}](${src})`;
148-
default:
149-
return children;
150-
}
151-
}
152-
153-
return Array.from(tempDiv.childNodes)
154-
.map(convertNode)
155-
.join("")
156-
.replace(/\n{3,}/g, "\n\n")
157-
.trim();
158-
}
6+
import { usePathname } from "next/navigation";
1597

1608
export function CopyAsMarkdownButton() {
1619
const [copied, setCopied] = useState(false);
16210
const [isLoading, setIsLoading] = useState(false);
11+
const pathname = usePathname();
16312

16413
useEffect(() => {
16514
if (copied) {
@@ -171,23 +20,16 @@ export function CopyAsMarkdownButton() {
17120
const handleCopy = async () => {
17221
setIsLoading(true);
17322
try {
174-
// Find the prose content area
175-
const proseElement = document.querySelector(".prose");
176-
if (!proseElement) {
177-
console.error("Could not find prose element");
178-
return;
23+
// Append .md to the current pathname to get the markdown route
24+
const markdownUrl = `${pathname}.md`;
25+
26+
// Fetch the markdown from the route
27+
const response = await fetch(markdownUrl);
28+
if (!response.ok) {
29+
throw new Error(`Failed to fetch markdown: ${response.statusText}`);
17930
}
18031

181-
// Clone the element to avoid modifying the original
182-
const cloned = proseElement.cloneNode(true) as HTMLElement;
183-
184-
// Remove elements that shouldn't be in markdown
185-
cloned.querySelectorAll(".not-prose, [data-pagefind-ignore]").forEach((el) => {
186-
el.remove();
187-
});
188-
189-
// Convert HTML to markdown
190-
const markdown = htmlToMarkdown(cloned.innerHTML);
32+
const markdown = await response.text();
19133

19234
// Copy to clipboard
19335
await navigator.clipboard.writeText(markdown);

0 commit comments

Comments
 (0)