添加标签功能

This commit is contained in:
2026-01-23 11:33:23 +08:00
parent 5932adcd9a
commit 771db7e59a

View File

@@ -12,7 +12,8 @@ import {
Text, Text,
makeStyles, makeStyles,
shorthands, shorthands,
tokens tokens,
Input
} from '@fluentui/react-components'; } from '@fluentui/react-components';
import { import {
NumberSymbol24Regular, NumberSymbol24Regular,
@@ -60,14 +61,89 @@ const useStyles = makeStyles({
autoSaveText: { autoSaveText: {
color: tokens.colorNeutralForeground4, color: tokens.colorNeutralForeground4,
fontSize: tokens.fontSizeBase200, 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 CreatePost: React.FC = () => {
const styles = useStyles(); const styles = useStyles();
const { isDarkMode } = useLayout(); const { isDarkMode } = useLayout();
const [value, setValue] = useState<string | undefined>(""); const [value, setValue] = useState<string | undefined>("");
const [lastSaved, setLastSaved] = useState<string>(""); const [lastSaved, setLastSaved] = useState<string>("");
// 标签输入相关状态
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(() => { useEffect(() => {
// 模拟自动保存时间显示 // 模拟自动保存时间显示
@@ -88,6 +164,17 @@ const CreatePost: React.FC = () => {
}} }}
commands={getCommands()} commands={getCommands()}
extraCommands={getExtraCommands()} extraCommands={getExtraCommands()}
previewOptions={{
remarkPlugins: [remarkTagPlugin],
components: {
a: ({ node, ...props }) => {
if (props.href && props.href.startsWith('tag:')) {
return <span style={{ color: tokens.colorBrandForeground1 }}>{props.children}</span>;
}
return <a {...props} />;
}
}
}}
/> />
</div> </div>
@@ -99,12 +186,28 @@ const CreatePost: React.FC = () => {
> >
</Button> </Button>
<Button <div className={styles.tagButtonWrapper}>
appearance="subtle" {showTagInput && (
icon={<NumberSymbol24Regular />} <div className={styles.tagInputContainer}>
> <Input
value={tagInputValue}
</Button> onChange={(e, data) => setTagInputValue(data.value)}
placeholder="输入标签..."
onKeyDown={(e) => {
if (e.key === 'Enter') handleTagSubmit();
}}
/>
<Button appearance="primary" size="small" onClick={handleTagSubmit}>OK</Button>
</div>
)}
<Button
appearance="subtle"
icon={<NumberSymbol24Regular />}
onClick={() => setShowTagInput(!showTagInput)}
>
</Button>
</div>
</div> </div>
<div className={styles.footerRight}> <div className={styles.footerRight}>