diff --git a/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx b/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx index 514ab4141..d77421ad2 100644 --- a/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx +++ b/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx @@ -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 -} -const parseMarkdown = (content: string, options?: Partial) => { - 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() diff --git a/apps/renderer/src/lib/parse-markdown.ts b/apps/renderer/src/lib/parse-markdown.ts index f0e0b06aa..f3ba091ef 100644 --- a/apps/renderer/src/lib/parse-markdown.ts +++ b/apps/renderer/src/lib/parse-markdown.ts @@ -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 -} export const parseMarkdown = (content: string, options?: Partial) => { - 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: ``, - }, - commend: { - title: "Success", - hint: ``, - }, - warn: { - title: "Warning", - hint: ``, - }, - deter: { - title: "Danger", - hint: ``, - }, - assert: { - title: "Info", - hint: ``, - }, - }, - }) - - .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" diff --git a/package.json b/package.json index 088cafd1e..e1f749afd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/components/package.json b/packages/components/package.json index 1edaa39e8..a751d6932 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -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", diff --git a/packages/components/src/utils/parse-markdown.tsx b/packages/components/src/utils/parse-markdown.tsx new file mode 100644 index 000000000..e8ef6daa7 --- /dev/null +++ b/packages/components/src/utils/parse-markdown.tsx @@ -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 +} +export const parseMarkdown = (content: string, options?: Partial) => { + 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: ``, + }, + commend: { + title: "Success", + hint: ``, + }, + warn: { + title: "Warning", + hint: ``, + }, + deter: { + title: "Danger", + hint: ``, + }, + assert: { + title: "Info", + hint: ``, + }, + }, + }) + + .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, + }), + } +} diff --git a/patches/@microflash__remark-callout-directives.patch b/patches/@microflash__remark-callout-directives.patch new file mode 100644 index 000000000..1c432e176 --- /dev/null +++ b/patches/@microflash__remark-callout-directives.patch @@ -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" + }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e497cef0..30117c834 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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