mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(mod-queue): add minutes support to alert threshold
This commit is contained in:
@@ -267,5 +267,7 @@
|
||||
"publishing": "نشر",
|
||||
"approved": "موافق عليه",
|
||||
"rejected": "مرفوض",
|
||||
"submitted": "تم الإرسال"
|
||||
"submitted": "تم الإرسال",
|
||||
"hours": "ساعات",
|
||||
"minutes": "دقائق"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "প্রকাশনা",
|
||||
"approved": "অনুমোদিত",
|
||||
"rejected": "প্রত্যাখ্যান",
|
||||
"submitted": "জমা দেওয়া হয়েছে"
|
||||
"submitted": "জমা দেওয়া হয়েছে",
|
||||
"hours": "ঘণ্টা",
|
||||
"minutes": "মিনিট"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publikování",
|
||||
"approved": "Schváleno",
|
||||
"rejected": "Zamítnuto",
|
||||
"submitted": "Odesláno"
|
||||
"submitted": "Odesláno",
|
||||
"hours": "hodin",
|
||||
"minutes": "minut"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Udgivelse",
|
||||
"approved": "Godkendt",
|
||||
"rejected": "Afvist",
|
||||
"submitted": "Indsendt"
|
||||
"submitted": "Indsendt",
|
||||
"hours": "timer",
|
||||
"minutes": "minutter"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Veröffentlichung",
|
||||
"approved": "Genehmigt",
|
||||
"rejected": "Abgelehnt",
|
||||
"submitted": "Eingereicht"
|
||||
"submitted": "Eingereicht",
|
||||
"hours": "Stunden",
|
||||
"minutes": "Minuten"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Δημοσίευση",
|
||||
"approved": "Εγκεκριμένο",
|
||||
"rejected": "Απορρίφθηκε",
|
||||
"submitted": "Υποβλήθηκε"
|
||||
"submitted": "Υποβλήθηκε",
|
||||
"hours": "ώρες",
|
||||
"minutes": "λεπτά"
|
||||
}
|
||||
|
||||
@@ -261,6 +261,8 @@
|
||||
"reject": "Reject",
|
||||
"filter_by_board": "Filter by board",
|
||||
"mod_queue_alert_threshold": "Mod queue alert threshold",
|
||||
"hours": "hours",
|
||||
"minutes": "minutes",
|
||||
"no_content": "No content",
|
||||
"excerpt": "Excerpt",
|
||||
"publishing": "Publishing",
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publicando",
|
||||
"approved": "Aprobado",
|
||||
"rejected": "Rechazado",
|
||||
"submitted": "Enviado"
|
||||
"submitted": "Enviado",
|
||||
"hours": "horas",
|
||||
"minutes": "minutos"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "در حال انتشار",
|
||||
"approved": "تایید شده",
|
||||
"rejected": "رد شده",
|
||||
"submitted": "ارسال شده"
|
||||
"submitted": "ارسال شده",
|
||||
"hours": "ساعت",
|
||||
"minutes": "دقیقه"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Julkaiseminen",
|
||||
"approved": "Hyväksytty",
|
||||
"rejected": "Hylätty",
|
||||
"submitted": "Lähetetty"
|
||||
"submitted": "Lähetetty",
|
||||
"hours": "tuntia",
|
||||
"minutes": "minuuttia"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Pag-publish",
|
||||
"approved": "Naaprubado",
|
||||
"rejected": "Tinanggihan",
|
||||
"submitted": "Isinumite"
|
||||
"submitted": "Isinumite",
|
||||
"hours": "oras",
|
||||
"minutes": "minuto"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publication",
|
||||
"approved": "Approuvé",
|
||||
"rejected": "Rejeté",
|
||||
"submitted": "Soumis"
|
||||
"submitted": "Soumis",
|
||||
"hours": "heures",
|
||||
"minutes": "minutes"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "מפרסם",
|
||||
"approved": "אושר",
|
||||
"rejected": "נדחה",
|
||||
"submitted": "נשלח"
|
||||
"submitted": "נשלח",
|
||||
"hours": "שעות",
|
||||
"minutes": "דקות"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "प्रकाशन",
|
||||
"approved": "अनुमोदित",
|
||||
"rejected": "अस्वीकृत",
|
||||
"submitted": "सबमिट किया गया"
|
||||
"submitted": "सबमिट किया गया",
|
||||
"hours": "घंटे",
|
||||
"minutes": "मिनट"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Közzététel",
|
||||
"approved": "Jóváhagyva",
|
||||
"rejected": "Elutasítva",
|
||||
"submitted": "Beküldve"
|
||||
"submitted": "Beküldve",
|
||||
"hours": "óra",
|
||||
"minutes": "perc"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Menerbitkan",
|
||||
"approved": "Disetujui",
|
||||
"rejected": "Ditolak",
|
||||
"submitted": "Dikirim"
|
||||
"submitted": "Dikirim",
|
||||
"hours": "jam",
|
||||
"minutes": "menit"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Pubblicazione",
|
||||
"approved": "Approvato",
|
||||
"rejected": "Rifiutato",
|
||||
"submitted": "Inviato"
|
||||
"submitted": "Inviato",
|
||||
"hours": "ore",
|
||||
"minutes": "minuti"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "公開中",
|
||||
"approved": "承認済み",
|
||||
"rejected": "却下",
|
||||
"submitted": "送信済み"
|
||||
"submitted": "送信済み",
|
||||
"hours": "時間",
|
||||
"minutes": "分"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "게시 중",
|
||||
"approved": "승인됨",
|
||||
"rejected": "거부됨",
|
||||
"submitted": "제출됨"
|
||||
"submitted": "제출됨",
|
||||
"hours": "시간",
|
||||
"minutes": "분"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "प्रकाशन करत आहे",
|
||||
"approved": "मंजूर",
|
||||
"rejected": "नाकारले",
|
||||
"submitted": "सबमिट केले"
|
||||
"submitted": "सबमिट केले",
|
||||
"hours": "तास",
|
||||
"minutes": "मिनिटे"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publiceren",
|
||||
"approved": "Goedgekeurd",
|
||||
"rejected": "Afgewezen",
|
||||
"submitted": "Ingediend"
|
||||
"submitted": "Ingediend",
|
||||
"hours": "uur",
|
||||
"minutes": "minuten"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publisering",
|
||||
"approved": "Godkjent",
|
||||
"rejected": "Avslått",
|
||||
"submitted": "Sendt inn"
|
||||
"submitted": "Sendt inn",
|
||||
"hours": "timer",
|
||||
"minutes": "minutter"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publikowanie",
|
||||
"approved": "Zatwierdzone",
|
||||
"rejected": "Odrzucone",
|
||||
"submitted": "Przesłane"
|
||||
"submitted": "Przesłane",
|
||||
"hours": "godzin",
|
||||
"minutes": "minut"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publicando",
|
||||
"approved": "Aprovado",
|
||||
"rejected": "Rejeitado",
|
||||
"submitted": "Enviado"
|
||||
"submitted": "Enviado",
|
||||
"hours": "horas",
|
||||
"minutes": "minutos"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publicare",
|
||||
"approved": "Aprobat",
|
||||
"rejected": "Respins",
|
||||
"submitted": "Trimis"
|
||||
"submitted": "Trimis",
|
||||
"hours": "ore",
|
||||
"minutes": "minute"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Публикация",
|
||||
"approved": "Одобрено",
|
||||
"rejected": "Отклонено",
|
||||
"submitted": "Отправлено"
|
||||
"submitted": "Отправлено",
|
||||
"hours": "часов",
|
||||
"minutes": "минут"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publikimi",
|
||||
"approved": "Aprovuar",
|
||||
"rejected": "Refuzuar",
|
||||
"submitted": "Dërguar"
|
||||
"submitted": "Dërguar",
|
||||
"hours": "orë",
|
||||
"minutes": "minuta"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Publicerar",
|
||||
"approved": "Godkänd",
|
||||
"rejected": "Avvisad",
|
||||
"submitted": "Inskickat"
|
||||
"submitted": "Inskickat",
|
||||
"hours": "timmar",
|
||||
"minutes": "minuter"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "ప్రచురించడం",
|
||||
"approved": "అనుమోదించబడింది",
|
||||
"rejected": "తిరస్కరించబడింది",
|
||||
"submitted": "సమర్పించబడింది"
|
||||
"submitted": "సమర్పించబడింది",
|
||||
"hours": "గంటలు",
|
||||
"minutes": "నిమిషాలు"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "กำลังเผยแพร่",
|
||||
"approved": "อนุมัติแล้ว",
|
||||
"rejected": "ปฏิเสธ",
|
||||
"submitted": "ส่งแล้ว"
|
||||
"submitted": "ส่งแล้ว",
|
||||
"hours": "ชั่วโมง",
|
||||
"minutes": "นาที"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Yayınlama",
|
||||
"approved": "Onaylandı",
|
||||
"rejected": "Reddedildi",
|
||||
"submitted": "Gönderildi"
|
||||
"submitted": "Gönderildi",
|
||||
"hours": "saat",
|
||||
"minutes": "dakika"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Публікація",
|
||||
"approved": "Схвалено",
|
||||
"rejected": "Відхилено",
|
||||
"submitted": "Відправлено"
|
||||
"submitted": "Відправлено",
|
||||
"hours": "годин",
|
||||
"minutes": "хвилин"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "اشاعت",
|
||||
"approved": "منظور شدہ",
|
||||
"rejected": "مسترد",
|
||||
"submitted": "جمع کرایا گیا"
|
||||
"submitted": "جمع کرایا گیا",
|
||||
"hours": "گھنٹے",
|
||||
"minutes": "منٹ"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "Đang xuất bản",
|
||||
"approved": "Đã phê duyệt",
|
||||
"rejected": "Đã từ chối",
|
||||
"submitted": "Đã gửi"
|
||||
"submitted": "Đã gửi",
|
||||
"hours": "giờ",
|
||||
"minutes": "phút"
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@
|
||||
"publishing": "发布中",
|
||||
"approved": "已批准",
|
||||
"rejected": "已拒绝",
|
||||
"submitted": "已提交"
|
||||
"submitted": "已提交",
|
||||
"hours": "小时",
|
||||
"minutes": "分钟"
|
||||
}
|
||||
|
||||
@@ -1,21 +1,57 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type AlertThresholdUnit = 'hours' | 'minutes';
|
||||
|
||||
interface ModQueueState {
|
||||
alertThresholdHours: number;
|
||||
alertThresholdValue: number;
|
||||
alertThresholdUnit: AlertThresholdUnit;
|
||||
selectedBoardFilter: string | null;
|
||||
setAlertThresholdHours: (hours: number) => void;
|
||||
setAlertThreshold: (value: number, unit: AlertThresholdUnit) => void;
|
||||
setSelectedBoardFilter: (boardAddress: string | null) => void;
|
||||
// Helper to get threshold in seconds for calculations
|
||||
getAlertThresholdSeconds: () => number;
|
||||
}
|
||||
|
||||
const useModQueueStore = create<ModQueueState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
alertThresholdHours: 6,
|
||||
selectedBoardFilter: null,
|
||||
setAlertThresholdHours: (hours) => set({ alertThresholdHours: hours }),
|
||||
setSelectedBoardFilter: (boardAddress) => set({ selectedBoardFilter: boardAddress }),
|
||||
}),
|
||||
(set, get) => {
|
||||
// Migration: Check for old format in localStorage
|
||||
let initialState = { alertThresholdValue: 6, alertThresholdUnit: 'hours' as AlertThresholdUnit };
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const stored = localStorage.getItem('mod-queue-storage');
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
// If old format exists, migrate it
|
||||
if (parsed.state?.alertThresholdHours !== undefined) {
|
||||
initialState = {
|
||||
alertThresholdValue: parsed.state.alertThresholdHours,
|
||||
alertThresholdUnit: 'hours',
|
||||
};
|
||||
} else if (parsed.state?.alertThresholdValue !== undefined) {
|
||||
initialState = {
|
||||
alertThresholdValue: parsed.state.alertThresholdValue,
|
||||
alertThresholdUnit: parsed.state.alertThresholdUnit || 'hours',
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parse errors, use defaults
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...initialState,
|
||||
selectedBoardFilter: null,
|
||||
setAlertThreshold: (value, unit) => set({ alertThresholdValue: value, alertThresholdUnit: unit }),
|
||||
setSelectedBoardFilter: (boardAddress) => set({ selectedBoardFilter: boardAddress }),
|
||||
getAlertThresholdSeconds: () => {
|
||||
const { alertThresholdValue, alertThresholdUnit } = get();
|
||||
return alertThresholdUnit === 'hours' ? alertThresholdValue * 3600 : alertThresholdValue * 60;
|
||||
},
|
||||
};
|
||||
},
|
||||
{
|
||||
name: 'mod-queue-storage',
|
||||
},
|
||||
|
||||
@@ -39,6 +39,14 @@
|
||||
color: var(--body-font-color);
|
||||
}
|
||||
|
||||
.alertThresholdUnitSelect {
|
||||
padding: 4px;
|
||||
border: var(--mod-queue-table-border);
|
||||
background: var(--mod-queue-header-background-color);
|
||||
color: var(--body-font-color);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
border: var(--mod-queue-table-border);
|
||||
}
|
||||
@@ -147,8 +155,6 @@
|
||||
color: var(--mod-queue-alert-color);
|
||||
font-weight: bold;
|
||||
animation: blink 2s infinite;
|
||||
/* Synchronize all animations by starting from the same phase */
|
||||
animation-delay: calc(var(--mod-queue-blink-phase, 0s) * -1);
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
|
||||
@@ -48,7 +48,7 @@ type ModerationAction = 'approve' | 'reject' | null;
|
||||
|
||||
const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { alertThresholdHours } = useModQueueStore();
|
||||
const { getAlertThresholdSeconds } = useModQueueStore();
|
||||
const [initiatedAction, setInitiatedAction] = useState<ModerationAction>(null);
|
||||
|
||||
// handle pending mod or author edit
|
||||
@@ -65,7 +65,8 @@ const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) =>
|
||||
|
||||
const boardPath = useBoardPath(subplebbitAddress);
|
||||
const timeWaiting = Date.now() / 1000 - timestamp;
|
||||
const isOverThreshold = timeWaiting > alertThresholdHours * 3600;
|
||||
const alertThresholdSeconds = getAlertThresholdSeconds();
|
||||
const isOverThreshold = timeWaiting > alertThresholdSeconds;
|
||||
|
||||
// Only show alert animation for comments awaiting approval (not approved or rejected)
|
||||
const isAwaitingApproval = !alreadyApproved && !alreadyRejected;
|
||||
@@ -256,18 +257,18 @@ interface ModQueueButtonProps {
|
||||
// and reports its status via callback
|
||||
interface ModQueueCountItemProps {
|
||||
comment: Comment;
|
||||
alertThresholdHours: number;
|
||||
alertThresholdSeconds: number;
|
||||
onStatusChange: (cid: string, status: { awaiting: boolean; urgent: boolean }) => void;
|
||||
}
|
||||
|
||||
const ModQueueCountItem = ({ comment, alertThresholdHours, onStatusChange }: ModQueueCountItemProps) => {
|
||||
const ModQueueCountItem = ({ comment, alertThresholdSeconds, onStatusChange }: ModQueueCountItemProps) => {
|
||||
const { editedComment } = useEditedComment({ comment });
|
||||
const displayComment = editedComment || comment;
|
||||
|
||||
const { cid, approved, removed, timestamp } = displayComment;
|
||||
const isAwaiting = approved !== true && removed !== true;
|
||||
const timeWaiting = Date.now() / 1000 - timestamp;
|
||||
const isUrgent = isAwaiting && timeWaiting > alertThresholdHours * 3600;
|
||||
const isUrgent = isAwaiting && timeWaiting > alertThresholdSeconds;
|
||||
|
||||
// Report status changes to parent - useEffect is appropriate here for syncing with parent state
|
||||
useEffect(() => {
|
||||
@@ -280,12 +281,12 @@ const ModQueueCountItem = ({ comment, alertThresholdHours, onStatusChange }: Mod
|
||||
// Inner component that handles counting with useEditedComment for each item
|
||||
interface ModQueueButtonContentProps {
|
||||
feed: Comment[];
|
||||
alertThresholdHours: number;
|
||||
alertThresholdSeconds: number;
|
||||
boardIdentifier?: string;
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
const ModQueueButtonContent = ({ feed, alertThresholdHours, boardIdentifier, isMobile }: ModQueueButtonContentProps) => {
|
||||
const ModQueueButtonContent = ({ feed, alertThresholdSeconds, boardIdentifier, isMobile }: ModQueueButtonContentProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [statusMap, setStatusMap] = useState<Map<string, { awaiting: boolean; urgent: boolean }>>(new Map());
|
||||
|
||||
@@ -344,7 +345,7 @@ const ModQueueButtonContent = ({ feed, alertThresholdHours, boardIdentifier, isM
|
||||
<>
|
||||
{/* Render counter items to track real-time moderation status */}
|
||||
{feed.map((item) => (
|
||||
<ModQueueCountItem key={item.cid} comment={item} alertThresholdHours={alertThresholdHours} onStatusChange={handleStatusChange} />
|
||||
<ModQueueCountItem key={item.cid} comment={item} alertThresholdSeconds={alertThresholdSeconds} onStatusChange={handleStatusChange} />
|
||||
))}
|
||||
{isMobile ? buttonContent : <>[{buttonContent}]</>}
|
||||
</>
|
||||
@@ -352,7 +353,7 @@ const ModQueueButtonContent = ({ feed, alertThresholdHours, boardIdentifier, isM
|
||||
};
|
||||
|
||||
export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProps) => {
|
||||
const { alertThresholdHours } = useModQueueStore();
|
||||
const { getAlertThresholdSeconds } = useModQueueStore();
|
||||
const { accountSubplebbits } = useAccountSubplebbits();
|
||||
const accountSubplebbitAddresses = useMemo(() => Object.keys(accountSubplebbits || {}), [accountSubplebbits]);
|
||||
const defaultSubplebbits = useDefaultSubplebbits();
|
||||
@@ -388,13 +389,14 @@ export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProp
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ModQueueButtonContent feed={feed} alertThresholdHours={alertThresholdHours} boardIdentifier={boardIdentifier} isMobile={isMobile} />;
|
||||
const alertThresholdSeconds = getAlertThresholdSeconds();
|
||||
return <ModQueueButtonContent feed={feed} alertThresholdSeconds={alertThresholdSeconds} boardIdentifier={boardIdentifier} isMobile={isMobile} />;
|
||||
};
|
||||
|
||||
export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueViewProps) => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
const { selectedBoardFilter, alertThresholdHours, setAlertThresholdHours } = useModQueueStore();
|
||||
const { selectedBoardFilter, alertThresholdValue, alertThresholdUnit, setAlertThreshold } = useModQueueStore();
|
||||
const { accountSubplebbits } = useAccountSubplebbits();
|
||||
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
||||
const defaultSubplebbits = useDefaultSubplebbits();
|
||||
@@ -436,43 +438,6 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
||||
setResetFunction(reset);
|
||||
}, [reset, setResetFunction]);
|
||||
|
||||
// Check if any feed items need the blinking animation (awaiting approval + over threshold)
|
||||
// This is a heuristic based on raw feed data - real-time edits might change this,
|
||||
// but it's good enough to avoid running the interval when nothing needs to blink
|
||||
const hasBlinkingItems = useMemo(() => {
|
||||
const thresholdSeconds = alertThresholdHours * 3600;
|
||||
const now = Date.now() / 1000;
|
||||
return feed.some((comment) => {
|
||||
const isAwaiting = comment.approved !== true && comment.removed !== true;
|
||||
const timeWaiting = now - comment.timestamp;
|
||||
return isAwaiting && timeWaiting > thresholdSeconds;
|
||||
});
|
||||
}, [feed, alertThresholdHours]);
|
||||
|
||||
// Synchronize blinking animation across all rows
|
||||
// Set a CSS variable with the current animation phase so all elements start from the same point
|
||||
// Update frequently to ensure elements rendered at different times stay synchronized
|
||||
// Only run when there are items that actually need the blinking animation
|
||||
useEffect(() => {
|
||||
if (!hasBlinkingItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ANIMATION_DURATION = 2000; // 2 seconds
|
||||
const UPDATE_INTERVAL = 100; // Update every 100ms for smooth synchronization
|
||||
|
||||
const updateAnimationPhase = () => {
|
||||
// Calculate current phase in the animation cycle (0 to 2 seconds)
|
||||
const phase = (Date.now() % ANIMATION_DURATION) / 1000;
|
||||
document.documentElement.style.setProperty('--mod-queue-blink-phase', `${phase}s`);
|
||||
};
|
||||
|
||||
// Update immediately and then at regular intervals
|
||||
updateAnimationPhase();
|
||||
const interval = setInterval(updateAnimationPhase, UPDATE_INTERVAL);
|
||||
return () => clearInterval(interval);
|
||||
}, [hasBlinkingItems]);
|
||||
|
||||
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
|
||||
const showBoardColumn = !resolvedAddress && !selectedBoardFilter;
|
||||
|
||||
@@ -494,11 +459,29 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
||||
<input
|
||||
type='number'
|
||||
min='1'
|
||||
value={alertThresholdHours}
|
||||
onChange={(e) => setAlertThresholdHours(Number(e.target.value))}
|
||||
step={alertThresholdUnit === 'minutes' ? '1' : '1'}
|
||||
value={alertThresholdValue}
|
||||
onChange={(e) => setAlertThreshold(Number(e.target.value), alertThresholdUnit)}
|
||||
className={styles.alertThresholdInput}
|
||||
/>{' '}
|
||||
{t('hours')}
|
||||
/>
|
||||
<select
|
||||
value={alertThresholdUnit}
|
||||
onChange={(e) => {
|
||||
const newUnit = e.target.value as 'hours' | 'minutes';
|
||||
// Convert value when switching units
|
||||
const newValue =
|
||||
alertThresholdUnit === 'hours' && newUnit === 'minutes'
|
||||
? alertThresholdValue * 60
|
||||
: alertThresholdUnit === 'minutes' && newUnit === 'hours'
|
||||
? Math.round(alertThresholdValue / 60)
|
||||
: alertThresholdValue;
|
||||
setAlertThreshold(Math.max(1, newValue), newUnit);
|
||||
}}
|
||||
className={styles.alertThresholdUnitSelect}
|
||||
>
|
||||
<option value='minutes'>{t('minutes')}</option>
|
||||
<option value='hours'>{t('hours')}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user