Skip to content

Commit 7892e4a

Browse files
committed
feat: impl threads
1 parent 91520d6 commit 7892e4a

File tree

6 files changed

+107
-71
lines changed

6 files changed

+107
-71
lines changed

apps/frontend/src/components/ui/moderation/ModerationReportCard.vue

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,10 @@
117117
</div>
118118
</div>
119119
</div>
120-
<div class="border-1 bg-surface-l2 rounded-b-3xl border-solid border-bg-raised p-4">
121-
<ReportThread
120+
<div class="border-1 bg-surface-2 rounded-b-3xl border-solid border-bg-raised p-4 pt-2">
121+
<ThreadView
122122
v-if="report.thread"
123123
ref="reportThread"
124-
class="mb-16 sm:mb-0"
125124
:thread="report.thread"
126125
:report="report"
127126
:reporter="report.reporter_user"
@@ -149,15 +148,15 @@ import {
149148
} from '@modrinth/ui'
150149
import { computed } from 'vue'
151150
152-
import ReportThread from '../thread/ReportThread.vue'
151+
import ThreadView from '../thread/ThreadView.vue'
153152
154153
const { addNotification } = injectNotificationManager()
155154
156155
const props = defineProps<{
157156
report: ExtendedReport
158157
}>()
159158
160-
const reportThread = ref<InstanceType<typeof ReportThread> | null>(null)
159+
const reportThread = ref<InstanceType<typeof ThreadView> | null>(null)
161160
// const collapsibleRegion = ref<InstanceType<typeof CollapsibleRegion> | null>(null)
162161
163162
const formatRelativeTime = useRelativeTime()

apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ import {
2121
OverflowMenu,
2222
type OverflowMenuOption,
2323
} from '@modrinth/ui'
24-
import { capitalizeString, formatProjectType, highlightCodeLines } from '@modrinth/utils'
24+
import { capitalizeString, formatProjectType, highlightCodeLines, type Thread } from '@modrinth/utils'
2525
import { computed, ref } from 'vue'
2626
27+
import ThreadView from '~/components/ui/thread/ThreadView.vue'
28+
2729
const props = defineProps<{
2830
item: {
2931
project: Labrinth.Projects.v3.Project
3032
project_owner: Labrinth.TechReview.Internal.Ownership
31-
thread: Labrinth.TechReview.Internal.DBThread
33+
thread: Labrinth.TechReview.Internal.Thread
3234
reports: Labrinth.TechReview.Internal.FileReport[]
3335
}
3436
loadingIssues: Set<string>
@@ -171,6 +173,10 @@ function toggleIssue(issueId: string) {
171173
}
172174
}
173175
176+
function handleThreadUpdate() {
177+
emit('refetch')
178+
}
179+
174180
// function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReport) {
175181
// const counts = {
176182
// SEVERE: 0,
@@ -204,7 +210,7 @@ function toggleIssue(issueId: string) {
204210
<div class="flex items-center gap-4">
205211
<Avatar
206212
:src="item.project.icon_url"
207-
class="rounded-2xl border border-surface-5 bg-surface-4"
213+
class="rounded-2xl border border-surface-5 bg-surface-4 !shadow-none"
208214
size="4rem"
209215
/>
210216

@@ -244,7 +250,7 @@ function toggleIssue(issueId: string) {
244250
<div class="flex items-center gap-1">
245251
<Avatar
246252
:src="item.project_owner.icon_url"
247-
class="rounded-full border border-surface-5 bg-surface-4"
253+
class="rounded-full border border-surface-5 bg-surface-4 !shadow-none"
248254
size="1.5rem"
249255
circle
250256
/>
@@ -316,17 +322,10 @@ function toggleIssue(issueId: string) {
316322

317323
<div class="border-t border-surface-3 bg-surface-2">
318324
<div v-if="currentTab === 'Thread'" class="p-4">
319-
<div v-if="true" class="flex min-h-[75px] items-center justify-center">
320-
<div class="text-center text-secondary">
321-
<p class="text-sm">
322-
Not yet implemented. See reports in prod for how thread will look (its the same)
323-
</p>
324-
</div>
325-
</div>
326-
327-
<div v-else class="flex flex-col gap-6">
328-
<!-- TODO: Report thread stuff -->
329-
</div>
325+
<ThreadView
326+
:thread="item.thread as Thread"
327+
@update-thread="handleThreadUpdate"
328+
/>
330329
</div>
331330

332331
<div v-else-if="currentTab === 'Files' && !selectedFile" class="flex flex-col">
@@ -383,11 +382,11 @@ function toggleIssue(issueId: string) {
383382
<div
384383
v-for="(issue, idx) in selectedFile.issues"
385384
:key="issue.id"
386-
class="border-x border-b border-t-0 border-solid border-surface-3 bg-surface-2"
385+
class="border-x border-b border-t-0 border-solid border-surface-3 bg-surface-2 transition-colors duration-200 hover:bg-surface-4"
387386
:class="{ 'rounded-bl-2xl rounded-br-2xl': idx === selectedFile.issues.length - 1 }"
388387
>
389388
<div
390-
class="flex cursor-pointer items-center justify-between p-4 transition-colors hover:bg-surface-3"
389+
class="flex cursor-pointer items-center justify-between p-4"
391390
@click="toggleIssue(issue.id)"
392391
>
393392
<div class="my-auto flex items-center gap-2">

apps/frontend/src/components/ui/thread/ReportThread.vue renamed to apps/frontend/src/components/ui/thread/ThreadView.vue

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
@update-thread="() => updateThreadLocal()"
1919
/>
2020
</div>
21+
<div v-else class="flex flex-col items-center justify-center space-y-3 py-12">
22+
<MessageIcon class="size-12 text-secondary" />
23+
<p class="text-lg text-secondary">No messages yet</p>
24+
</div>
2125

2226
<template v-if="reportClosed">
2327
<p class="text-secondary">This thread is closed and new messages cannot be sent to it.</p>
24-
<ButtonStyled v-if="isStaff(auth.user)" color="green" class="mt-2 w-full sm:w-auto">
28+
<ButtonStyled v-if="isStaff(auth.user)" color="green" class="mt-2">
2529
<button
26-
class="flex w-full items-center justify-center gap-2 sm:w-auto"
30+
class="w-full gap-2 sm:w-auto"
2731
@click="reopenReport()"
2832
>
2933
<CheckCircleIcon class="size-4" />
@@ -33,7 +37,7 @@
3337
</template>
3438

3539
<template v-else>
36-
<div class="mt-4">
40+
<div>
3741
<MarkdownEditor
3842
v-model="replyBody"
3943
:placeholder="sortedMessages.length > 0 ? 'Reply to thread...' : 'Send a message...'"
@@ -45,30 +49,30 @@
4549
class="mt-4 flex flex-col items-stretch justify-between gap-3 sm:flex-row sm:items-center sm:gap-2"
4650
>
4751
<div class="flex flex-col items-stretch gap-2 sm:flex-row sm:items-center">
48-
<ButtonStyled v-if="sortedMessages.length > 0" color="brand" class="w-full sm:w-auto">
52+
<ButtonStyled v-if="sortedMessages.length > 0" color="brand">
4953
<button
5054
:disabled="!replyBody"
51-
class="flex w-full items-center justify-center gap-2 sm:w-auto"
55+
class="w-full gap-2 sm:w-auto"
5256
@click="sendReply()"
5357
>
5458
<ReplyIcon class="size-4" />
5559
Reply
5660
</button>
5761
</ButtonStyled>
58-
<ButtonStyled v-else color="brand" class="w-full sm:w-auto">
62+
<ButtonStyled v-else color="brand">
5963
<button
6064
:disabled="!replyBody"
61-
class="flex w-full items-center justify-center gap-2 sm:w-auto"
65+
class="w-full gap-2 sm:w-auto"
6266
@click="sendReply()"
6367
>
6468
<SendIcon class="size-4" />
6569
Send
6670
</button>
6771
</ButtonStyled>
68-
<ButtonStyled v-if="isStaff(auth.user)" class="w-full sm:w-auto">
72+
<ButtonStyled v-if="isStaff(auth.user)">
6973
<button
7074
:disabled="!replyBody"
71-
class="flex w-full items-center justify-center gap-2 sm:w-auto"
75+
class="w-full sm:w-auto"
7276
@click="sendReply(true)"
7377
>
7478
Add note
@@ -84,18 +88,18 @@
8488

8589
<div class="flex flex-col items-stretch gap-2 sm:flex-row sm:items-center">
8690
<template v-if="isStaff(auth.user)">
87-
<ButtonStyled v-if="replyBody" color="red" class="w-full sm:w-auto">
91+
<ButtonStyled v-if="replyBody" color="red">
8892
<button
89-
class="flex w-full items-center justify-center gap-2 sm:w-auto"
93+
class="w-full gap-2 sm:w-auto"
9094
@click="closeReport(true)"
9195
>
9296
<CheckCircleIcon class="size-4" />
9397
Reply and close
9498
</button>
9599
</ButtonStyled>
96-
<ButtonStyled v-else color="red" class="w-full sm:w-auto">
100+
<ButtonStyled v-else color="red">
97101
<button
98-
class="flex w-full items-center justify-center gap-2 sm:w-auto"
102+
class="w-full gap-2 sm:w-auto"
99103
@click="closeReport()"
100104
>
101105
<CheckCircleIcon class="size-4" />
@@ -110,7 +114,7 @@
110114
</template>
111115

112116
<script setup lang="ts">
113-
import { CheckCircleIcon, ReplyIcon, SendIcon } from '@modrinth/assets'
117+
import { CheckCircleIcon, MessageIcon, ReplyIcon, SendIcon } from '@modrinth/assets'
114118
import {
115119
type ExtendedReport,
116120
reportQuickReplies,
@@ -136,11 +140,13 @@ import ThreadMessage from './ThreadMessage.vue'
136140
const { addNotification } = injectNotificationManager()
137141
138142
const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
143+
if (!props.report) return []
144+
139145
return reportQuickReplies
140146
.filter((reply) => {
141147
if (reply.shouldShow === undefined) return true
142148
if (typeof reply.shouldShow === 'function') {
143-
return reply.shouldShow(props.report)
149+
return reply.shouldShow(props.report! as ExtendedReport)
144150
}
145151
146152
return reply.shouldShow
@@ -156,13 +162,20 @@ const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
156162
157163
const props = defineProps<{
158164
thread: Thread
159-
reporter: User
160-
report: ExtendedReport
165+
reporter?: User
166+
report?: Partial<ExtendedReport> & {
167+
id?: string
168+
thread_id?: string
169+
closed?: boolean
170+
created?: string
171+
}
161172
}>()
162173
163174
async function handleQuickReply(reply: ReportQuickReply) {
175+
if (!props.report) return
176+
164177
const message =
165-
typeof reply.message === 'function' ? await reply.message(props.report) : reply.message
178+
typeof reply.message === 'function' ? await reply.message(props.report as ExtendedReport) : reply.message
166179
167180
await nextTick()
168181
setReplyContent(message)
@@ -181,8 +194,9 @@ const emit = defineEmits<{
181194
const flags = useFeatureFlags()
182195
183196
const members = computed(() => {
184-
const membersMap: Record<string, User> = {
185-
[props.reporter.id]: props.reporter,
197+
const membersMap: Record<string, User> = {}
198+
if (props.reporter) {
199+
membersMap[props.reporter.id] = props.reporter
186200
}
187201
for (const member of props.thread.members) {
188202
membersMap[member.id] = member
@@ -196,21 +210,25 @@ function setReplyContent(content: string) {
196210
}
197211
198212
const sortedMessages = computed(() => {
199-
const messages: TypeThreadMessage[] = [
200-
{
213+
const messages: TypeThreadMessage[] = []
214+
215+
// Add synthetic first message for reports
216+
if (props.report?.created && props.reporter) {
217+
messages.push({
201218
id: null,
202219
author_id: props.reporter.id,
203220
body: {
204221
type: 'text',
205-
body: props.report.body || 'Report opened.',
222+
body: props.report.body || 'Thread opened.',
206223
private: false,
207224
replying_to: null,
208225
associated_images: [],
209226
},
210227
created: props.report.created,
211228
hide_identity: false,
212-
},
213-
]
229+
})
230+
}
231+
214232
if (props.thread) {
215233
messages.push(
216234
...[...props.thread.messages].sort(
@@ -223,7 +241,7 @@ const sortedMessages = computed(() => {
223241
})
224242
225243
async function updateThreadLocal() {
226-
const threadId = props.report.thread_id
244+
const threadId = props.report?.thread_id || props.thread.id
227245
if (threadId) {
228246
try {
229247
const thread = (await useBaseFetch(`thread/${threadId}`)) as Thread
@@ -284,6 +302,8 @@ const reportClosed = computed(() => {
284302
})
285303
286304
async function closeReport(reply = false) {
305+
if (!props.report?.id) return
306+
287307
if (reply) {
288308
await sendReply()
289309
}
@@ -307,6 +327,8 @@ async function closeReport(reply = false) {
307327
}
308328
309329
async function reopenReport() {
330+
if (!props.report?.id) return
331+
310332
try {
311333
await useBaseFetch(`report/${props.report.id}`, {
312334
method: 'PATCH',

0 commit comments

Comments
 (0)