diff --git a/components/mod-event/EventItem.tsx b/components/mod-event/EventItem.tsx
index 25d3230..eddee3d 100644
--- a/components/mod-event/EventItem.tsx
+++ b/components/mod-event/EventItem.tsx
@@ -11,6 +11,7 @@ import { ReasonBadge } from '@/reports/ReasonBadge'
import { useConfigurationContext } from '@/shell/ConfigurationContext'
import { ItemTitle } from './ItemTitle'
import { PreviewCard } from '@/common/PreviewCard'
+import { ModEventViewWithDetails } from './useModEventList'
const LinkToAuthor = ({
creatorHandle,
@@ -33,7 +34,7 @@ const LinkToAuthor = ({
const Comment = ({
modEvent,
}: {
- modEvent: ToolsOzoneModerationDefs.ModEventView & {
+ modEvent: ModEventViewWithDetails & {
event:
| ToolsOzoneModerationDefs.ModEventEscalate
| ToolsOzoneModerationDefs.ModEventAcknowledge
@@ -102,12 +103,19 @@ function isMessageSubject(
return subject.messageId !== undefined
}
+type ModEventType = { event: T } & ToolsOzoneModerationDefs.ModEventView
+
+function isModEventType(
+ e: ToolsOzoneModerationDefs.ModEventView,
+ predicate: (event: unknown) => event is T,
+): e is ModEventType {
+ return predicate(e.event)
+}
+
const Report = ({
modEvent,
}: {
- modEvent: {
- event: ToolsOzoneModerationDefs.ModEventReport
- } & ToolsOzoneModerationDefs.ModEventView
+ modEvent: ModEventType
}) => {
const isAppeal =
modEvent.event.reportType === ComAtprotoModerationDefs.REASONAPPEAL
@@ -208,7 +216,7 @@ const EventLabels = ({
if (!labels?.length) return null
return (
-
+
{header}
{labels.map((label) => {
if (isTag) {
@@ -235,9 +243,7 @@ const EventLabels = ({
const Label = ({
modEvent,
}: {
- modEvent: {
- event: ToolsOzoneModerationDefs.ModEventLabel
- } & ToolsOzoneModerationDefs.ModEventView
+ modEvent: ModEventType
}) => {
return (
<>
@@ -306,7 +312,7 @@ export const ModEventItem = ({
showContentAuthor,
showContentPreview,
}: {
- modEvent: ToolsOzoneModerationDefs.ModEventView
+ modEvent: ModEventViewWithDetails
showContentDetails: boolean
showContentAuthor: boolean
showContentPreview: boolean
@@ -331,20 +337,16 @@ export const ModEventItem = ({
) {
eventItem =
}
- if (ToolsOzoneModerationDefs.isModEventReport(modEvent.event)) {
- // @ts-ignore
+ if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventReport)) {
eventItem =
}
- if (ToolsOzoneModerationDefs.isModEventLabel(modEvent.event)) {
- //@ts-ignore
+ if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventLabel)) {
eventItem =
}
- if (ToolsOzoneModerationDefs.isModEventTag(modEvent.event)) {
- //@ts-ignore
+ if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventTag)) {
eventItem =
}
- if (ToolsOzoneModerationDefs.isModEventEmail(modEvent.event)) {
- //@ts-ignore
+ if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventEmail)) {
eventItem =
}
const previewSubject = modEvent.subject.uri || modEvent.subject.did
diff --git a/components/mod-event/ItemTitle.tsx b/components/mod-event/ItemTitle.tsx
index da1f2f6..fb3f77b 100644
--- a/components/mod-event/ItemTitle.tsx
+++ b/components/mod-event/ItemTitle.tsx
@@ -4,6 +4,8 @@ import {
ToolsOzoneModerationDefs,
ComAtprotoModerationDefs,
} from '@atproto/api'
+import { ModEventViewWithDetails } from './useModEventList'
+import { ReviewStateIcon } from '@/subject/ReviewStateMarker'
const dateFormatter = new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
@@ -15,7 +17,7 @@ export const ItemTitle = ({
showContentDetails,
showContentAuthor,
}: {
- modEvent: ToolsOzoneModerationDefs.ModEventView
+ modEvent: ModEventViewWithDetails
showContentDetails: boolean
showContentAuthor: boolean
}) => {
@@ -110,13 +112,19 @@ export const ItemTitle = ({
{showContentDetails && (
-
+
+ {modEvent.repo?.moderation.subjectStatus && (
+
+ )}
)}
diff --git a/components/mod-event/useModEventList.tsx b/components/mod-event/useModEventList.tsx
index 322d9ce..cbea835 100644
--- a/components/mod-event/useModEventList.tsx
+++ b/components/mod-event/useModEventList.tsx
@@ -1,9 +1,11 @@
import {
+ Agent,
AtUri,
ChatBskyConvoDefs,
ComAtprotoAdminDefs,
ComAtprotoModerationDefs,
ComAtprotoRepoStrongRef,
+ ToolsOzoneModerationDefs,
ToolsOzoneModerationQueryEvents,
} from '@atproto/api'
import { useInfiniteQuery } from '@tanstack/react-query'
@@ -14,6 +16,7 @@ import { useLabelerAgent } from '@/shell/ConfigurationContext'
import { MOD_EVENT_TITLES, MOD_EVENTS } from './constants'
import { useWorkspaceAddItemsMutation } from '@/workspace/hooks'
import { DM_DISABLE_TAG, VIDEO_UPLOAD_DISABLE_TAG } from '@/lib/constants'
+import { chunkArray } from '@/lib/util'
export type WorkspaceConfirmationOptions =
| 'subjects'
@@ -27,6 +30,11 @@ export type ModEventListQueryOptions = {
}
}
+export type ModEventViewWithDetails = ToolsOzoneModerationDefs.ModEventView & {
+ repo?: ToolsOzoneModerationDefs.RepoViewDetail
+ record?: ToolsOzoneModerationDefs.RecordViewDetail
+}
+
type CommentFilter = {
enabled: boolean
keyword: string
@@ -57,6 +65,70 @@ const initialListState = {
showContentPreview: false,
}
+const getReposAndRecordsForEvents = async (
+ labelerAgent: Agent,
+ events: ToolsOzoneModerationDefs.ModEventView[],
+) => {
+ const repos = new Map<
+ string,
+ ToolsOzoneModerationDefs.RepoViewDetail | undefined
+ >()
+ const records = new Map<
+ string,
+ ToolsOzoneModerationDefs.RecordViewDetail | undefined
+ >()
+
+ for (const event of events) {
+ if (
+ ComAtprotoAdminDefs.isRepoRef(event.subject) ||
+ ChatBskyConvoDefs.isMessageRef(event.subject)
+ ) {
+ repos.set(event.subject.did, undefined)
+ } else if (ComAtprotoRepoStrongRef.isMain(event.subject)) {
+ records.set(event.subject.uri, undefined)
+ }
+ }
+
+ const fetchers: Array> = []
+
+ // Right now, we're only loading 25 events at a time so this chunking never really takes effect
+ // But to future proof page size change, we're implementing the chunking anyways
+ if (repos.size) {
+ for (const chunk of chunkArray(Array.from(repos.keys()), 50)) {
+ fetchers.push(
+ labelerAgent.tools.ozone.moderation
+ .getRepos({ dids: chunk })
+ .then(({ data }) => {
+ for (const repo of data.repos) {
+ if (ToolsOzoneModerationDefs.isRepoViewDetail(repo)) {
+ repos.set(repo.did, repo)
+ }
+ }
+ }),
+ )
+ }
+ }
+ if (records.size) {
+ for (const chunk of chunkArray(Array.from(records.keys()), 50)) {
+ fetchers.push(
+ labelerAgent.tools.ozone.moderation
+ .getRecords({ uris: chunk })
+ .then(({ data }) => {
+ for (const record of data.records) {
+ if (ToolsOzoneModerationDefs.isRecordViewDetail(record)) {
+ records.set(record.uri, record)
+ }
+ }
+ }),
+ )
+ }
+ }
+
+ await Promise.all(fetchers)
+
+ return { repos, records }
+}
+
// The 2 fields need overriding because in the initialState, they are set as undefined so the alternative string type is not accepted without override
export type EventListState = Omit<
typeof initialListState,
@@ -152,7 +224,10 @@ export const useModEventList = (
}
}, [props.createdBy])
- const results = useInfiniteQuery({
+ const results = useInfiniteQuery<{
+ events: ModEventViewWithDetails[]
+ cursor?: string
+ }>({
queryKey: ['modEventList', { listState }],
queryFn: async ({ pageParam }) => {
const {
@@ -256,12 +331,29 @@ export const useModEventList = (
})
}
- const { data } =
- await labelerAgent.tools.ozone.moderation.queryEvents({
- limit: 25,
- ...queryParams,
- })
- return data
+ const { data } = await labelerAgent.tools.ozone.moderation.queryEvents({
+ limit: 25,
+ ...queryParams,
+ })
+ const { repos, records } = await getReposAndRecordsForEvents(
+ labelerAgent,
+ data.events,
+ )
+
+ return {
+ events: data.events.map((e) => {
+ if (
+ ComAtprotoAdminDefs.isRepoRef(e.subject) ||
+ ChatBskyConvoDefs.isMessageRef(e.subject)
+ ) {
+ return { ...e, repo: repos.get(e.subject.did) }
+ } else if (ComAtprotoRepoStrongRef.isMain(e.subject)) {
+ return { ...e, record: records.get(e.subject.uri) }
+ }
+ return { ...e }
+ }),
+ cursor: data.cursor,
+ }
},
getNextPageParam: (lastPage) => lastPage.cursor,
...(props.queryOptions || {}),
diff --git a/components/subject/ReviewStateMarker.tsx b/components/subject/ReviewStateMarker.tsx
index 8fbf983..ccb544b 100644
--- a/components/subject/ReviewStateMarker.tsx
+++ b/components/subject/ReviewStateMarker.tsx
@@ -1,4 +1,5 @@
import { DM_DISABLE_TAG } from '@/lib/constants'
+import { classNames } from '@/lib/util'
import { ToolsOzoneModerationDefs } from '@atproto/api'
import {
CheckCircleIcon,
@@ -111,8 +112,10 @@ export const SubjectReviewStateBadge = ({
export const ReviewStateIcon = ({
subjectStatus,
className,
+ size = 'md',
}: {
subjectStatus: ToolsOzoneModerationDefs.SubjectStatusView
+ size?: 'md' | 'sm'
className?: string
}) => {
let text =
@@ -134,10 +137,17 @@ export const ReviewStateIcon = ({
Icon = ScaleIcon
}
+ const sizeClasses = size === 'sm' ? 'h-4 w-4' : 'h-6 w-6'
+
return (
)
}