mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(mod-queue): improve compact view and add pending approval features
- Update excerpt format to show both title and content (title: content) - Add Type and Image columns to compact view (desktop and mobile) - Show parent post above replies in feed view - Add approve/reject buttons to post page for pending posts when user is mod - Translate pending approval string across all languages - Reorder columns: No., Excerpt, Submitted, Type, Image, Actions - Right-align Type and Image columns in desktop view
This commit is contained in:
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "عتبة التنبيه",
|
"alert_threshold": "عتبة التنبيه",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "সতর্কতা থ্রেশহোল্ড",
|
"alert_threshold": "সতর্কতা থ্রেশহোল্ড",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Práh upozornění",
|
"alert_threshold": "Práh upozornění",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Alarmtærskel",
|
"alert_threshold": "Alarmtærskel",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Alarmschwellenwert",
|
"alert_threshold": "Alarmschwellenwert",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Typ",
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nein",
|
||||||
|
"pending_mod_approval": "Ausstehende Moderatorgenehmigung, für Benutzer nicht sichtbar"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Όριο ειδοποίησης",
|
"alert_threshold": "Όριο ειδοποίησης",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"approved": "Approved",
|
"approved": "Approved",
|
||||||
"rejected": "Rejected",
|
"rejected": "Rejected",
|
||||||
"submitted": "Submitted",
|
"submitted": "Submitted",
|
||||||
"downloading_board": "Downloading board"
|
"downloading_board": "Downloading board",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Umbral de alerta",
|
"alert_threshold": "Umbral de alerta",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Tipo",
|
||||||
|
"yes": "Sí",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pendiente de aprobación del moderador, no visible para los usuarios"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "آستانه هشدار",
|
"alert_threshold": "آستانه هشدار",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Hälytyskynnys",
|
"alert_threshold": "Hälytyskynnys",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Alert threshold",
|
"alert_threshold": "Alert threshold",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Seuil d'alerte",
|
"alert_threshold": "Seuil d'alerte",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Oui",
|
||||||
|
"no": "Non",
|
||||||
|
"pending_mod_approval": "En attente d'approbation du modérateur, non visible pour les utilisateurs"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "סף התרעה",
|
"alert_threshold": "סף התרעה",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "सतर्कता सीमा",
|
"alert_threshold": "सतर्कता सीमा",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Riasztási küszöb",
|
"alert_threshold": "Riasztási küszöb",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Ambang peringatan",
|
"alert_threshold": "Ambang peringatan",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Soglia di allerta",
|
"alert_threshold": "Soglia di allerta",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Tipo",
|
||||||
|
"yes": "Sì",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "In attesa di approvazione del moderatore, non visibile agli utenti"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "アラート閾値",
|
"alert_threshold": "アラート閾値",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "タイプ",
|
||||||
|
"yes": "はい",
|
||||||
|
"no": "いいえ",
|
||||||
|
"pending_mod_approval": "モデレーターの承認待ち、ユーザーには表示されません"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "알림 임계값",
|
"alert_threshold": "알림 임계값",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "सतर्कता मर्यादा",
|
"alert_threshold": "सतर्कता मर्यादा",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Drempelwaarde voor waarschuwing",
|
"alert_threshold": "Drempelwaarde voor waarschuwing",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Terskel for varsel",
|
"alert_threshold": "Terskel for varsel",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Próg alertu",
|
"alert_threshold": "Próg alertu",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Limite de alerta",
|
"alert_threshold": "Limite de alerta",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Tipo",
|
||||||
|
"yes": "Sim",
|
||||||
|
"no": "Não",
|
||||||
|
"pending_mod_approval": "Aguardando aprovação do moderador, não visível para os usuários"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Prag de alertă",
|
"alert_threshold": "Prag de alertă",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Порог предупреждения",
|
"alert_threshold": "Порог предупреждения",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Тип",
|
||||||
|
"yes": "Да",
|
||||||
|
"no": "Нет",
|
||||||
|
"pending_mod_approval": "Ожидает одобрения модератора, не видно пользователям"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Pragu i alarmit",
|
"alert_threshold": "Pragu i alarmit",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Tröskelvärde för varning",
|
"alert_threshold": "Tröskelvärde för varning",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "హెచ్చరిక థ్రెషోల్డ్",
|
"alert_threshold": "హెచ్చరిక థ్రెషోల్డ్",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "เกณฑ์การแจ้งเตือน",
|
"alert_threshold": "เกณฑ์การแจ้งเตือน",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Uyarı eşiği",
|
"alert_threshold": "Uyarı eşiği",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Поріг попередження",
|
"alert_threshold": "Поріг попередження",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "انتباہ کی حد",
|
"alert_threshold": "انتباہ کی حد",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "Ngưỡng cảnh báo",
|
"alert_threshold": "Ngưỡng cảnh báo",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "Type",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,9 @@
|
|||||||
"alert_threshold": "警报阈值",
|
"alert_threshold": "警报阈值",
|
||||||
"modQueue.viewLabel": "View",
|
"modQueue.viewLabel": "View",
|
||||||
"modQueue.compact": "Compact",
|
"modQueue.compact": "Compact",
|
||||||
"modQueue.feed": "Feed"
|
"modQueue.feed": "Feed",
|
||||||
|
"type": "类型",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否",
|
||||||
|
"pending_mod_approval": "等待版主批准,用户不可见"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
{pendingApproval && (
|
{pendingApproval && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
<span className={styles.pendingApproval}>(Pending mod approval, not visible to users)</span>
|
<span className={styles.pendingApproval}>({t('pending_mod_approval')})</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{((!isInPostView && content?.length > 1000 && !showFullComment) || (isInPostView && content?.length > 2000 && !showFullComment)) && (
|
{((!isInPostView && content?.length > 1000 && !showFullComment) || (isInPostView && content?.length > 2000 && !showFullComment)) && (
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
||||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||||
import { Comment, useAuthorAvatar, useEditedComment, useReplies } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAuthorAvatar, useEditedComment, useReplies, useAccount } from '@plebbit/plebbit-react-hooks';
|
||||||
import Plebbit from '@plebbit/plebbit-js';
|
import Plebbit from '@plebbit/plebbit-js';
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||||
@@ -35,6 +35,11 @@ import _ from 'lodash';
|
|||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||||
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
||||||
|
import useChallengesStore from '../../stores/use-challenges-store';
|
||||||
|
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||||
|
import { usePublishCommentModeration } from '@plebbit/plebbit-react-hooks';
|
||||||
|
|
||||||
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
// Store scroll position for replies virtuoso across navigations
|
// Store scroll position for replies virtuoso across navigations
|
||||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||||
@@ -89,6 +94,97 @@ const PostInfo = ({
|
|||||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||||
const isInModQueueView = isModQueueView(location.pathname);
|
const isInModQueueView = isModQueueView(location.pathname);
|
||||||
const { getAlertThresholdSeconds } = useModQueueStore();
|
const { getAlertThresholdSeconds } = useModQueueStore();
|
||||||
|
const account = useAccount();
|
||||||
|
const accountAddress = account?.author?.address;
|
||||||
|
|
||||||
|
// Check if user is mod of this board
|
||||||
|
const accountRole = roles?.[accountAddress]?.role;
|
||||||
|
const isAccountMod = accountRole === 'admin' || accountRole === 'owner' || accountRole === 'moderator';
|
||||||
|
|
||||||
|
// Check if post is pending approval and user is mod (for post page view)
|
||||||
|
const pendingApproval = post?.pendingApproval;
|
||||||
|
const shouldShowPendingApprovalButtons = isInPostPageView && !isInModQueueView && pendingApproval && isAccountMod && subplebbitAddress;
|
||||||
|
|
||||||
|
// Moderation actions for pending approval posts
|
||||||
|
const {
|
||||||
|
publishCommentModeration: approvePending,
|
||||||
|
state: approvePendingState,
|
||||||
|
error: approvePendingError,
|
||||||
|
} = usePublishCommentModeration({
|
||||||
|
commentCid: cid,
|
||||||
|
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
|
||||||
|
commentModeration: { approved: true },
|
||||||
|
onChallenge: async (...args: any) => {
|
||||||
|
addChallenge([...args, post]);
|
||||||
|
},
|
||||||
|
onChallengeVerification: async (challengeVerification, comment) => {
|
||||||
|
alertChallengeVerificationFailed(challengeVerification, comment);
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error('Approve failed:', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
publishCommentModeration: rejectPending,
|
||||||
|
state: rejectPendingState,
|
||||||
|
error: rejectPendingError,
|
||||||
|
} = usePublishCommentModeration({
|
||||||
|
commentCid: cid,
|
||||||
|
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
|
||||||
|
commentModeration: { removed: true },
|
||||||
|
onChallenge: async (...args: any) => {
|
||||||
|
addChallenge([...args, post]);
|
||||||
|
},
|
||||||
|
onChallengeVerification: async (challengeVerification, comment) => {
|
||||||
|
alertChallengeVerificationFailed(challengeVerification, comment);
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error('Reject failed:', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [initiatedPendingAction, setInitiatedPendingAction] = useState<'approve' | 'reject' | null>(null);
|
||||||
|
|
||||||
|
const handlePendingApprove = useCallback(async () => {
|
||||||
|
const confirm = window.confirm(t('double_confirm'));
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInitiatedPendingAction('approve');
|
||||||
|
try {
|
||||||
|
await approvePending();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, [approvePending, t]);
|
||||||
|
|
||||||
|
const handlePendingReject = useCallback(async () => {
|
||||||
|
const confirm = window.confirm(t('double_confirm'));
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInitiatedPendingAction('reject');
|
||||||
|
try {
|
||||||
|
await rejectPending();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, [rejectPending, t]);
|
||||||
|
|
||||||
|
const isApprovingPending =
|
||||||
|
initiatedPendingAction === 'approve' && approvePendingState !== 'initializing' && approvePendingState !== 'succeeded' && approvePendingState !== 'failed';
|
||||||
|
const isRejectingPending =
|
||||||
|
initiatedPendingAction === 'reject' && rejectPendingState !== 'initializing' && rejectPendingState !== 'succeeded' && rejectPendingState !== 'failed';
|
||||||
|
const isPublishingPending = isApprovingPending || isRejectingPending;
|
||||||
|
|
||||||
|
const approvePendingSucceeded = initiatedPendingAction === 'approve' && approvePendingState === 'succeeded';
|
||||||
|
const rejectPendingSucceeded = initiatedPendingAction === 'reject' && rejectPendingState === 'succeeded';
|
||||||
|
const approvePendingFailed = initiatedPendingAction === 'approve' && approvePendingState === 'failed';
|
||||||
|
const rejectPendingFailed = initiatedPendingAction === 'reject' && rejectPendingState === 'failed';
|
||||||
|
|
||||||
|
const pendingStatus = approvePendingSucceeded ? 'approved' : rejectPendingSucceeded ? 'rejected' : approvePendingFailed || rejectPendingFailed ? 'failed' : null;
|
||||||
|
const pendingErrorMessage = approvePendingFailed ? approvePendingError?.message : rejectPendingFailed ? rejectPendingError?.message : undefined;
|
||||||
|
|
||||||
// Check if post is awaiting approval and over threshold (for mod queue view)
|
// Check if post is awaiting approval and over threshold (for mod queue view)
|
||||||
const approved = post?.approved;
|
const approved = post?.approved;
|
||||||
@@ -277,6 +373,39 @@ const PostInfo = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{shouldShowPendingApprovalButtons && (
|
||||||
|
<span className={styles.modQueueActions}>
|
||||||
|
{pendingStatus === 'approved' ? (
|
||||||
|
<span className={styles.modQueueStatusApproved}>{t('approved')}</span>
|
||||||
|
) : pendingStatus === 'rejected' ? (
|
||||||
|
<span className={styles.modQueueStatusRejected}>{t('rejected')}</span>
|
||||||
|
) : pendingStatus === 'failed' ? (
|
||||||
|
<span className={styles.modQueueStatusRejected}>
|
||||||
|
{t('failed')}
|
||||||
|
{pendingErrorMessage ? `: ${pendingErrorMessage}` : ''}
|
||||||
|
</span>
|
||||||
|
) : isPublishingPending ? (
|
||||||
|
<LoadingEllipsis string={t('publishing')} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className={styles.modQueueButtonWrapper}>
|
||||||
|
[
|
||||||
|
<button className={styles.modQueueActionButton} onClick={handlePendingApprove} disabled={isPublishingPending}>
|
||||||
|
{t('approve')}
|
||||||
|
</button>
|
||||||
|
]
|
||||||
|
</span>
|
||||||
|
<span className={styles.modQueueButtonWrapper}>
|
||||||
|
[
|
||||||
|
<button className={styles.modQueueActionButton} onClick={handlePendingReject} disabled={isPublishingPending}>
|
||||||
|
{t('reject')}
|
||||||
|
</button>
|
||||||
|
]
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
{!(removed || deleted) && !isModQueue && <PostMenuDesktop postMenu={postMenuProps} />}
|
{!(removed || deleted) && !isModQueue && <PostMenuDesktop postMenu={postMenuProps} />}
|
||||||
{cid &&
|
{cid &&
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
||||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||||
import { Comment, useAuthorAvatar, useEditedComment, useReplies } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAuthorAvatar, useEditedComment, useReplies, useAccount, usePublishCommentModeration } from '@plebbit/plebbit-react-hooks';
|
||||||
import Plebbit from '@plebbit/plebbit-js';
|
import Plebbit from '@plebbit/plebbit-js';
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
@@ -30,6 +30,10 @@ import { PostProps } from '../../views/post/post';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||||
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
||||||
|
import useChallengesStore from '../../stores/use-challenges-store';
|
||||||
|
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||||
|
|
||||||
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
// Store scroll position for replies virtuoso across navigations
|
// Store scroll position for replies virtuoso across navigations
|
||||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||||
@@ -55,6 +59,97 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
|||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||||
const isInModQueueView = isModQueueView(location.pathname);
|
const isInModQueueView = isModQueueView(location.pathname);
|
||||||
const { getAlertThresholdSeconds } = useModQueueStore();
|
const { getAlertThresholdSeconds } = useModQueueStore();
|
||||||
|
const account = useAccount();
|
||||||
|
const accountAddress = account?.author?.address;
|
||||||
|
|
||||||
|
// Check if user is mod of this board
|
||||||
|
const accountRole = roles?.[accountAddress]?.role;
|
||||||
|
const isAccountMod = accountRole === 'admin' || accountRole === 'owner' || accountRole === 'moderator';
|
||||||
|
|
||||||
|
// Check if post is pending approval and user is mod (for post page view)
|
||||||
|
const pendingApproval = post?.pendingApproval;
|
||||||
|
const shouldShowPendingApprovalButtons = isInPostPageView && !isInModQueueView && pendingApproval && isAccountMod && subplebbitAddress;
|
||||||
|
|
||||||
|
// Moderation actions for pending approval posts
|
||||||
|
const {
|
||||||
|
publishCommentModeration: approvePending,
|
||||||
|
state: approvePendingState,
|
||||||
|
error: approvePendingError,
|
||||||
|
} = usePublishCommentModeration({
|
||||||
|
commentCid: cid,
|
||||||
|
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
|
||||||
|
commentModeration: { approved: true },
|
||||||
|
onChallenge: async (...args: any) => {
|
||||||
|
addChallenge([...args, post]);
|
||||||
|
},
|
||||||
|
onChallengeVerification: async (challengeVerification, comment) => {
|
||||||
|
alertChallengeVerificationFailed(challengeVerification, comment);
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error('Approve failed:', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
publishCommentModeration: rejectPending,
|
||||||
|
state: rejectPendingState,
|
||||||
|
error: rejectPendingError,
|
||||||
|
} = usePublishCommentModeration({
|
||||||
|
commentCid: cid,
|
||||||
|
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
|
||||||
|
commentModeration: { removed: true },
|
||||||
|
onChallenge: async (...args: any) => {
|
||||||
|
addChallenge([...args, post]);
|
||||||
|
},
|
||||||
|
onChallengeVerification: async (challengeVerification, comment) => {
|
||||||
|
alertChallengeVerificationFailed(challengeVerification, comment);
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error('Reject failed:', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [initiatedPendingAction, setInitiatedPendingAction] = useState<'approve' | 'reject' | null>(null);
|
||||||
|
|
||||||
|
const handlePendingApprove = useCallback(async () => {
|
||||||
|
const confirm = window.confirm(t('double_confirm'));
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInitiatedPendingAction('approve');
|
||||||
|
try {
|
||||||
|
await approvePending();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, [approvePending, t]);
|
||||||
|
|
||||||
|
const handlePendingReject = useCallback(async () => {
|
||||||
|
const confirm = window.confirm(t('double_confirm'));
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInitiatedPendingAction('reject');
|
||||||
|
try {
|
||||||
|
await rejectPending();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, [rejectPending, t]);
|
||||||
|
|
||||||
|
const isApprovingPending =
|
||||||
|
initiatedPendingAction === 'approve' && approvePendingState !== 'initializing' && approvePendingState !== 'succeeded' && approvePendingState !== 'failed';
|
||||||
|
const isRejectingPending =
|
||||||
|
initiatedPendingAction === 'reject' && rejectPendingState !== 'initializing' && rejectPendingState !== 'succeeded' && rejectPendingState !== 'failed';
|
||||||
|
const isPublishingPending = isApprovingPending || isRejectingPending;
|
||||||
|
|
||||||
|
const approvePendingSucceeded = initiatedPendingAction === 'approve' && approvePendingState === 'succeeded';
|
||||||
|
const rejectPendingSucceeded = initiatedPendingAction === 'reject' && rejectPendingState === 'succeeded';
|
||||||
|
const approvePendingFailed = initiatedPendingAction === 'approve' && approvePendingState === 'failed';
|
||||||
|
const rejectPendingFailed = initiatedPendingAction === 'reject' && rejectPendingState === 'failed';
|
||||||
|
|
||||||
|
const pendingStatus = approvePendingSucceeded ? 'approved' : rejectPendingSucceeded ? 'rejected' : approvePendingFailed || rejectPendingFailed ? 'failed' : null;
|
||||||
|
const pendingErrorMessage = approvePendingFailed ? approvePendingError?.message : rejectPendingFailed ? rejectPendingError?.message : undefined;
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
@@ -218,6 +313,31 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{shouldShowPendingApprovalButtons && (
|
||||||
|
<div className={styles.modQueueActions}>
|
||||||
|
{pendingStatus === 'approved' ? (
|
||||||
|
<span className={styles.modQueueStatusApproved}>{t('approved')}</span>
|
||||||
|
) : pendingStatus === 'rejected' ? (
|
||||||
|
<span className={styles.modQueueStatusRejected}>{t('rejected')}</span>
|
||||||
|
) : pendingStatus === 'failed' ? (
|
||||||
|
<span className={styles.modQueueStatusRejected}>
|
||||||
|
{t('failed')}
|
||||||
|
{pendingErrorMessage ? `: ${pendingErrorMessage}` : ''}
|
||||||
|
</span>
|
||||||
|
) : isPublishingPending ? (
|
||||||
|
<LoadingEllipsis string={t('publishing')} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button className={`button ${styles.approveButton}`} onClick={handlePendingApprove} disabled={isPublishingPending}>
|
||||||
|
{t('approve')}
|
||||||
|
</button>
|
||||||
|
<button className={`button ${styles.rejectButton}`} onClick={handlePendingReject} disabled={isPublishingPending}>
|
||||||
|
{t('reject')}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
.numberHeader,
|
.numberHeader,
|
||||||
.excerptHeader,
|
.excerptHeader,
|
||||||
.timeHeader,
|
.timeHeader,
|
||||||
|
.typeHeader,
|
||||||
|
.imageHeader,
|
||||||
.actionsHeader {
|
.actionsHeader {
|
||||||
background: var(--mod-queue-header-background-color);
|
background: var(--mod-queue-header-background-color);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@@ -80,6 +82,18 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.typeHeader {
|
||||||
|
width: 70px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageHeader {
|
||||||
|
width: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.timeHeader {
|
.timeHeader {
|
||||||
width: 210px;
|
width: 210px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -124,6 +138,8 @@
|
|||||||
.number,
|
.number,
|
||||||
.excerpt,
|
.excerpt,
|
||||||
.time,
|
.time,
|
||||||
|
.type,
|
||||||
|
.image,
|
||||||
.actions {
|
.actions {
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
}
|
}
|
||||||
@@ -135,6 +151,8 @@
|
|||||||
:global(body.yotsuba) .rowOdd .number,
|
:global(body.yotsuba) .rowOdd .number,
|
||||||
:global(body.yotsuba) .rowOdd .excerpt,
|
:global(body.yotsuba) .rowOdd .excerpt,
|
||||||
:global(body.yotsuba) .rowOdd .time,
|
:global(body.yotsuba) .rowOdd .time,
|
||||||
|
:global(body.yotsuba) .rowOdd .type,
|
||||||
|
:global(body.yotsuba) .rowOdd .image,
|
||||||
:global(body.yotsuba) .rowOdd .actions {
|
:global(body.yotsuba) .rowOdd .actions {
|
||||||
background-color: #ede2d4;
|
background-color: #ede2d4;
|
||||||
}
|
}
|
||||||
@@ -142,6 +160,8 @@
|
|||||||
:global(body.yotsuba-b) .rowOdd .number,
|
:global(body.yotsuba-b) .rowOdd .number,
|
||||||
:global(body.yotsuba-b) .rowOdd .excerpt,
|
:global(body.yotsuba-b) .rowOdd .excerpt,
|
||||||
:global(body.yotsuba-b) .rowOdd .time,
|
:global(body.yotsuba-b) .rowOdd .time,
|
||||||
|
:global(body.yotsuba-b) .rowOdd .type,
|
||||||
|
:global(body.yotsuba-b) .rowOdd .image,
|
||||||
:global(body.yotsuba-b) .rowOdd .actions {
|
:global(body.yotsuba-b) .rowOdd .actions {
|
||||||
background-color: #e0e5f6;
|
background-color: #e0e5f6;
|
||||||
}
|
}
|
||||||
@@ -149,6 +169,8 @@
|
|||||||
:global(body.futaba) .rowOdd .number,
|
:global(body.futaba) .rowOdd .number,
|
||||||
:global(body.futaba) .rowOdd .excerpt,
|
:global(body.futaba) .rowOdd .excerpt,
|
||||||
:global(body.futaba) .rowOdd .time,
|
:global(body.futaba) .rowOdd .time,
|
||||||
|
:global(body.futaba) .rowOdd .type,
|
||||||
|
:global(body.futaba) .rowOdd .image,
|
||||||
:global(body.futaba) .rowOdd .actions {
|
:global(body.futaba) .rowOdd .actions {
|
||||||
background-color: #ede2d4;
|
background-color: #ede2d4;
|
||||||
}
|
}
|
||||||
@@ -156,6 +178,8 @@
|
|||||||
:global(body.burichan) .rowOdd .number,
|
:global(body.burichan) .rowOdd .number,
|
||||||
:global(body.burichan) .rowOdd .excerpt,
|
:global(body.burichan) .rowOdd .excerpt,
|
||||||
:global(body.burichan) .rowOdd .time,
|
:global(body.burichan) .rowOdd .time,
|
||||||
|
:global(body.burichan) .rowOdd .type,
|
||||||
|
:global(body.burichan) .rowOdd .image,
|
||||||
:global(body.burichan) .rowOdd .actions {
|
:global(body.burichan) .rowOdd .actions {
|
||||||
background-color: #e0e5f6;
|
background-color: #e0e5f6;
|
||||||
}
|
}
|
||||||
@@ -163,6 +187,8 @@
|
|||||||
:global(body.tomorrow) .rowOdd .number,
|
:global(body.tomorrow) .rowOdd .number,
|
||||||
:global(body.tomorrow) .rowOdd .excerpt,
|
:global(body.tomorrow) .rowOdd .excerpt,
|
||||||
:global(body.tomorrow) .rowOdd .time,
|
:global(body.tomorrow) .rowOdd .time,
|
||||||
|
:global(body.tomorrow) .rowOdd .type,
|
||||||
|
:global(body.tomorrow) .rowOdd .image,
|
||||||
:global(body.tomorrow) .rowOdd .actions {
|
:global(body.tomorrow) .rowOdd .actions {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
@@ -170,6 +196,8 @@
|
|||||||
:global(body.photon) .rowOdd .number,
|
:global(body.photon) .rowOdd .number,
|
||||||
:global(body.photon) .rowOdd .excerpt,
|
:global(body.photon) .rowOdd .excerpt,
|
||||||
:global(body.photon) .rowOdd .time,
|
:global(body.photon) .rowOdd .time,
|
||||||
|
:global(body.photon) .rowOdd .type,
|
||||||
|
:global(body.photon) .rowOdd .image,
|
||||||
:global(body.photon) .rowOdd .actions {
|
:global(body.photon) .rowOdd .actions {
|
||||||
background-color: #888;
|
background-color: #888;
|
||||||
}
|
}
|
||||||
@@ -187,6 +215,18 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
width: 70px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.excerpt a {
|
.excerpt a {
|
||||||
text-decoration: var(--post-link-text-decoration);
|
text-decoration: var(--post-link-text-decoration);
|
||||||
color: var(--post-link-text-color);
|
color: var(--post-link-text-color);
|
||||||
@@ -397,26 +437,38 @@ span.reject:hover {
|
|||||||
:global(body.yotsuba) .rowOdd .number,
|
:global(body.yotsuba) .rowOdd .number,
|
||||||
:global(body.yotsuba) .rowOdd .excerpt,
|
:global(body.yotsuba) .rowOdd .excerpt,
|
||||||
:global(body.yotsuba) .rowOdd .time,
|
:global(body.yotsuba) .rowOdd .time,
|
||||||
|
:global(body.yotsuba) .rowOdd .type,
|
||||||
|
:global(body.yotsuba) .rowOdd .image,
|
||||||
:global(body.yotsuba) .rowOdd .actions,
|
:global(body.yotsuba) .rowOdd .actions,
|
||||||
:global(body.yotsuba-b) .rowOdd .number,
|
:global(body.yotsuba-b) .rowOdd .number,
|
||||||
:global(body.yotsuba-b) .rowOdd .excerpt,
|
:global(body.yotsuba-b) .rowOdd .excerpt,
|
||||||
:global(body.yotsuba-b) .rowOdd .time,
|
:global(body.yotsuba-b) .rowOdd .time,
|
||||||
|
:global(body.yotsuba-b) .rowOdd .type,
|
||||||
|
:global(body.yotsuba-b) .rowOdd .image,
|
||||||
:global(body.yotsuba-b) .rowOdd .actions,
|
:global(body.yotsuba-b) .rowOdd .actions,
|
||||||
:global(body.futaba) .rowOdd .number,
|
:global(body.futaba) .rowOdd .number,
|
||||||
:global(body.futaba) .rowOdd .excerpt,
|
:global(body.futaba) .rowOdd .excerpt,
|
||||||
:global(body.futaba) .rowOdd .time,
|
:global(body.futaba) .rowOdd .time,
|
||||||
|
:global(body.futaba) .rowOdd .type,
|
||||||
|
:global(body.futaba) .rowOdd .image,
|
||||||
:global(body.futaba) .rowOdd .actions,
|
:global(body.futaba) .rowOdd .actions,
|
||||||
:global(body.burichan) .rowOdd .number,
|
:global(body.burichan) .rowOdd .number,
|
||||||
:global(body.burichan) .rowOdd .excerpt,
|
:global(body.burichan) .rowOdd .excerpt,
|
||||||
:global(body.burichan) .rowOdd .time,
|
:global(body.burichan) .rowOdd .time,
|
||||||
|
:global(body.burichan) .rowOdd .type,
|
||||||
|
:global(body.burichan) .rowOdd .image,
|
||||||
:global(body.burichan) .rowOdd .actions,
|
:global(body.burichan) .rowOdd .actions,
|
||||||
:global(body.tomorrow) .rowOdd .number,
|
:global(body.tomorrow) .rowOdd .number,
|
||||||
:global(body.tomorrow) .rowOdd .excerpt,
|
:global(body.tomorrow) .rowOdd .excerpt,
|
||||||
:global(body.tomorrow) .rowOdd .time,
|
:global(body.tomorrow) .rowOdd .time,
|
||||||
|
:global(body.tomorrow) .rowOdd .type,
|
||||||
|
:global(body.tomorrow) .rowOdd .image,
|
||||||
:global(body.tomorrow) .rowOdd .actions,
|
:global(body.tomorrow) .rowOdd .actions,
|
||||||
:global(body.photon) .rowOdd .number,
|
:global(body.photon) .rowOdd .number,
|
||||||
:global(body.photon) .rowOdd .excerpt,
|
:global(body.photon) .rowOdd .excerpt,
|
||||||
:global(body.photon) .rowOdd .time,
|
:global(body.photon) .rowOdd .time,
|
||||||
|
:global(body.photon) .rowOdd .type,
|
||||||
|
:global(body.photon) .rowOdd .image,
|
||||||
:global(body.photon) .rowOdd .actions {
|
:global(body.photon) .rowOdd .actions {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import { useFeed, Comment, usePublishCommentModeration, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { useFeed, Comment, usePublishCommentModeration, useEditedComment, useSubplebbit, useComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts';
|
import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
@@ -22,6 +22,7 @@ import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-util
|
|||||||
import Tooltip from '../../components/tooltip';
|
import Tooltip from '../../components/tooltip';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
import { Post } from '../post/post';
|
import { Post } from '../post/post';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const { addChallenge } = useChallengesStore.getState();
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
@@ -162,7 +163,8 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => {
|
|||||||
const { editedComment } = useEditedComment({ comment });
|
const { editedComment } = useEditedComment({ comment });
|
||||||
const displayComment = editedComment || comment;
|
const displayComment = editedComment || comment;
|
||||||
|
|
||||||
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number } = displayComment;
|
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number, parentCid } =
|
||||||
|
displayComment;
|
||||||
|
|
||||||
// Check if already moderated (from previous session or API update)
|
// Check if already moderated (from previous session or API update)
|
||||||
// Note: `approved` and `removed` are direct fields on the comment from CommentUpdate,
|
// Note: `approved` and `removed` are direct fields on the comment from CommentUpdate,
|
||||||
@@ -183,11 +185,15 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => {
|
|||||||
const hasTitle = title && title.trim().length > 0;
|
const hasTitle = title && title.trim().length > 0;
|
||||||
const hasContent = content && content.trim().length > 0;
|
const hasContent = content && content.trim().length > 0;
|
||||||
const hasLink = link && link.length > 0;
|
const hasLink = link && link.length > 0;
|
||||||
|
const isReply = !!parentCid;
|
||||||
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
const rawExcerpt =
|
const rawExcerpt =
|
||||||
|
(hasTitle && hasContent ? `${title}: ${content}` : null) ||
|
||||||
(hasTitle ? title : null) ||
|
(hasTitle ? title : null) ||
|
||||||
(hasContent ? content : null) ||
|
(hasContent ? content : null) ||
|
||||||
(hasLink ? link : null) ||
|
(hasLink ? link : null) ||
|
||||||
(getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : null) ||
|
(hasThumbnail ? t('image') : null) ||
|
||||||
t('no_content');
|
t('no_content');
|
||||||
// Only truncate excerpt on desktop, allow wrapping on mobile
|
// Only truncate excerpt on desktop, allow wrapping on mobile
|
||||||
const excerpt = !isMobile && rawExcerpt.length > 101 ? rawExcerpt.slice(0, 98) + '...' : rawExcerpt;
|
const excerpt = !isMobile && rawExcerpt.length > 101 ? rawExcerpt.slice(0, 98) + '...' : rawExcerpt;
|
||||||
@@ -268,6 +274,8 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => {
|
|||||||
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />
|
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.type}>{isReply ? _.capitalize(t('reply')) : _.capitalize(t('post'))}</div>
|
||||||
|
<div className={styles.image}>{hasThumbnail ? t('yes') : t('no')}</div>
|
||||||
<div className={styles.actions}>{renderActions()}</div>
|
<div className={styles.actions}>{renderActions()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -284,7 +292,8 @@ const ModQueueCard = ({ comment }: ModQueueCardProps) => {
|
|||||||
const { editedComment } = useEditedComment({ comment });
|
const { editedComment } = useEditedComment({ comment });
|
||||||
const displayComment = editedComment || comment;
|
const displayComment = editedComment || comment;
|
||||||
|
|
||||||
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number } = displayComment;
|
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number, parentCid } =
|
||||||
|
displayComment;
|
||||||
|
|
||||||
const alreadyApproved = approved === true;
|
const alreadyApproved = approved === true;
|
||||||
const alreadyRejected = removed === true;
|
const alreadyRejected = removed === true;
|
||||||
@@ -300,11 +309,15 @@ const ModQueueCard = ({ comment }: ModQueueCardProps) => {
|
|||||||
const hasTitle = title && title.trim().length > 0;
|
const hasTitle = title && title.trim().length > 0;
|
||||||
const hasContent = content && content.trim().length > 0;
|
const hasContent = content && content.trim().length > 0;
|
||||||
const hasLink = link && link.length > 0;
|
const hasLink = link && link.length > 0;
|
||||||
|
const isReply = !!parentCid;
|
||||||
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
const rawExcerpt =
|
const rawExcerpt =
|
||||||
|
(hasTitle && hasContent ? `${title}: ${content}` : null) ||
|
||||||
(hasTitle ? title : null) ||
|
(hasTitle ? title : null) ||
|
||||||
(hasContent ? content : null) ||
|
(hasContent ? content : null) ||
|
||||||
(hasLink ? link : null) ||
|
(hasLink ? link : null) ||
|
||||||
(getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : null) ||
|
(hasThumbnail ? t('image') : null) ||
|
||||||
t('no_content');
|
t('no_content');
|
||||||
const excerpt = rawExcerpt.length > 140 ? rawExcerpt.slice(0, 137) + '...' : rawExcerpt;
|
const excerpt = rawExcerpt.length > 140 ? rawExcerpt.slice(0, 137) + '...' : rawExcerpt;
|
||||||
const threadTargetCid = threadCid || cid;
|
const threadTargetCid = threadCid || cid;
|
||||||
@@ -377,7 +390,8 @@ const ModQueueCard = ({ comment }: ModQueueCardProps) => {
|
|||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span title={excerpt}>{excerpt}</span>
|
<span title={excerpt}>{excerpt}</span>
|
||||||
)}
|
)}{' '}
|
||||||
|
/ {t('type')}: {isReply ? t('reply') : t('post')} / {_.capitalize(t('image'))}: {hasThumbnail ? _.lowerCase(t('yes')) : _.lowerCase(t('no'))}
|
||||||
</div>
|
</div>
|
||||||
{renderActions()}
|
{renderActions()}
|
||||||
</div>
|
</div>
|
||||||
@@ -388,19 +402,30 @@ const ModQueueFeedPost = ({ comment }: { comment: Comment }) => {
|
|||||||
const { editedComment } = useEditedComment({ comment });
|
const { editedComment } = useEditedComment({ comment });
|
||||||
const displayComment = editedComment || comment;
|
const displayComment = editedComment || comment;
|
||||||
const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment);
|
const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment);
|
||||||
|
const { parentCid } = displayComment;
|
||||||
|
|
||||||
|
// Fetch parent comment if this is a reply
|
||||||
|
const parentComment = useComment({ commentCid: parentCid });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Post
|
<>
|
||||||
post={displayComment}
|
{parentCid && parentComment && (
|
||||||
showAllReplies={false}
|
<div style={{ marginBottom: '10px', paddingLeft: '20px', borderLeft: '2px solid var(--mod-queue-alert-color, #ccc)' }}>
|
||||||
showReplies={false}
|
<Post post={parentComment} showAllReplies={false} showReplies={false} isModQueue={false} />
|
||||||
isModQueue={true}
|
</div>
|
||||||
modQueueStatus={status}
|
)}
|
||||||
modQueueError={errorMessage}
|
<Post
|
||||||
isPublishing={isPublishing}
|
post={displayComment}
|
||||||
onApprove={handleApprove}
|
showAllReplies={false}
|
||||||
onReject={handleReject}
|
showReplies={false}
|
||||||
/>
|
isModQueue={true}
|
||||||
|
modQueueStatus={status}
|
||||||
|
modQueueError={errorMessage}
|
||||||
|
isPublishing={isPublishing}
|
||||||
|
onApprove={handleApprove}
|
||||||
|
onReject={handleReject}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -730,6 +755,8 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
|||||||
<div className={styles.numberHeader}>No.</div>
|
<div className={styles.numberHeader}>No.</div>
|
||||||
<div className={styles.excerptHeader}>{t('excerpt')}</div>
|
<div className={styles.excerptHeader}>{t('excerpt')}</div>
|
||||||
<div className={styles.timeHeader}>{t('submitted')}</div>
|
<div className={styles.timeHeader}>{t('submitted')}</div>
|
||||||
|
<div className={styles.typeHeader}>{t('type')}</div>
|
||||||
|
<div className={styles.imageHeader}>{t('image')}</div>
|
||||||
<div className={styles.actionsHeader}>{t('actions')}</div>
|
<div className={styles.actionsHeader}>{t('actions')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user