add approve/reject mod queue functionality, translations

This commit is contained in:
plebeius
2026-01-08 17:36:47 +01:00
parent 404b613b44
commit 6a740a8bb9
36 changed files with 223 additions and 55 deletions
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "إظهار مؤشر أحمر وامض عندما تكون العناصر في الانتظار لأكثر من هذا الوقت",
"no_content": "لا يوجد محتوى",
"view": "عرض",
"excerpt": "مقتطف"
"excerpt": "مقتطف",
"publishing": "نشر",
"approved": "موافق عليه",
"rejected": "مرفوض"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "আইটেমগুলি এই সময়ের চেয়ে বেশি অপেক্ষা করলে একটি ঝলমলে লাল সূচক দেখান",
"no_content": "কোনো বিষয়বস্তু নেই",
"view": "দেখুন",
"excerpt": "উদ্ধৃতি"
"excerpt": "উদ্ধৃতি",
"publishing": "প্রকাশনা",
"approved": "অনুমোদিত",
"rejected": "প্রত্যাখ্যান"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Zobrazit blikající červený indikátor, když položky čekají déle než tento čas",
"no_content": "Žádný obsah",
"view": "Zobrazit",
"excerpt": "Výňatek"
"excerpt": "Výňatek",
"publishing": "Publikování",
"approved": "Schváleno",
"rejected": "Zamítnuto"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Vis en blinkende rød indikator, når elementer venter længere end denne tid",
"no_content": "Intet indhold",
"view": "Vis",
"excerpt": "Uddrag"
"excerpt": "Uddrag",
"publishing": "Udgivelse",
"approved": "Godkendt",
"rejected": "Afvist"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Einen blinkenden roten Indikator anzeigen, wenn Elemente länger als diese Zeit warten",
"no_content": "Kein Inhalt",
"view": "Anzeigen",
"excerpt": "Auszug"
"excerpt": "Auszug",
"publishing": "Veröffentlichung",
"approved": "Genehmigt",
"rejected": "Abgelehnt"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Εμφάνιση αναβοσβήνοντος κόκκινου δείκτη όταν τα στοιχεία περιμένουν περισσότερο από αυτόν τον χρόνο",
"no_content": "Χωρίς περιεχόμενο",
"view": "Προβολή",
"excerpt": "Απόσπασμα"
"excerpt": "Απόσπασμα",
"publishing": "Δημοσίευση",
"approved": "Εγκεκριμένο",
"rejected": "Απορρίφθηκε"
}
+4 -1
View File
@@ -265,5 +265,8 @@
"mod_queue_alert_threshold_tip": "Show a blinking red indicator when items are waiting for longer than this time",
"no_content": "No content",
"view": "View",
"excerpt": "Excerpt"
"excerpt": "Excerpt",
"publishing": "Publishing",
"approved": "Approved",
"rejected": "Rejected"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Mostrar un indicador rojo parpadeante cuando los elementos están esperando más de este tiempo",
"no_content": "Sin contenido",
"view": "Ver",
"excerpt": "Extracto"
"excerpt": "Extracto",
"publishing": "Publicando",
"approved": "Aprobado",
"rejected": "Rechazado"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "نمایش نشانگر قرمز چشمک‌زن زمانی که موارد بیش از این زمان در انتظار هستند",
"no_content": "بدون محتوا",
"view": "مشاهده",
"excerpt": "گزیده"
"excerpt": "گزیده",
"publishing": "در حال انتشار",
"approved": "تایید شده",
"rejected": "رد شده"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Näytä vilkkuva punainen indikaattori, kun kohteet odottavat pidempään kuin tämä aika",
"no_content": "Ei sisältöä",
"view": "Näytä",
"excerpt": "Ote"
"excerpt": "Ote",
"publishing": "Julkaiseminen",
"approved": "Hyväksytty",
"rejected": "Hylätty"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Magpakita ng kumikislap na pulang indicator kapag ang mga item ay naghihintay nang mas mahaba kaysa sa oras na ito",
"no_content": "Walang nilalaman",
"view": "Tingnan",
"excerpt": "Sipi"
"excerpt": "Sipi",
"publishing": "Pag-publish",
"approved": "Naaprubado",
"rejected": "Tinanggihan"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Afficher un indicateur rouge clignotant lorsque les éléments attendent plus longtemps que ce temps",
"no_content": "Aucun contenu",
"view": "Voir",
"excerpt": "Extrait"
"excerpt": "Extrait",
"publishing": "Publication",
"approved": "Approuvé",
"rejected": "Rejeté"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "הצג אינדיקטור אדום מהבהב כאשר פריטים ממתינים יותר מזמן זה",
"no_content": "אין תוכן",
"view": "הצג",
"excerpt": "תקציר"
"excerpt": "תקציר",
"publishing": "מפרסם",
"approved": "אושר",
"rejected": "נדחה"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "जब आइटम इस समय से अधिक समय तक प्रतीक्षा कर रहे हों तो एक चमकता हुआ लाल संकेतक दिखाएं",
"no_content": "कोई सामग्री नहीं",
"view": "देखें",
"excerpt": "अंश"
"excerpt": "अंश",
"publishing": "प्रकाशन",
"approved": "अनुमोदित",
"rejected": "अस्वीकृत"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Villogó piros jelző megjelenítése, amikor az elemek ennél hosszabb ideig várakoznak",
"no_content": "Nincs tartalom",
"view": "Megtekintés",
"excerpt": "Kivonat"
"excerpt": "Kivonat",
"publishing": "Közzététel",
"approved": "Jóváhagyva",
"rejected": "Elutasítva"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Tampilkan indikator merah berkedip ketika item menunggu lebih lama dari waktu ini",
"no_content": "Tidak ada konten",
"view": "Lihat",
"excerpt": "Kutipan"
"excerpt": "Kutipan",
"publishing": "Menerbitkan",
"approved": "Disetujui",
"rejected": "Ditolak"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Mostra un indicatore rosso lampeggiante quando gli elementi attendono più di questo tempo",
"no_content": "Nessun contenuto",
"view": "Visualizza",
"excerpt": "Estratto"
"excerpt": "Estratto",
"publishing": "Pubblicazione",
"approved": "Approvato",
"rejected": "Rifiutato"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "アイテムがこの時間より長く待機している場合、点滅する赤いインジケーターを表示",
"no_content": "コンテンツなし",
"view": "表示",
"excerpt": "抜粋"
"excerpt": "抜粋",
"publishing": "公開中",
"approved": "承認済み",
"rejected": "却下"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "항목이 이 시간보다 오래 대기 중일 때 깜빡이는 빨간색 표시기 표시",
"no_content": "콘텐츠 없음",
"view": "보기",
"excerpt": "요약"
"excerpt": "요약",
"publishing": "게시 중",
"approved": "승인됨",
"rejected": "거부됨"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "जेव्हा आयटम या वेळेपेक्षा जास्त वेळ प्रतीक्षा करत असतात तेव्हा एक चमकणारा लाल सूचक दाखवा",
"no_content": "सामग्री नाही",
"view": "पहा",
"excerpt": "उतारा"
"excerpt": "उतारा",
"publishing": "प्रकाशन करत आहे",
"approved": "मंजूर",
"rejected": "नाकारले"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Toon een knipperende rode indicator wanneer items langer dan deze tijd wachten",
"no_content": "Geen inhoud",
"view": "Bekijken",
"excerpt": "Uittreksel"
"excerpt": "Uittreksel",
"publishing": "Publiceren",
"approved": "Goedgekeurd",
"rejected": "Afgewezen"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Vis en blinkende rød indikator når elementer venter lenger enn denne tiden",
"no_content": "Ingen innhold",
"view": "Vis",
"excerpt": "Utdrag"
"excerpt": "Utdrag",
"publishing": "Publisering",
"approved": "Godkjent",
"rejected": "Avslått"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Pokaż migający czerwony wskaźnik, gdy elementy czekają dłużej niż ten czas",
"no_content": "Brak zawartości",
"view": "Zobacz",
"excerpt": "Fragment"
"excerpt": "Fragment",
"publishing": "Publikowanie",
"approved": "Zatwierdzone",
"rejected": "Odrzucone"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Mostrar um indicador vermelho piscante quando os itens estão aguardando por mais tempo que este tempo",
"no_content": "Sem conteúdo",
"view": "Ver",
"excerpt": "Trecho"
"excerpt": "Trecho",
"publishing": "Publicando",
"approved": "Aprovado",
"rejected": "Rejeitado"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Afișează un indicator roșu intermitent când elementele așteaptă mai mult decât acest timp",
"no_content": "Fără conținut",
"view": "Vizualizează",
"excerpt": "Fragment"
"excerpt": "Fragment",
"publishing": "Publicare",
"approved": "Aprobat",
"rejected": "Respins"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Показывать мигающий красный индикатор, когда элементы ждут дольше этого времени",
"no_content": "Нет содержимого",
"view": "Просмотр",
"excerpt": "Выдержка"
"excerpt": "Выдержка",
"publishing": "Публикация",
"approved": "Одобрено",
"rejected": "Отклонено"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Shfaq një tregues të kuq që blinkon kur elementët po presin më shumë se kjo kohë",
"no_content": "Pa përmbajtje",
"view": "Shiko",
"excerpt": "Fragment"
"excerpt": "Fragment",
"publishing": "Publikimi",
"approved": "Aprovuar",
"rejected": "Refuzuar"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Visa en blinkande röd indikator när objekt väntar längre än denna tid",
"no_content": "Inget innehåll",
"view": "Visa",
"excerpt": "Utdrag"
"excerpt": "Utdrag",
"publishing": "Publicerar",
"approved": "Godkänd",
"rejected": "Avvisad"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "అంశాలు ఈ సమయం కంటే ఎక్కువ సమయం వేచి ఉన్నప్పుడు మెరిసే ఎరుపు సూచికను చూపించు",
"no_content": "కంటెంట్ లేదు",
"view": "చూడు",
"excerpt": "ఎక్సెర్ప్ట్"
"excerpt": "ఎక్సెర్ప్ట్",
"publishing": "ప్రచురించడం",
"approved": "అనుమోదించబడింది",
"rejected": "తిరస్కరించబడింది"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "แสดงตัวบ่งชี้สีแดงกะพริบเมื่อรายการรอเกินเวลานี้",
"no_content": "ไม่มีเนื้อหา",
"view": "ดู",
"excerpt": "ข้อความย่อ"
"excerpt": "ข้อความย่อ",
"publishing": "กำลังเผยแพร่",
"approved": "อนุมัติแล้ว",
"rejected": "ปฏิเสธ"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Öğeler bu süreden daha uzun süre beklediğinde yanıp sönen kırmızı bir gösterge göster",
"no_content": "İçerik yok",
"view": "Görüntüle",
"excerpt": "Alıntı"
"excerpt": "Alıntı",
"publishing": "Yayınlama",
"approved": "Onaylandı",
"rejected": "Reddedildi"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Показувати мигаючий червоний індикатор, коли елементи чекають довше цього часу",
"no_content": "Немає вмісту",
"view": "Перегляд",
"excerpt": "Витяг"
"excerpt": "Витяг",
"publishing": "Публікація",
"approved": "Схвалено",
"rejected": "Відхилено"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "جب آئٹمز اس وقت سے زیادہ انتظار کر رہے ہوں تو ایک ٹمٹمانے والا سرخ اشارہ دکھائیں",
"no_content": "کوئی مواد نہیں",
"view": "دیکھیں",
"excerpt": "اقتباس"
"excerpt": "اقتباس",
"publishing": "اشاعت",
"approved": "منظور شدہ",
"rejected": "مسترد"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "Hiển thị chỉ báo màu đỏ nhấp nháy khi các mục đang chờ lâu hơn thời gian này",
"no_content": "Không có nội dung",
"view": "Xem",
"excerpt": "Trích đoạn"
"excerpt": "Trích đoạn",
"publishing": "Đang xuất bản",
"approved": "Đã phê duyệt",
"rejected": "Đã từ chối"
}
+4 -1
View File
@@ -266,5 +266,8 @@
"mod_queue_alert_threshold_tip": "当项目等待时间超过此时长时显示闪烁的红色指示器",
"no_content": "无内容",
"view": "查看",
"excerpt": "摘要"
"excerpt": "摘要",
"publishing": "发布中",
"approved": "已批准",
"rejected": "已拒绝"
}
+83 -20
View File
@@ -14,6 +14,10 @@ import { useDefaultSubplebbits, MultisubSubplebbit } from '../../hooks/use-defau
import { useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { getHasThumbnail, getCommentMediaInfo } from '../../lib/utils/media-utils';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useChallengesStore from '../../stores/use-challenges-store';
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
const { addChallenge } = useChallengesStore.getState();
interface ModQueueViewProps {
boardIdentifier?: string; // If provided, shows queue for single board
@@ -38,10 +42,13 @@ interface ModQueueRowProps {
showBoardColumn?: boolean;
}
// Track which action was initiated to show appropriate completion message
type ModerationAction = 'approve' | 'reject' | null;
const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) => {
const { t } = useTranslation();
const { alertThresholdHours } = useModQueueStore();
const [isModerating, setIsModerating] = useState(false);
const [initiatedAction, setInitiatedAction] = useState<ModerationAction>(null);
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight } = comment;
@@ -49,43 +56,109 @@ const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) =>
const timeWaiting = Date.now() / 1000 - timestamp;
const isOverThreshold = timeWaiting > alertThresholdHours * 3600;
const { publishCommentModeration: approve } = usePublishCommentModeration({
const {
publishCommentModeration: approve,
state: approveState,
error: approveError,
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress,
commentModeration: { approved: true },
onChallenge: (...args: any) => addChallenge([...args, comment]),
onChallengeVerification: alertChallengeVerificationFailed,
onError: (error: Error) => {
console.error('Approve failed:', error);
},
});
const { publishCommentModeration: reject } = usePublishCommentModeration({
const {
publishCommentModeration: reject,
state: rejectState,
error: rejectError,
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress,
commentModeration: { removed: true },
onChallenge: (...args: any) => addChallenge([...args, comment]),
onChallengeVerification: alertChallengeVerificationFailed,
onError: (error: Error) => {
console.error('Reject failed:', error);
},
});
const handleApprove = async () => {
setInitiatedAction('approve');
try {
setIsModerating(true);
await approve();
} catch (e) {
console.error(e);
} finally {
setIsModerating(false);
}
};
const handleReject = async () => {
setInitiatedAction('reject');
try {
setIsModerating(true);
await reject();
} catch (e) {
console.error(e);
} finally {
setIsModerating(false);
}
};
// Determine the current moderation state based on which action was initiated
const isApproving = initiatedAction === 'approve' && approveState !== 'initializing' && approveState !== 'succeeded' && approveState !== 'failed';
const isRejecting = initiatedAction === 'reject' && rejectState !== 'initializing' && rejectState !== 'succeeded' && rejectState !== 'failed';
const isPublishing = isApproving || isRejecting;
const approveSucceeded = initiatedAction === 'approve' && approveState === 'succeeded';
const rejectSucceeded = initiatedAction === 'reject' && rejectState === 'succeeded';
const approveFailed = initiatedAction === 'approve' && approveState === 'failed';
const rejectFailed = initiatedAction === 'reject' && rejectState === 'failed';
const excerpt = title || content || (getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : t('no_content'));
const postUrl = `/${boardPath}/thread/${threadCid || cid}`;
// Render the status or action buttons
const renderActions = () => {
if (approveSucceeded) {
return <span className={`${styles.button} ${styles.approve}`}>{t('approved')}</span>;
}
if (rejectSucceeded) {
return <span className={`${styles.button} ${styles.reject}`}>{t('rejected')}</span>;
}
if (approveFailed) {
return (
<span className={`${styles.button} ${styles.reject}`}>
{t('failed')}: {approveError?.message}
</span>
);
}
if (rejectFailed) {
return (
<span className={`${styles.button} ${styles.reject}`}>
{t('failed')}: {rejectError?.message}
</span>
);
}
if (isPublishing) {
return <LoadingEllipsis string={t('publishing')} />;
}
return (
<>
<button className={`${styles.button} ${styles.approve}`} onClick={handleApprove} disabled={isPublishing}>
{t('approve')}
</button>
<button className={`${styles.button} ${styles.reject}`} onClick={handleReject} disabled={isPublishing}>
{t('reject')}
</button>
<Link to={postUrl} className={styles.button}>
{t('view')}
</Link>
</>
);
};
return (
<div className={styles.row}>
{showBoardColumn && (
@@ -99,17 +172,7 @@ const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) =>
</Link>
</div>
<div className={`${styles.time} ${isOverThreshold ? styles.alert : ''}`}>{formatDistanceToNow(timestamp * 1000, { addSuffix: false })}</div>
<div className={styles.actions}>
<button className={`${styles.button} ${styles.approve}`} onClick={handleApprove} disabled={isModerating}>
{t('approve')}
</button>
<button className={`${styles.button} ${styles.reject}`} onClick={handleReject} disabled={isModerating}>
{t('reject')}
</button>
<Link to={postUrl} className={styles.button}>
{t('view')}
</Link>
</div>
<div className={styles.actions}>{renderActions()}</div>
</div>
);
};