refactor: extract shared parse markdown

- Added `@microflash/remark-callout-directives` to `package.json` and `pnpm-lock.yaml`.
- Updated `MarkdownWeb` component to utilize a new markdown parsing utility.
- Removed unused imports related to `remark-gh-alerts` and refactored markdown parsing logic.
- Enhanced `parse-markdown` function to integrate with the new utility and support custom components.

This update improves markdown rendering capabilities and streamlines the codebase.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2024-12-31 23:52:34 +08:00
parent 93b38a7c70
commit 8007924a7f
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
7 changed files with 140 additions and 146 deletions

View File

@ -1,61 +1,12 @@
"use dom"
import "@/src/global.css"
import "remark-gh-alerts/styles/github-base.css"
import "remark-gh-alerts/styles/github-colors-dark-media.css"
import "remark-gh-alerts/styles/github-colors-light.css"
import { parseMarkdown } from "@follow/components/src/utils/parse-markdown"
import { cn } from "@follow/utils"
import type { Components } from "hast-util-to-jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import { Fragment, jsx, jsxs } from "react/jsx-runtime"
import rehypeStringify from "rehype-stringify"
import remarkDirective from "remark-directive"
import remarkGfm from "remark-gfm"
import remarkGithubAlerts from "remark-gh-alerts"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import { unified } from "unified"
import { useDarkMode } from "usehooks-ts"
import { VFile } from "vfile"
import { useCSSInjection } from "@/src/theme/web"
export interface RemarkOptions {
components: Partial<Components>
}
const parseMarkdown = (content: string, options?: Partial<RemarkOptions>) => {
const file = new VFile(content)
const { components } = options || {}
const pipeline = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkGithubAlerts)
.use(remarkDirective)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true })
const tree = pipeline.parse(content)
const hastTree = pipeline.runSync(tree, file)
return {
content: toJsxRuntime(hastTree, {
Fragment,
ignoreInvalidStyle: true,
jsx: (type, props, key) => jsx(type as any, props, key),
jsxs: (type, props, key) => jsxs(type as any, props, key),
passNode: true,
components: {
...components,
},
}),
}
}
const MarkdownWeb: WebComponent<{ value: string }> = ({ value }) => {
useCSSInjection()

View File

@ -1,96 +1,28 @@
import "@microflash/remark-callout-directives/theme/github"
import "remark-gh-alerts/styles/github-colors-light.css"
import "remark-gh-alerts/styles/github-colors-dark-media.css"
import "remark-gh-alerts/styles/github-base.css"
import remarkCalloutDirectives from "@microflash/remark-callout-directives"
import type { Components } from "hast-util-to-jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import type { RemarkOptions } from "@follow/components/utils/parse-markdown.js"
import { parseMarkdown as parseMarkdownImpl } from "@follow/components/utils/parse-markdown.js"
import { createElement } from "react"
import { Fragment, jsx, jsxs } from "react/jsx-runtime"
import rehypeStringify from "rehype-stringify"
import remarkDirective from "remark-directive"
import remarkGfm from "remark-gfm"
import remarkGithubAlerts from "remark-gh-alerts"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import { unified } from "unified"
import { VFile } from "vfile"
import { MarkdownLink } from "~/components/ui/markdown/renderers/MarkdownLink"
import { VideoPlayer } from "~/components/ui/media/VideoPlayer"
export interface RemarkOptions {
components: Partial<Components>
}
export const parseMarkdown = (content: string, options?: Partial<RemarkOptions>) => {
const file = new VFile(content)
const { components } = options || {}
const pipeline = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkGithubAlerts)
.use(remarkDirective)
.use(remarkCalloutDirectives, {
aliases: {
danger: "deter",
tip: "note",
return parseMarkdownImpl(content, {
...options,
components: {
a: ({ node, ...props }) => createElement(MarkdownLink, { ...props } as any),
img: ({ node, ...props }) => {
const { src } = props
const isVideo = src?.endsWith(".mp4")
if (isVideo) {
return createElement(VideoPlayer, {
src: src as string,
})
}
return createElement("img", { ...props } as any)
},
callouts: {
note: {
title: "Note",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 8h.01M12 12v4"/><circle cx="12" cy="12" r="10"/></svg>`,
},
commend: {
title: "Success",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m8 12 2.7 2.7L16 9.3"/><circle cx="12" cy="12" r="10"/></svg>`,
},
warn: {
title: "Warning",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 9v4m0 4h.01M8.681 4.082C9.351 2.797 10.621 2 12 2s2.649.797 3.319 2.082l6.203 11.904a4.28 4.28 0 0 1-.046 4.019C20.793 21.241 19.549 22 18.203 22H5.797c-1.346 0-2.59-.759-3.273-1.995a4.28 4.28 0 0 1-.046-4.019L8.681 4.082Z"/></svg>`,
},
deter: {
title: "Danger",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 12s-5.6 4.6-3.6 8c1.6 2.6 5.7 2.7 7.2 0 2-3.7-3.6-8-3.6-8Z"/><path d="M13.004 2 8.5 9 6.001 6s-4.268 7.206-1.629 11.8c3.016 5.5 11.964 5.7 15.08 0C23.876 10 13.004 2 13.004 2Z"/></svg>`,
},
assert: {
title: "Info",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12.5 7.5h.01m-.01 4v4m-7.926.685L2 21l6.136-1.949c1.307.606 2.791.949 4.364.949 5.243 0 9.5-3.809 9.5-8.5S17.743 3 12.5 3 3 6.809 3 11.5c0 1.731.579 3.341 1.574 4.685"/></svg>`,
},
},
})
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true })
const tree = pipeline.parse(content)
const hastTree = pipeline.runSync(tree, file)
return {
content: toJsxRuntime(hastTree, {
Fragment,
ignoreInvalidStyle: true,
jsx: (type, props, key) => jsx(type as any, props, key),
jsxs: (type, props, key) => jsxs(type as any, props, key),
passNode: true,
components: {
a: ({ node, ...props }) => createElement(MarkdownLink, { ...props } as any),
img: ({ node, ...props }) => {
const { src } = props
const isVideo = src?.endsWith(".mp4")
if (isVideo) {
return createElement(VideoPlayer, {
src: src as string,
})
}
return createElement("img", { ...props } as any)
},
...components,
},
}),
}
...options?.components,
},
})
}
export { type RemarkOptions } from "@follow/components/utils/parse-markdown.js"

