/* global React, Button, LetterCard, isLiked, isSaved, toggleLike, toggleSave, UI, ShareModal, ReadingProgress, SensitiveNotice */ const { useState: useStateOther, useRef: useRefOther, useEffect: useEffOther } = React; function LetterDetail({ letter, onBack, backLabel, user }) { // API may return `author` as an object {name, rank_badge}; legacy mock uses a string. const authorName = typeof letter.author === 'object' && letter.author ? letter.author.name : (letter.author_name || letter.author || '某位爱卿'); const initialLiked = letter.i_liked !== undefined ? letter.i_liked : isLiked(letter.id); const initialSaved = letter.i_saved !== undefined ? letter.i_saved : isSaved(letter.id); const initialLikes = letter.likes_count !== undefined ? letter.likes_count : (letter.likes || 0); const [liked, setLiked] = useStateOther(initialLiked); const [saved, setSaved] = useStateOther(initialSaved); const [likeCount, setLikeCount] = useStateOther(initialLikes); const [shareOpen, setShareOpen] = useStateOther(false); const [burst, setBurst] = useStateOther(false); // API returns comments with {is_emperor} (snake); legacy mocks use {isEmperor} — normalise. const normaliseComments = (cs) => (cs || []).map(c => ({ id: c.id, author: c.author, isEmperor: c.is_emperor !== undefined ? !!c.is_emperor : !!c.isEmperor, body: c.body, date: c.date, })); const [comments, setComments] = useStateOther(normaliseComments(letter.comments)); const [newComment, setNewComment] = useStateOther(''); const [posting, setPosting] = useStateOther(false); // double-click guard const [cmtSens, setCmtSens] = useStateOther({ blocked: [], warnings: [] }); // 评论违规词检测 const articleRef = useRefOther(null); const cmtCheckTimer = useRefOther(null); useEffOther(() => { setLiked(letter.i_liked !== undefined ? letter.i_liked : isLiked(letter.id)); setSaved(letter.i_saved !== undefined ? letter.i_saved : isSaved(letter.id)); setLikeCount(letter.likes_count !== undefined ? letter.likes_count : (letter.likes || 0)); setComments(normaliseComments(letter.comments)); }, [letter.id]); // 评论违规词实时检测(防抖 400ms),与写信同一套词库/接口 useEffOther(() => { if (!window.bxApi) return; const text = newComment.trim(); if (!text) { setCmtSens({ blocked: [], warnings: [] }); return; } clearTimeout(cmtCheckTimer.current); cmtCheckTimer.current = setTimeout(() => { window.bxApi.checkContent(text, 'comment') // 评论用宽松档 .then(r => setCmtSens({ blocked: r.blocked || [], warnings: r.warnings || [] })) .catch(() => {}); }, 400); return () => clearTimeout(cmtCheckTimer.current); }, [newComment]); async function onLike() { if (window.bxApi) { try { const r = await window.bxApi.toggleLike(letter.id); setLiked(r.liked); setLikeCount(r.likes_count); if (r.liked) { setBurst(true); setTimeout(() => setBurst(false), 700); } window.showToast && window.showToast({ type: 'success', msg: r.liked ? '已点喜欢 · 朕知你心' : '已取消' }); return; } catch (e) { if (e.status !== 401) window.showToast && window.showToast({ type: 'error', msg: e.message }); return; } } const now = toggleLike(letter.id); setLiked(now); setLikeCount(c => c + (now ? 1 : -1)); if (now) { setBurst(true); setTimeout(() => setBurst(false), 700); } window.showToast && window.showToast({ type: 'success', msg: now ? '已点喜欢 · 朕知你心' : '已取消' }); } async function onSave() { if (window.bxApi) { try { const r = await window.bxApi.toggleSave(letter.id); setSaved(r.saved); window.showToast && window.showToast({ type: 'success', msg: r.saved ? '已收入爱卿的收藏匣' : '已取消收藏' }); return; } catch (e) { if (e.status !== 401) window.showToast && window.showToast({ type: 'error', msg: e.message }); return; } } const now = toggleSave(letter.id); setSaved(now); window.showToast && window.showToast({ type: 'success', msg: now ? '已收入爱卿的收藏匣' : '已取消收藏' }); } async function postComment() { if (posting) return; const text = newComment.trim(); if (!text) return; if (text.length > 30000) { window.showToast && window.showToast({ type: 'error', msg: '评论过长,请酌减' }); return; } if (cmtSens.blocked.length) { window.showToast && window.showToast({ type: 'error', msg: '评论含违规词,请先修改' }); return; } setPosting(true); try { if (window.bxApi) { try { const c = await window.bxApi.postComment(letter.id, text); setComments(cs => [...cs, { id: c.id, author: c.author, isEmperor: !!c.is_emperor, body: c.body, date: c.date }]); setNewComment(''); setCmtSens({ blocked: [], warnings: [] }); window.showToast && window.showToast({ type: 'success', msg: '评论已呈' }); return; } catch (e) { if (e && e.error === 'sensitive_content') { if (e.details) setCmtSens({ blocked: e.details.blocked || [], warnings: e.details.warnings || [] }); window.showToast && window.showToast({ type: 'error', msg: '评论含违规词,请修改后再呈' }); return; } if (e.status !== 401) window.showToast && window.showToast({ type: 'error', msg: e.message }); return; } } // offline fallback const now = new Date(); const stamp = now.getFullYear() + '.' + String(now.getMonth()+1).padStart(2,'0') + '.' + String(now.getDate()).padStart(2,'0') + ' ' + String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0'); setComments(cs => [...cs, { id: 'c' + Date.now(), author: (user && user.name) || '御膳小当家', isEmperor: false, body: text, date: stamp }]); setNewComment(''); window.showToast && window.showToast({ type: 'success', msg: '评论已呈' }); } finally { setPosting(false); } } return ( <>
{letter.category} #{letter.id} {letter.praised && 陛下赞赏过}

{letter.title}

{letter.anonymous ? '某位爱卿' : authorName} · {letter.date}
{(letter.body || '').split('\n').map((p, i) =>

{p}

)}
{Array.isArray(letter.images) && letter.images.length > 0 && (
{letter.images.map((im, i) => ( ))}
)} {letter.reply && ( )}

评论 · {comments.length} 条

爱卿间可互相议论,陛下偶有临幸点评

{comments.length === 0 ? (

尚无爱卿评论,何不由你起个头?

) : (
    {comments.map(c => (
  1. {c.isEmperor ? ( ) : ( )}
    {c.isEmperor ? '陛下' : c.author} {c.isEmperor && EMPEROR} {c.date}

    {c.body}

  2. ))}
)}