import { useCallback, useEffect, useMemo, useState } from 'react'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { notify } from '@/lib/notify'; import { api } from '@/api/client'; import type { PostItem, SitePageSummary } from '@/api/types'; import { pagePath, postPath } from '@/utils/permalink'; import { useForumLimits } from '@/hooks/useForumLimits'; import { ChevronDown, Loader2 } from 'lucide-react'; export interface ArticleLinkConfirm { url: string; text: string; } interface Props { open: boolean; onOpenChange: (open: boolean) => void; initialUrl?: string; initialText?: string; /** 是否正在编辑已有链接 */ editing?: boolean; onConfirm: (payload: ArticleLinkConfirm) => void; onRemove?: () => void; } type SiteHit = { key: string; title: string; url: string; kind: 'post' | 'page'; }; /** 校验可插入的链接地址:外链或站内绝对路径 */ export function isValidLinkHref(url: string): boolean { const u = url.trim(); if (!u) return false; if (u.startsWith('/') && !u.startsWith('//')) return true; if (u.startsWith('#') && u.length > 1) return true; try { const parsed = new URL(u); return parsed.protocol === 'http:' || parsed.protocol === 'https:'; } catch { return false; } } const SEARCH_DEBOUNCE_MS = 300; const POST_PAGE_SIZE = 12; /** 文章/评论编辑器:插入或编辑链接(含站内内容搜索) */ export function ArticleLinkDialog({ open, onOpenChange, initialUrl = '', initialText = '', editing = false, onConfirm, onRemove, }: Props) { const { limits } = useForumLimits(); const [url, setUrl] = useState(''); const [text, setText] = useState(''); const [advancedOpen, setAdvancedOpen] = useState(false); const [query, setQuery] = useState(''); const [debouncedQuery, setDebouncedQuery] = useState(''); const [posts, setPosts] = useState([]); const [pages, setPages] = useState([]); const [loading, setLoading] = useState(false); const [loaded, setLoaded] = useState(false); useEffect(() => { if (!open) return; setUrl(initialUrl || ''); setText(initialText || ''); setAdvancedOpen(false); setQuery(''); setDebouncedQuery(''); setPosts([]); setPages([]); setLoaded(false); }, [open, initialUrl, initialText]); useEffect(() => { if (!open || !advancedOpen) return; const t = window.setTimeout(() => setDebouncedQuery(query.trim()), SEARCH_DEBOUNCE_MS); return () => window.clearTimeout(t); }, [query, open, advancedOpen]); const loadSiteHits = useCallback(async (keyword: string) => { setLoading(true); try { const [postsRes, pagesRes] = await Promise.all([ api.posts({ page: 1, size: POST_PAGE_SIZE, sort: 'new', ...(keyword ? { keyword, title_only: '1' } : {}), }), api.pages(), ]); setPosts(postsRes.posts || []); let nextPages = pagesRes.pages || []; if (keyword) { const q = keyword.toLowerCase(); nextPages = nextPages.filter( p => p.title.toLowerCase().includes(q) || p.slug.toLowerCase().includes(q), ); } setPages(nextPages.slice(0, POST_PAGE_SIZE)); setLoaded(true); } catch (e: unknown) { notify.error(e instanceof Error ? e.message : '加载站内内容失败'); setLoaded(true); } finally { setLoading(false); } }, []); // 仅展开高级选项时再请求站内内容,避免默认打开就打接口 useEffect(() => { if (!open || !advancedOpen) return; void loadSiteHits(debouncedQuery); }, [open, advancedOpen, debouncedQuery, loadSiteHits]); const hits: SiteHit[] = useMemo(() => { const postHits: SiteHit[] = posts.map(p => ({ key: `post-${p.id}`, title: p.title, url: postPath(p.id, limits), kind: 'post', })); const pageHits: SiteHit[] = pages.map(p => ({ key: `page-${p.slug}`, title: p.title, url: pagePath(p.slug, limits), kind: 'page', })); return [...postHits, ...pageHits]; }, [posts, pages, limits]); const pickHit = (hit: SiteHit) => { setUrl(hit.url); // 选站内内容时同步用标题作为链接文字 setText(hit.title); }; const handleConfirm = () => { const nextUrl = url.trim(); if (!nextUrl) { notify.warning('请输入网址'); return; } if (!isValidLinkHref(nextUrl)) { notify.warning('请使用 http(s) 外链或本站以 / 开头的路径'); return; } onConfirm({ url: nextUrl, text: text.trim() || '链接文字', }); onOpenChange(false); }; return ( 插入或编辑链接
setUrl(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleConfirm(); } }} autoFocus />
setText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleConfirm(); } }} />
{advancedOpen ? (

链接到站点中的内容

setQuery(e.target.value)} />

{debouncedQuery ? `搜索「${debouncedQuery}」` : '未指定搜索条件。自动显示最近发布条目。'}

{loading && !loaded ? (
加载中…
) : !hits.length ? (
暂无匹配内容
) : (
    {hits.map(hit => (
  • ))}
)} {loading && loaded ? (
) : null}
) : null}
{editing && onRemove ? ( ) : null}
); }