Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Render markdown with mermaid graph #4

Merged
merged 9 commits into from
Oct 7, 2024
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
33 changes: 30 additions & 3 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
name: Release
# Sample workflow for building and deploying a Next.js site to GitHub Pages
#
# To get started with Next.js see: https://nextjs.org/docs/getting-started
#
name: Deploy Next.js site to Pages

on:
# Runs on pushes targeting the default branch
push:
branches:
- main
branches: ["main"]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
setup:
Expand All @@ -14,3 +33,11 @@ jobs:
uses: zhumeisongsong/shared-actions/.github/workflows/reusable-semantic-release.yml@main
secrets:
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}

build:
needs: [lint, type-check]
uses: zhumeisongsong/shared-actions/.github/workflows/reusable-nextjs-build-github-pages.yml@main

deploy:
needs: build
uses: zhumeisongsong/shared-actions/.github/workflows/reusable-nextjs-deploy-github-pages.yml@main
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: CI

on:
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

jobs:
setup:
uses: zhumeisongsong/shared-actions/.github/workflows/reusable-pnpm-setup.yml@main

lint:
needs: setup
uses: zhumeisongsong/shared-actions/.github/workflows/reusable-eslint.yml@main

type-check:
needs: setup
uses: zhumeisongsong/shared-actions/.github/workflows/reusable-ts-type-check.yml@main

1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"cSpell.words": [
"dompurify",
"Nextjs"
]
}
4 changes: 2 additions & 2 deletions _posts/2024-08-29-DDD.md → _posts/2024-08-29-DDD-1.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "Domain-Driven Design"
title: "Domain-Driven Design 1"
excerpt: ""
coverImage: "/blog/assets/ddd-cover.jpg"
date: "2024-08-29"
Expand All @@ -24,7 +24,7 @@ Given a deadline, as long as in this deadline before the completion of the requi
- Lack of process management
- Only the pursuit of short-term business goals

As a result, the corresponding code may be a lumpy mess that is difficult to extend and maintain, laying a huge technical pit for the future.
As a result, the corresponding code may be `a lumpy mess` that is difficult to extend and maintain, laying a huge technical pit for the future.

### Data-driven design

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"classnames": "^2.5.1",
"date-fns": "^3.6.0",
"dompurify": "^3.1.7",
"gray-matter": "^4.0.3",
"mermaid": "^11.3.0",
"next": "latest",
Expand All @@ -22,6 +23,7 @@
"remark-html": "^16.0.1"
},
"devDependencies": {
"@types/dompurify": "^3.0.5",
"@types/node": "^20.14.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
23 changes: 23 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/app/_components/body-markdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";
import markdownToHtml from "@/lib/markdown-to-html";
import { useEffect, useState } from "react";
import DOMPurify from 'dompurify';

import markdownStyles from "./markdown-styles.module.css";

type Props = {
content: string;
};

export function BodyMarkdown({ content }: Props) {
const [html, setHtml] = useState<string>("");
useEffect(() => {
markdownToHtml(content).then((html) => {
setHtml(html);
});
}, [content]);

return (
<div
className={markdownStyles["markdown"]}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }}
/>
);
}
35 changes: 35 additions & 0 deletions src/app/_components/body-mermaid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";
import mermaid from "mermaid";
import { useEffect, useRef } from "react";

import mermaidStyles from "./mermaid-styles.module.css";
zhumeisongsong marked this conversation as resolved.
Show resolved Hide resolved

type Props = {
graph: string;
};

export function BodyMermaid({ graph }: Props) {
const chartRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (chartRef.current) {
try {
mermaid.contentLoaded();
} catch (error) {
console.error("Error rendering mermaid diagram:", error);
// Optionally, you could set an error state here and render an error message
}
}
}, [graph]);

// Basic validation
if (!graph || typeof graph !== "string") {
return <div>Invalid graph data</div>;
}

return (
<div ref={chartRef} className={mermaidStyles["mermaid"]}>
<div className="mermaid">{graph}</div>
</div>
);
}
10 changes: 10 additions & 0 deletions src/app/_components/markdown-styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@
@apply text-sky-500 underline;
}

.markdown ul {
list-style: disc;
margin-left: 32px;
}

.markdown code {
@apply bg-orange-200 text-black;
}


3 changes: 3 additions & 0 deletions src/app/_components/mermaid-styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.mermaid {
text-align: center;
}
45 changes: 18 additions & 27 deletions src/app/_components/post-body.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,34 @@
"use client";

import { useEffect, useRef } from "react";
import mermaid from "mermaid";
import markdownStyles from "./markdown-styles.module.css";
import { BodyMarkdown } from "@/app/_components/body-markdown";
import { BodyMermaid } from "@/app/_components/body-mermaid";

type Props = {
content: string;
};

const isMermaidGraph = (content: string) => {
const mermaidKeywords = [
"graph",
"flowchart",
"sequenceDiagram",
"classDiagram",
"stateDiagram",
"erDiagram",
"journey",
];
return mermaidKeywords.some((keyword) => content.trim().startsWith(keyword));
};

export function PostBody({ content }: Props) {
const chartRef = useRef(null);
const array = content.split("```");

useEffect(() => {
array.map((item, index) => {
if (item.indexOf("graph") > -1 && chartRef.current) {
console.log(item);
mermaid.contentLoaded(); // Rerenders any existing mermaid diagrams
}
});
}, [array]);

return (
<div className="max-w-2xl mx-auto">
{array.map((item, index) => {
if (item.indexOf("graph") > -1) {
return (
<div ref={chartRef} key={index} className="mermaid">
{item}
</div>
);
if (isMermaidGraph(item)) {
return <BodyMermaid key={index} graph={item} />;
}
return (
<div
key={index}
className={markdownStyles["markdown"]}
dangerouslySetInnerHTML={{ __html: item }}
/>
);
return <BodyMarkdown key={index} content={item} />;
})}
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/markdown-to-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import remarkGfm from "remark-gfm";

export default async function markdownToHtml(markdown: string) {
const result = await remark()
// .use(remarkGfm)
// .use(remarkHtml)
.use(remarkGfm)
.use(remarkHtml)
.process(markdown);

return result.toString();
Expand Down