Skip to content

Instantly share code, notes, and snippets.

@CreatiCoding
Created October 12, 2024 06:22
Show Gist options
  • Save CreatiCoding/28d841883aef3b701835ee6dc8272cb0 to your computer and use it in GitHub Desktop.
Save CreatiCoding/28d841883aef3b701835ee6dc8272cb0 to your computer and use it in GitHub Desktop.
markdown api
import rehypeStringify from "rehype-stringify";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import remarkGfm from "remark-gfm";
import unified, { ProcessorSettings, Settings } from "unified";
import type { NextApiRequest, NextApiResponse } from "next";
// case 1 curl -XPOST "http://localhost:3000/api/markdown/render" -d '{ "markdown": "#test\n##test\n\n| ![](https://divopsor.github.io/blog-images/2024/05/18/the-best-welfare-is-colleagues-2-present-1.jpg) | ![](https://divopsor.github.io/blog-images/2024/05/18/the-best-welfare-is-colleagues-2-present-2.jpg) |\n| --- | --- |\n| ![](https://divopsor.github.io/blog-images/2024/05/18/the-best-welfare-is-colleagues-2-present-3.jpg) | ![](https://divopsor.github.io/blog-images/2024/05/18/the-best-welfare-is-colleagues-2.jpg) |\n" }' -H "Content-Type: application/json" | jq
// case 2 curl -XPOST "http://localhost:3000/api/markdown/render" -d '{ "markdown": "test![width=100% test](https://asd.com)" }' -H "Content-Type: application/json" | jq
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const markdown = req.body.markdown as string;
if (markdown == null || markdown === "") {
return res.status(200).json({ data: "" });
}
const file = await unified()
.use(remarkParse as ProcessorSettings<Settings>)
.use(remarkRehype, {
handlers: (node: any) => {
console.log(node);
if (node.tagName === "img") {
node.properties.loading = "lazy";
}
},
})
.use(remarkGfm)
.use(rehypeStringify)
.use(remarkNewlinesToBrs)
.use(remarkImageAltToWidth)
.process(markdown);
return res.status(200).json({
data: String(file),
});
} catch (error: any) {
res
.status(400)
.json({ message: "Internal server error", errorMessage: error.message });
}
}
const visit = require("unist-util-visit");
const u = require("unist-builder");
const remarkNewlinesToBrs = () => (tree: any) => {
visit(tree, "text", (node: any, index: any, parent: any) => {
const isTable = ["table", "thead", "tbody", "tr", "td"].includes(
parent.tagName
);
if (isTable) {
if (node.value.includes("\n")) {
// Split the text at newlines
const parts = node.value.split("\n");
const newNodes: any = [];
parts.forEach((part: any, i: any) => {
if (part !== "" || i !== parts.length - 1) {
// Add text node for the non-empty part
newNodes.push(u("text", part));
}
});
// Replace the current text node with the new nodes
parent.children.splice(index, 1, ...newNodes);
}
return;
}
if (node.value.includes("\n")) {
// Split the text at newlines
const parts = node.value.split("\n");
const newNodes: any = [];
parts.forEach((part: any, i: any) => {
if (part !== "") {
// Add text node for the non-empty part
newNodes.push(u("text", part));
}
if (i !== parts.length - 1) {
// Add a `br` element node for each newline, except after the last part
newNodes.push(u("element", { tagName: "br" }, []));
}
});
// Replace the current text node with the new nodes
parent.children.splice(index, 1, ...newNodes);
}
});
};
const remarkImageAltToWidth = () => (tree: any) => {
visit(tree, "element", (node: any, index: any, parent: any) => {
if (node.tagName === "img") {
const alt = node.properties.alt as string;
if (alt && alt.startsWith("width=")) {
const width = alt.split(" ")[0].split("=")[1];
node.properties.alt = alt.split(" ").slice(1).join(" ");
node.properties.style = `width: ${width};${
node.properties.style ? ` ${node.properties.style}` : ""
}`;
}
}
});
};
{
"name": "api",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prepare": "husky install"
},
"dependencies": {
"@babel/core": "^7.19.1",
"@emotion/react": "^11.10.4",
"date-fns": "^3.6.0",
"multer": "^1.4.5-lts.1",
"next": "12.3.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"rehype-stringify": "^8.0.0",
"remark-gfm": "^1.0.0",
"remark-parse": "^9.0.0",
"remark-rehype": "^8.1.0",
"unified": "^9.2.1",
"unist-builder": "^2.0.3",
"unist-util-visit": "^2.0.3"
},
"devDependencies": {
"@emotion/babel-plugin": "^11.10.2",
"@types/babel__core": "^7",
"@types/eslint": "^8",
"@types/multer": "^1",
"@types/node": "18.7.18",
"@types/react": "18.0.21",
"@types/react-dom": "18.0.6",
"@typescript-eslint/eslint-plugin": "^5.36.2",
"@typescript-eslint/parser": "^5.36.2",
"eslint": "8.23.1",
"eslint-config-next": "12.3.1",
"eslint-plugin-import": "^2.26.0",
"husky": "^8.0.1",
"typescript": "4.8.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment