From 771db7e59a37bc0120eba9b3c623154c7c750b16 Mon Sep 17 00:00:00 2001 From: Leonxie Date: Fri, 23 Jan 2026 11:33:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=87=E7=AD=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/src/components/CreatePost.tsx | 117 ++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 7 deletions(-) diff --git a/front/src/components/CreatePost.tsx b/front/src/components/CreatePost.tsx index 701dbc4..18df702 100644 --- a/front/src/components/CreatePost.tsx +++ b/front/src/components/CreatePost.tsx @@ -12,7 +12,8 @@ import { Text, makeStyles, shorthands, - tokens + tokens, + Input } from '@fluentui/react-components'; import { NumberSymbol24Regular, @@ -60,14 +61,89 @@ const useStyles = makeStyles({ autoSaveText: { color: tokens.colorNeutralForeground4, fontSize: tokens.fontSizeBase200, + }, + tagInputContainer: { + position: 'absolute', + bottom: '100%', + left: '0', + marginBottom: '8px', + display: 'flex', + gap: '8px', + backgroundColor: tokens.colorNeutralBackground1, + padding: '8px', + borderRadius: tokens.borderRadiusMedium, + boxShadow: tokens.shadow16, + zIndex: 10, + }, + tagButtonWrapper: { + position: 'relative', } }); +// 自定义 remark 插件,用于高亮 #tag +const remarkTagPlugin = () => { + return (tree: any) => { + const transformNode = (node: any, inLink = false) => { + if (node.type === 'link') inLink = true; + + if (node.children) { + node.children = node.children.flatMap((child: any) => { + if (child.type === 'text' && !inLink) { + // Split by tag pattern (# followed by non-whitespace) + const parts = child.value.split(/(#\S+)/g); + return parts.map((part: string) => { + if (part.match(/^#\S+$/)) { + return { + type: 'link', + url: 'tag:' + part, + children: [{ type: 'text', value: part }] + }; + } + if (part === "") return []; + return { type: 'text', value: part }; + }).flat(); + } + return transformNode(child, inLink); + }); + } + return node; + }; + transformNode(tree); + }; +}; + const CreatePost: React.FC = () => { const styles = useStyles(); const { isDarkMode } = useLayout(); const [value, setValue] = useState(""); const [lastSaved, setLastSaved] = useState(""); + + // 标签输入相关状态 + const [showTagInput, setShowTagInput] = useState(false); + const [tagInputValue, setTagInputValue] = useState(""); + + const handleTagSubmit = () => { + if (!tagInputValue.trim()) { + setShowTagInput(false); + return; + } + + // 分割并处理标签 + const tags = tagInputValue.trim().split(/\s+/).map(t => { + // 去除开头的 #(如果有多个也只去除第一个,或者确保开头有且仅有一个 #) + const cleanText = t.replace(/^#+/, ''); + return '#' + cleanText; + }).join(' '); + + // 添加到编辑器内容 + setValue(prev => { + const prefix = prev ? prev + ' ' : ''; + return prefix + tags; + }); + + setTagInputValue(""); + setShowTagInput(false); + }; useEffect(() => { // 模拟自动保存时间显示 @@ -88,6 +164,17 @@ const CreatePost: React.FC = () => { }} commands={getCommands()} extraCommands={getExtraCommands()} + previewOptions={{ + remarkPlugins: [remarkTagPlugin], + components: { + a: ({ node, ...props }) => { + if (props.href && props.href.startsWith('tag:')) { + return {props.children}; + } + return ; + } + } + }} /> @@ -99,12 +186,28 @@ const CreatePost: React.FC = () => { > 提交 - +
+ {showTagInput && ( +
+ setTagInputValue(data.value)} + placeholder="输入标签..." + onKeyDown={(e) => { + if (e.key === 'Enter') handleTagSubmit(); + }} + /> + +
+ )} + +