From 0dd93421c4aedf2af70e9ed5e8cdec36aa38649f Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 29 Jun 2026 14:40:26 +0800 Subject: [PATCH] feat: enhance public task markdown preview --- .../src/pages/PublicScheduledTaskPage.tsx | 164 +++++++++++++++++- 1 file changed, 161 insertions(+), 3 deletions(-) diff --git a/frontend-web/src/pages/PublicScheduledTaskPage.tsx b/frontend-web/src/pages/PublicScheduledTaskPage.tsx index 76ec60ec..79244de9 100644 --- a/frontend-web/src/pages/PublicScheduledTaskPage.tsx +++ b/frontend-web/src/pages/PublicScheduledTaskPage.tsx @@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { + BookOpen, Check, ChevronLeft, ChevronRight, @@ -46,7 +47,9 @@ import { getBackendBaseURL } from "@/core/config"; import { usePublicBackground } from "@/core/embed/background"; import { downloadMarkdownAsDocx } from "@/core/scheduled-tasks/markdown-docx"; import { reasoningPlugins } from "@/core/streamdown"; +import { createMarkdownAnchorResolver } from "@/core/utils/markdown"; import { cn } from "@/lib/utils"; +import "@/components/wiki/wiki.css"; type PublicTask = { task_id: string; @@ -264,6 +267,153 @@ function stripTimestampSuffix(title: string): string { return cleaned.trim() || title; } +function stripMarkdownToText(markdown: string): string { + return markdown + .replace(/```[\s\S]*?```/g, " ") + .replace(/~~~[\s\S]*?~~~/g, " ") + .replace(/!\[[^\]]*\]\([^)]+\)/g, " ") + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") + .replace(/^#{1,6}\s+/gm, "") + .replace(/^[>\-*+\d.)\s]+/gm, "") + .replace(/[`*_~|]/g, "") + .replace(/<[^>]+>/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + +function getMarkdownExcerpt(markdown: string): string { + const text = stripMarkdownToText(markdown); + if (!text) return ""; + return text.length > 180 ? `${text.slice(0, 180)}...` : text; +} + +function countReadableWords(markdown: string): number { + const text = stripMarkdownToText(markdown); + if (!text) return 0; + const cjk = text.match(/[\u4e00-\u9fff]/g)?.length ?? 0; + const words = text.replace(/[\u4e00-\u9fff]/g, " ").match(/[A-Za-z0-9]+/g)?.length ?? 0; + return cjk + words; +} + +function selectorForId(id: string): string { + const css = window.CSS; + if (css?.escape) return `#${css.escape(id)}`; + return `[id="${id.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`; +} + +function KnowledgeMarkdownView({ + content, + title, + subtitle, + className, +}: { + content: string; + title?: string; + subtitle?: string; + className?: string; + isLoading?: boolean; + remarkPlugins?: unknown; + rehypePlugins?: unknown; +}) { + const rootRef = useRef(null); + const anchorResolver = useMemo(() => createMarkdownAnchorResolver(content), [content]); + const headings = useMemo( + () => anchorResolver.headings.filter((item) => item.level >= 1 && item.level <= 3), + [anchorResolver], + ); + const excerpt = useMemo(() => getMarkdownExcerpt(content), [content]); + const sectionCount = headings.length; + const wordCount = useMemo(() => countReadableWords(content), [content]); + + const scrollToHeading = useCallback((id: string) => { + const root = rootRef.current; + const target = + root?.querySelector(selectorForId(id)) ?? document.getElementById(id); + target?.scrollIntoView({ behavior: "smooth", block: "start" }); + }, []); + + return ( +
+
+
+
+ + + 知识梳理 + + {subtitle && {subtitle}} +
+ {title && ( +

+ {title} +

+ )} + {excerpt && ( +

+ {excerpt} +

+ )} +
+ + {sectionCount || 1} 个章节 + + + 约 {wordCount} 字 + +
+
+
+ +
+
+ + +
+ ); +} + const BODY_TAB_VALUE = "__body__"; const HTML_TAB_VALUE = "__html__"; const RUN_LIST_COLLAPSED_WIDTH = 56; @@ -688,9 +838,10 @@ function PublicFullscreenPreviewContent({ preview }: { preview: PublicFullscreen } if (preview.mode === "markdown") { return ( - ) : ( -