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": "عتبة التنبيه",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "সতর্কতা থ্রেশহোল্ড",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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í",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "Όριο ειδοποίησης",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"rejected": "Rejected",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "آستانه هشدار",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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.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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "סף התרעה",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "सतर्कता सीमा",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "アラート閾値",
|
||||
"modQueue.viewLabel": "View",
|
||||
"modQueue.compact": "Compact",
|
||||
"modQueue.feed": "Feed"
|
||||
"modQueue.feed": "Feed",
|
||||
"type": "タイプ",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"pending_mod_approval": "モデレーターの承認待ち、ユーザーには表示されません"
|
||||
}
|
||||
|
||||
@@ -272,5 +272,9 @@
|
||||
"alert_threshold": "알림 임계값",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "सतर्कता मर्यादा",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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ă",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "Порог предупреждения",
|
||||
"modQueue.viewLabel": "View",
|
||||
"modQueue.compact": "Compact",
|
||||
"modQueue.feed": "Feed"
|
||||
"modQueue.feed": "Feed",
|
||||
"type": "Тип",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"pending_mod_approval": "Ожидает одобрения модератора, не видно пользователям"
|
||||
}
|
||||
|
||||
@@ -272,5 +272,9 @@
|
||||
"alert_threshold": "Pragu i alarmit",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "హెచ్చరిక థ్రెషోల్డ్",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "เกณฑ์การแจ้งเตือน",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "Поріг попередження",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "انتباہ کی حد",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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": "警报阈值",
|
||||
"modQueue.viewLabel": "View",
|
||||
"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 && (
|
||||
<>
|
||||
<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)) && (
|
||||
|
||||
@@ -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 { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
||||
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 styles from '../../views/post/post.module.css';
|
||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
@@ -35,6 +35,11 @@ import _ from 'lodash';
|
||||
import { shouldShowSnow } from '../../lib/snow';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
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
|
||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||
@@ -89,6 +94,97 @@ const PostInfo = ({
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const isInModQueueView = isModQueueView(location.pathname);
|
||||
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)
|
||||
const approved = post?.approved;
|
||||
@@ -277,6 +373,39 @@ const PostInfo = ({
|
||||
)}
|
||||
</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>
|
||||
{!(removed || deleted) && !isModQueue && <PostMenuDesktop postMenu={postMenuProps} />}
|
||||
{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 { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
||||
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 styles from '../../views/post/post.module.css';
|
||||
import { shouldShowSnow } from '../../lib/snow';
|
||||
@@ -30,6 +30,10 @@ import { PostProps } from '../../views/post/post';
|
||||
import _ from 'lodash';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
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
|
||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||
@@ -55,6 +59,97 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||
const isInModQueueView = isModQueueView(location.pathname);
|
||||
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 hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
@@ -218,6 +313,31 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
.numberHeader,
|
||||
.excerptHeader,
|
||||
.timeHeader,
|
||||
.typeHeader,
|
||||
.imageHeader,
|
||||
.actionsHeader {
|
||||
background: var(--mod-queue-header-background-color);
|
||||
padding: 5px;
|
||||
@@ -80,6 +82,18 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.typeHeader {
|
||||
width: 70px;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.imageHeader {
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timeHeader {
|
||||
width: 210px;
|
||||
text-align: right;
|
||||
@@ -124,6 +138,8 @@
|
||||
.number,
|
||||
.excerpt,
|
||||
.time,
|
||||
.type,
|
||||
.image,
|
||||
.actions {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
@@ -135,6 +151,8 @@
|
||||
:global(body.yotsuba) .rowOdd .number,
|
||||
:global(body.yotsuba) .rowOdd .excerpt,
|
||||
:global(body.yotsuba) .rowOdd .time,
|
||||
:global(body.yotsuba) .rowOdd .type,
|
||||
:global(body.yotsuba) .rowOdd .image,
|
||||
:global(body.yotsuba) .rowOdd .actions {
|
||||
background-color: #ede2d4;
|
||||
}
|
||||
@@ -142,6 +160,8 @@
|
||||
:global(body.yotsuba-b) .rowOdd .number,
|
||||
:global(body.yotsuba-b) .rowOdd .excerpt,
|
||||
:global(body.yotsuba-b) .rowOdd .time,
|
||||
:global(body.yotsuba-b) .rowOdd .type,
|
||||
:global(body.yotsuba-b) .rowOdd .image,
|
||||
:global(body.yotsuba-b) .rowOdd .actions {
|
||||
background-color: #e0e5f6;
|
||||
}
|
||||
@@ -149,6 +169,8 @@
|
||||
:global(body.futaba) .rowOdd .number,
|
||||
:global(body.futaba) .rowOdd .excerpt,
|
||||
:global(body.futaba) .rowOdd .time,
|
||||
:global(body.futaba) .rowOdd .type,
|
||||
:global(body.futaba) .rowOdd .image,
|
||||
:global(body.futaba) .rowOdd .actions {
|
||||
background-color: #ede2d4;
|
||||
}
|
||||
@@ -156,6 +178,8 @@
|
||||
:global(body.burichan) .rowOdd .number,
|
||||
:global(body.burichan) .rowOdd .excerpt,
|
||||
:global(body.burichan) .rowOdd .time,
|
||||
:global(body.burichan) .rowOdd .type,
|
||||
:global(body.burichan) .rowOdd .image,
|
||||
:global(body.burichan) .rowOdd .actions {
|
||||
background-color: #e0e5f6;
|
||||
}
|
||||
@@ -163,6 +187,8 @@
|
||||
:global(body.tomorrow) .rowOdd .number,
|
||||
:global(body.tomorrow) .rowOdd .excerpt,
|
||||
:global(body.tomorrow) .rowOdd .time,
|
||||
:global(body.tomorrow) .rowOdd .type,
|
||||
:global(body.tomorrow) .rowOdd .image,
|
||||
:global(body.tomorrow) .rowOdd .actions {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
@@ -170,6 +196,8 @@
|
||||
:global(body.photon) .rowOdd .number,
|
||||
:global(body.photon) .rowOdd .excerpt,
|
||||
:global(body.photon) .rowOdd .time,
|
||||
:global(body.photon) .rowOdd .type,
|
||||
:global(body.photon) .rowOdd .image,
|
||||
:global(body.photon) .rowOdd .actions {
|
||||
background-color: #888;
|
||||
}
|
||||
@@ -187,6 +215,18 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.type {
|
||||
width: 70px;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.excerpt a {
|
||||
text-decoration: var(--post-link-text-decoration);
|
||||
color: var(--post-link-text-color);
|
||||
@@ -397,26 +437,38 @@ span.reject:hover {
|
||||
:global(body.yotsuba) .rowOdd .number,
|
||||
:global(body.yotsuba) .rowOdd .excerpt,
|
||||
:global(body.yotsuba) .rowOdd .time,
|
||||
:global(body.yotsuba) .rowOdd .type,
|
||||
:global(body.yotsuba) .rowOdd .image,
|
||||
:global(body.yotsuba) .rowOdd .actions,
|
||||
:global(body.yotsuba-b) .rowOdd .number,
|
||||
:global(body.yotsuba-b) .rowOdd .excerpt,
|
||||
: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.futaba) .rowOdd .number,
|
||||
:global(body.futaba) .rowOdd .excerpt,
|
||||
:global(body.futaba) .rowOdd .time,
|
||||
:global(body.futaba) .rowOdd .type,
|
||||
:global(body.futaba) .rowOdd .image,
|
||||
:global(body.futaba) .rowOdd .actions,
|
||||
:global(body.burichan) .rowOdd .number,
|
||||
:global(body.burichan) .rowOdd .excerpt,
|
||||
:global(body.burichan) .rowOdd .time,
|
||||
:global(body.burichan) .rowOdd .type,
|
||||
:global(body.burichan) .rowOdd .image,
|
||||
:global(body.burichan) .rowOdd .actions,
|
||||
:global(body.tomorrow) .rowOdd .number,
|
||||
:global(body.tomorrow) .rowOdd .excerpt,
|
||||
:global(body.tomorrow) .rowOdd .time,
|
||||
:global(body.tomorrow) .rowOdd .type,
|
||||
:global(body.tomorrow) .rowOdd .image,
|
||||
:global(body.tomorrow) .rowOdd .actions,
|
||||
:global(body.photon) .rowOdd .number,
|
||||
:global(body.photon) .rowOdd .excerpt,
|
||||
:global(body.photon) .rowOdd .time,
|
||||
:global(body.photon) .rowOdd .type,
|
||||
:global(body.photon) .rowOdd .image,
|
||||
:global(body.photon) .rowOdd .actions {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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 { Virtuoso } from 'react-virtuoso';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
@@ -22,6 +22,7 @@ import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-util
|
||||
import Tooltip from '../../components/tooltip';
|
||||
import useIsMobile from '../../hooks/use-is-mobile';
|
||||
import { Post } from '../post/post';
|
||||
import _ from 'lodash';
|
||||
|
||||
const { addChallenge } = useChallengesStore.getState();
|
||||
|
||||
@@ -162,7 +163,8 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => {
|
||||
const { editedComment } = useEditedComment({ 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)
|
||||
// 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 hasContent = content && content.trim().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 =
|
||||
(hasTitle && hasContent ? `${title}: ${content}` : null) ||
|
||||
(hasTitle ? title : null) ||
|
||||
(hasContent ? content : null) ||
|
||||
(hasLink ? link : null) ||
|
||||
(getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : null) ||
|
||||
(hasThumbnail ? t('image') : null) ||
|
||||
t('no_content');
|
||||
// Only truncate excerpt on desktop, allow wrapping on mobile
|
||||
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)} />
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
@@ -284,7 +292,8 @@ const ModQueueCard = ({ comment }: ModQueueCardProps) => {
|
||||
const { editedComment } = useEditedComment({ 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 alreadyRejected = removed === true;
|
||||
@@ -300,11 +309,15 @@ const ModQueueCard = ({ comment }: ModQueueCardProps) => {
|
||||
const hasTitle = title && title.trim().length > 0;
|
||||
const hasContent = content && content.trim().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 =
|
||||
(hasTitle && hasContent ? `${title}: ${content}` : null) ||
|
||||
(hasTitle ? title : null) ||
|
||||
(hasContent ? content : null) ||
|
||||
(hasLink ? link : null) ||
|
||||
(getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : null) ||
|
||||
(hasThumbnail ? t('image') : null) ||
|
||||
t('no_content');
|
||||
const excerpt = rawExcerpt.length > 140 ? rawExcerpt.slice(0, 137) + '...' : rawExcerpt;
|
||||
const threadTargetCid = threadCid || cid;
|
||||
@@ -377,7 +390,8 @@ const ModQueueCard = ({ comment }: ModQueueCardProps) => {
|
||||
</Link>
|
||||
) : (
|
||||
<span title={excerpt}>{excerpt}</span>
|
||||
)}
|
||||
)}{' '}
|
||||
/ {t('type')}: {isReply ? t('reply') : t('post')} / {_.capitalize(t('image'))}: {hasThumbnail ? _.lowerCase(t('yes')) : _.lowerCase(t('no'))}
|
||||
</div>
|
||||
{renderActions()}
|
||||
</div>
|
||||
@@ -388,8 +402,18 @@ const ModQueueFeedPost = ({ comment }: { comment: Comment }) => {
|
||||
const { editedComment } = useEditedComment({ comment });
|
||||
const displayComment = editedComment || comment;
|
||||
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 (
|
||||
<>
|
||||
{parentCid && parentComment && (
|
||||
<div style={{ marginBottom: '10px', paddingLeft: '20px', borderLeft: '2px solid var(--mod-queue-alert-color, #ccc)' }}>
|
||||
<Post post={parentComment} showAllReplies={false} showReplies={false} isModQueue={false} />
|
||||
</div>
|
||||
)}
|
||||
<Post
|
||||
post={displayComment}
|
||||
showAllReplies={false}
|
||||
@@ -401,6 +425,7 @@ const ModQueueFeedPost = ({ comment }: { comment: Comment }) => {
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -730,6 +755,8 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
||||
<div className={styles.numberHeader}>No.</div>
|
||||
<div className={styles.excerptHeader}>{t('excerpt')}</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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user