import type { Editor } from "@tiptap/core"; import { EditorContent, useEditor } from "@tiptap/react"; import { Button, ColorPicker, Select, Tooltip } from "antd"; import { Eraser, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { BannerAnnouncementLinkHint, BannerAnnouncementTitle, bannerAnnouncementBarStyle, } from "@/components/layout/banner-announcement-content"; import { readBannerNoticeSurface, bannerNoticeTypeLabel } from "@/lib/announcements/banner-notice"; import { BannerNoticeEmojiPopover } from "./banner-notice-emoji-picker"; import { BANNER_TITLE_DEFAULT_FONT_SIZE, BANNER_TITLE_DEFAULT_FONT_WEIGHT, BANNER_TITLE_FONT_FAMILIES, BANNER_TITLE_FONT_SIZES, BANNER_TITLE_FONT_WEIGHTS, BANNER_TITLE_MAX_CHARS, bannerTitleCharCount, lowContrastBannerTitleColors, type BannerTitleRun, } from "@/lib/announcements/banner-title"; import { applyBannerTitleStyle, bannerDocToRuns, bannerTitleRunsToDoc, bannerTitleSelectionStyle, clearBannerTitleStyle, createBannerTitleExtensions, type BannerTitleSelectionStyle, type BannerTitleStylePatch, } from "@/lib/announcements/banner-title-rich-text"; const DEFAULT_OPTION = "default"; /** 一次性读出编辑器当前的分段与光标处样式,供受控渲染和回调共用。 */ function readEditorTitle(instance: Editor) { const runs = bannerDocToRuns(instance.getJSON()); return { runs, selection: bannerTitleSelectionStyle(instance) }; } /** * 通知标题编辑区:文字按选区设置字号 / 字重 / 字体 / 颜色。 * 组件只把 title 分段当初始值,切换编辑对象时由调用方通过 key 重建实例。 */ export function BannerTitleEditor({ value, onChange }: { value: BannerTitleRun[]; onChange: (runs: BannerTitleRun[]) => void }) { const [selectionStyle, setSelectionStyle] = useState({}); const [charCount, setCharCount] = useState(() => bannerTitleCharCount(value)); const editor = useEditor({ immediatelyRender: false, extensions: createBannerTitleExtensions("输入首页顶部展示的通知标题"), content: bannerTitleRunsToDoc(value), editorProps: { attributes: { class: "outline-none", "aria-label": "通知标题编辑区" }, handleKeyDown: (_view, event) => { // 通知条标题是单行语义,Enter 不产生新段落。 return event.key === "Enter"; }, }, onUpdate: ({ editor: instance }) => { const { runs, selection } = readEditorTitle(instance); setSelectionStyle(selection); setCharCount(bannerTitleCharCount(runs)); onChange(runs); }, }); const applyStyle = (patch: BannerTitleStylePatch) => { if (!editor) return; editor.view.focus(); applyBannerTitleStyle(editor, patch); const { runs, selection } = readEditorTitle(editor); setSelectionStyle(selection); setCharCount(bannerTitleCharCount(runs)); onChange(runs); }; const tooLong = charCount > BANNER_TITLE_MAX_CHARS; return (
applyStyle({ fontWeight: next === DEFAULT_OPTION ? null : Number(next) })} options={[ { value: DEFAULT_OPTION, label: "默认字重" }, ...BANNER_TITLE_FONT_WEIGHTS.map((item) => ({ value: item.value, label: `${item.label} ${item.value}` })), ]} />