View File

@ -46,7 +46,6 @@
"update:main-hash": "tsx plugins/vite/generate-main-hash.ts"
},
"devDependencies": {
"@babel/generator": "7.26.3",
"@clack/prompts": "0.8.2",
"@egoist/tailwindcss-icons": "1.8.1",
"@electron-forge/cli": "7.6.0",
@ -57,14 +56,11 @@
"@electron-forge/publisher-github": "7.6.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@eslint/compat": "^1.2.4",
"@fontsource/sn-pro": "5.1.0",
"@hono/node-server": "1.13.7",
"@hookform/resolvers": "3.9.1",
"@iconify-json/logos": "1.2.3",
"@iconify-json/mingcute": "1.2.1",
"@iconify-json/simple-icons": "^1.2.15",
"@iconify/tools": "4.0.7",
"@microflash/remark-callout-directives": "4.3.2",
"@mozilla/readability": "^0.5.0",
"@pengx17/electron-forge-maker-appimage": "1.2.1",
"@sentry/vite-plugin": "2.22.7",
@ -141,7 +137,8 @@
"workbox-precaching": "patches/workbox-precaching.patch",
"@tanstack/react-virtual": "patches/@tanstack__react-virtual.patch",
"@pengx17/electron-forge-maker-appimage": "patches/@pengx17__electron-forge-maker-appimage.patch",
"devlop": "patches/devlop.patch"
"devlop": "patches/devlop.patch",
"@microflash/remark-callout-directives": "patches/@microflash__remark-callout-directives.patch"
},
"overrides": {
"@types/react": "npm:@types/react@^18.3.12",

View File

@ -28,6 +28,7 @@
"@follow/types": "workspace:*",
"@follow/utils": "workspace:*",
"@headlessui/react": "2.2.0",
"@microflash/remark-callout-directives": "4.3.2",
"@radix-ui/react-avatar": "1.1.1",
"@radix-ui/react-checkbox": "1.1.2",
"@radix-ui/react-context-menu": "2.2.2",
@ -65,6 +66,10 @@
"rehype-parse": "9.0.1",
"rehype-sanitize": "6.0.0",
"rehype-stringify": "10.0.1",
"remark-directive": "3.0.0",
"remark-gfm": "4.0.0",
"remark-gh-alerts": "0.0.3",
"remark-rehype": "11.1.1",
"sonner": "1.7.1",
"unified": "11.0.5",
"unist-util-visit": "5.0.0",

View File

@ -0,0 +1,79 @@
import "@microflash/remark-callout-directives/themes/github/index.css"
import "remark-gh-alerts/styles/github-colors-light.css"
import "remark-gh-alerts/styles/github-colors-dark-media.css"
import "remark-gh-alerts/styles/github-base.css"
import remarkCalloutDirectives from "@microflash/remark-callout-directives"
import type { Components } from "hast-util-to-jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import { Fragment, jsx, jsxs } from "react/jsx-runtime"
import rehypeStringify from "rehype-stringify"
import remarkDirective from "remark-directive"
import remarkGfm from "remark-gfm"
import remarkGithubAlerts from "remark-gh-alerts"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import { unified } from "unified"
import { VFile } from "vfile"
export interface RemarkOptions {
components: Partial<Components>
}
export const parseMarkdown = (content: string, options?: Partial<RemarkOptions>) => {
const file = new VFile(content)
const { components } = options || {}
const pipeline = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkGithubAlerts)
.use(remarkDirective)
.use(remarkCalloutDirectives, {
aliases: {
danger: "deter",
tip: "note",
},
callouts: {
note: {
title: "Note",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 8h.01M12 12v4"/><circle cx="12" cy="12" r="10"/></svg>`,
},
commend: {
title: "Success",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m8 12 2.7 2.7L16 9.3"/><circle cx="12" cy="12" r="10"/></svg>`,
},
warn: {
title: "Warning",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 9v4m0 4h.01M8.681 4.082C9.351 2.797 10.621 2 12 2s2.649.797 3.319 2.082l6.203 11.904a4.28 4.28 0 0 1-.046 4.019C20.793 21.241 19.549 22 18.203 22H5.797c-1.346 0-2.59-.759-3.273-1.995a4.28 4.28 0 0 1-.046-4.019L8.681 4.082Z"/></svg>`,
},
deter: {
title: "Danger",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 12s-5.6 4.6-3.6 8c1.6 2.6 5.7 2.7 7.2 0 2-3.7-3.6-8-3.6-8Z"/><path d="M13.004 2 8.5 9 6.001 6s-4.268 7.206-1.629 11.8c3.016 5.5 11.964 5.7 15.08 0C23.876 10 13.004 2 13.004 2Z"/></svg>`,
},
assert: {
title: "Info",
hint: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12.5 7.5h.01m-.01 4v4m-7.926.685L2 21l6.136-1.949c1.307.606 2.791.949 4.364.949 5.243 0 9.5-3.809 9.5-8.5S17.743 3 12.5 3 3 6.809 3 11.5c0 1.731.579 3.341 1.574 4.685"/></svg>`,
},
},
})
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true })
const tree = pipeline.parse(content)
const hastTree = pipeline.runSync(tree, file)
return {
content: toJsxRuntime(hastTree, {
Fragment,
ignoreInvalidStyle: true,
jsx: (type, props, key) => jsx(type as any, props, key),
jsxs: (type, props, key) => jsxs(type as any, props, key),
passNode: true,
components,
}),
}
}

