mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-08 02:36:37 +08:00
feat(frontend): support markdown in dashboard announcements
This commit is contained in:
@@ -13,6 +13,8 @@
|
|||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"@internationalized/date": "^3.10.0",
|
||||||
|
"@marsidev/react-turnstile": "^1.1.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-checkbox": "^1.1.0",
|
"@radix-ui/react-checkbox": "^1.1.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.0",
|
"@radix-ui/react-dialog": "^1.1.0",
|
||||||
@@ -25,8 +27,6 @@
|
|||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-switch": "^1.1.0",
|
"@radix-ui/react-switch": "^1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@marsidev/react-turnstile": "^1.1.0",
|
|
||||||
"@internationalized/date": "^3.10.0",
|
|
||||||
"@react-aria/i18n": "^3.12.12",
|
"@react-aria/i18n": "^3.12.12",
|
||||||
"@react-aria/visually-hidden": "3.8.25",
|
"@react-aria/visually-hidden": "3.8.25",
|
||||||
"@react-types/shared": "3.30.0",
|
"@react-types/shared": "3.30.0",
|
||||||
@@ -43,8 +43,11 @@
|
|||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-hot-toast": "^2.5.2",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-is": "^19.2.4",
|
"react-is": "^19.2.4",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"react-router-dom": "6.30.3",
|
"react-router-dom": "6.30.3",
|
||||||
"recharts": "^3.1.1",
|
"recharts": "^3.1.1",
|
||||||
|
"rehype-sanitize": "^6.0.0",
|
||||||
|
"remark-gfm": "^4.0.1",
|
||||||
"sonner": "^2.0.6",
|
"sonner": "^2.0.6",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwind-variants": "1.0.0",
|
"tailwind-variants": "1.0.0",
|
||||||
|
|||||||
@@ -700,13 +700,16 @@ export default function ConfigPage() {
|
|||||||
<Textarea
|
<Textarea
|
||||||
label="公告内容"
|
label="公告内容"
|
||||||
minRows={4}
|
minRows={4}
|
||||||
placeholder="请输入公告内容"
|
placeholder="支持 Markdown,例如:**加粗**、[链接](https://example.com)、- 列表"
|
||||||
value={announcement.content}
|
value={announcement.content}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setAnnouncement({ ...announcement, content: e.target.value })
|
setAnnouncement({ ...announcement, content: e.target.value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
公告支持 Markdown 语法,链接会在新标签页打开
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { AnnouncementData } from "@/api";
|
import type { AnnouncementData } from "@/api";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import rehypeSanitize from "rehype-sanitize";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
|
||||||
import { Card, CardBody } from "@/shadcn-bridge/heroui/card";
|
import { Card, CardBody } from "@/shadcn-bridge/heroui/card";
|
||||||
|
|
||||||
@@ -35,9 +38,48 @@ export const AnnouncementBanner = ({
|
|||||||
<h3 className="text-base font-semibold leading-none text-blue-900 dark:text-blue-100 mb-1.5">
|
<h3 className="text-base font-semibold leading-none text-blue-900 dark:text-blue-100 mb-1.5">
|
||||||
公告
|
公告
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-blue-800 dark:text-blue-200 whitespace-pre-wrap break-words leading-relaxed">
|
<div className="text-sm text-blue-800 dark:text-blue-200 break-words leading-relaxed">
|
||||||
{announcement.content}
|
<ReactMarkdown
|
||||||
</p>
|
rehypePlugins={[rehypeSanitize]}
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
|
components={{
|
||||||
|
p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
|
||||||
|
a: ({ children, href }) => (
|
||||||
|
<a
|
||||||
|
className="underline decoration-blue-500/70 underline-offset-2 hover:text-blue-700 dark:hover:text-blue-100"
|
||||||
|
href={href}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
ul: ({ children }) => (
|
||||||
|
<ul className="list-disc pl-5 space-y-1 mb-2 last:mb-0">{children}</ul>
|
||||||
|
),
|
||||||
|
ol: ({ children }) => (
|
||||||
|
<ol className="list-decimal pl-5 space-y-1 mb-2 last:mb-0">{children}</ol>
|
||||||
|
),
|
||||||
|
code: ({ children }) => (
|
||||||
|
<code className="rounded bg-blue-100/80 dark:bg-blue-900/40 px-1 py-0.5 text-[0.92em]">
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
),
|
||||||
|
pre: ({ children }) => (
|
||||||
|
<pre className="mb-2 overflow-x-auto rounded-md bg-blue-100/70 dark:bg-blue-900/40 p-2.5 text-xs leading-relaxed">
|
||||||
|
{children}
|
||||||
|
</pre>
|
||||||
|
),
|
||||||
|
blockquote: ({ children }) => (
|
||||||
|
<blockquote className="mb-2 border-l-2 border-blue-300/80 dark:border-blue-500/60 pl-3 italic">
|
||||||
|
{children}
|
||||||
|
</blockquote>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{announcement.content}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
Reference in New Issue
Block a user