View File

@ -0,0 +1,12 @@
diff --git a/package.json b/package.json
index de27f20188873e2f432fff3aba04bbe5a0a33389..d4926c3f2eb58a65d04354665db3b30c940dd3e5 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"./config/github": "./themes/github/index.js",
"./config/vitepress": "./themes/vitepress/index.js",
"./theme/github": "./themes/github/index.css",
+ "./themes/*": "./themes/*",
"./theme/microflash": "./themes/microflash/index.css",
"./theme/vitepress": "./themes/vitepress/index.css"
},

View File

@ -13,6 +13,9 @@ overrides:
vfile: 5.3.7
patchedDependencies:
'@microflash/remark-callout-directives':
hash: bl6uhe4vs4xm3d2jmecdpzbh2m
path: patches/@microflash__remark-callout-directives.patch
'@mozilla/readability':
hash: 43niildbdafdxi7qfcwhpkkxwa
path: patches/@mozilla__readability.patch
@ -107,7 +110,7 @@ importers:
version: 4.0.7
'@microflash/remark-callout-directives':
specifier: 4.3.2
version: 4.3.2
version: 4.3.2(patch_hash=bl6uhe4vs4xm3d2jmecdpzbh2m)
'@mozilla/readability':
specifier: ^0.5.0
version: 0.5.0(patch_hash=43niildbdafdxi7qfcwhpkkxwa)
@ -658,7 +661,7 @@ importers:
version: 0.11.0(react@18.3.1)
'@microflash/remark-callout-directives':
specifier: 4.3.2
version: 4.3.2
version: 4.3.2(patch_hash=bl6uhe4vs4xm3d2jmecdpzbh2m)
'@openpanel/web':
specifier: 1.0.1
version: 1.0.1
@ -1195,6 +1198,9 @@ importers:
'@headlessui/react':
specifier: 2.2.0
version: 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@microflash/remark-callout-directives':
specifier: 4.3.2
version: 4.3.2(patch_hash=bl6uhe4vs4xm3d2jmecdpzbh2m)
'@radix-ui/react-avatar':
specifier: 1.1.1
version: 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -1312,6 +1318,18 @@ importers:
rehype-stringify:
specifier: 10.0.1
version: 10.0.1
remark-directive:
specifier: 3.0.0
version: 3.0.0
remark-gfm:
specifier: 4.0.0
version: 4.0.0
remark-gh-alerts:
specifier: 0.0.3
version: 0.0.3(@types/mdast@4.0.4)(unified@11.0.5)
remark-rehype:
specifier: 11.1.1
version: 11.1.1
sonner:
specifier: 1.7.1
version: 1.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -18150,7 +18168,7 @@ snapshots:
- encoding
- supports-color
'@microflash/remark-callout-directives@4.3.2':
'@microflash/remark-callout-directives@4.3.2(patch_hash=bl6uhe4vs4xm3d2jmecdpzbh2m)':
dependencies:
defu: 6.1.4
hastscript: 9.0.0