mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 01:26:23 +00:00
chore: improve moderation ux (#6035)
* feat: save project review queue filters * reduce unnecessary network calls + prepr * missed file * ui tweaks * add fucked up * add label + prepr * prepr * update legacy badge labels * globe * fix margin * be more reasonable * pending state * fix double review, prepr * small badge text
This commit is contained in:
@@ -24,8 +24,16 @@
|
||||
</Checkbox>
|
||||
<div class="input-group push-right">
|
||||
<ButtonStyled color="orange">
|
||||
<button :disabled="!submissionConfirmation" @click="resubmit()">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="!submissionConfirmation || isLoading"
|
||||
@click="runBlockingAction('resubmit-modal', resubmit)"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'resubmit-modal'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -54,8 +62,16 @@
|
||||
</Checkbox>
|
||||
<div class="input-group push-right">
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!replyConfirmation" @click="sendReplyFromModal()">
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="!replyConfirmation || isLoading"
|
||||
@click="runBlockingAction('reply-modal', () => sendReplyFromModal())"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reply-modal'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ReplyIcon v-else aria-hidden="true" />
|
||||
Reply to thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -82,8 +98,9 @@
|
||||
<template v-if="report && report.closed">
|
||||
<p>This thread is closed and new messages cannot be sent to it.</p>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button @click="reopenReport()">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
<button :disabled="isLoading" @click="runBlockingAction('reopen', () => reopenReport())">
|
||||
<SpinnerIcon v-if="loadingAction === 'reopen'" class="animate-spin" aria-hidden="true" />
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
Reopen thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -100,35 +117,53 @@
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
v-if="sortedMessages.length > 0"
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="
|
||||
isApproved(project) && !isStaff(auth.user)
|
||||
? openReplyModal()
|
||||
: runBlockingAction('reply', () => sendReply())
|
||||
"
|
||||
>
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
<SpinnerIcon v-if="loadingAction === 'reply'" class="animate-spin" aria-hidden="true" />
|
||||
<ReplyIcon v-else aria-hidden="true" />
|
||||
Reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="
|
||||
isApproved(project) && !isStaff(auth.user)
|
||||
? openReplyModal()
|
||||
: runBlockingAction('send', () => sendReply())
|
||||
"
|
||||
>
|
||||
<SendIcon aria-hidden="true" />
|
||||
<SpinnerIcon v-if="loadingAction === 'send'" class="animate-spin" aria-hidden="true" />
|
||||
<SendIcon v-else aria-hidden="true" />
|
||||
Send
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button :disabled="!replyBody" @click="sendReply(null, true)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="runBlockingAction('private-note', () => sendReply(null, true))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'private-note'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
Add private note
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<template v-if="currentMember && !isStaff(auth.user)">
|
||||
<template v-if="isRejected(project)">
|
||||
<ButtonStyled color="orange">
|
||||
<button v-if="replyBody" @click="openResubmitModal(true)">
|
||||
<button v-if="replyBody" :disabled="isLoading" @click="openResubmitModal(true)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review with reply
|
||||
</button>
|
||||
<button v-else @click="openResubmitModal(false)">
|
||||
<button v-else :disabled="isLoading" @click="openResubmitModal(false)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
@@ -140,12 +175,30 @@
|
||||
<template v-if="report">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<ButtonStyled color="red">
|
||||
<button v-if="replyBody" @click="closeReport(true)">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
<button
|
||||
v-if="replyBody"
|
||||
:disabled="isLoading"
|
||||
@click="runBlockingAction('close-with-reply', () => closeReport(true))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'close-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
Close with reply
|
||||
</button>
|
||||
<button v-else @click="closeReport()">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
<button
|
||||
v-else
|
||||
:disabled="isLoading"
|
||||
@click="runBlockingAction('close', () => closeReport())"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'close'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
Close thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -154,92 +207,122 @@
|
||||
<template v-if="project">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<ButtonStyled v-if="replyBody" color="green">
|
||||
<button :disabled="isApproved(project)" @click="sendReply(requestedStatus)">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="isApproved(project) || isLoading"
|
||||
@click="runBlockingAction('approve-with-reply', () => sendReply(requestedStatus))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'approve-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
Approve with reply
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="green">
|
||||
<button :disabled="isApproved(project)" @click="setStatus(requestedStatus)">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="isApproved(project) || isLoading"
|
||||
@click="runBlockingAction('approve', () => setStatus(requestedStatus))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'approve'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
Approve
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="joined-buttons">
|
||||
<ButtonStyled v-if="replyBody" color="red">
|
||||
<button :disabled="project.status === 'rejected'" @click="sendReply('rejected')">
|
||||
<XIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="project.status === 'rejected' || isLoading"
|
||||
@click="runBlockingAction('reject-with-reply', () => sendReply('rejected'))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reject-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
Reject with reply
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="red">
|
||||
<button :disabled="project.status === 'rejected'" @click="setStatus('rejected')">
|
||||
<XIcon aria-hidden="true" />
|
||||
<button
|
||||
:disabled="project.status === 'rejected' || isLoading"
|
||||
@click="runBlockingAction('reject', () => setStatus('rejected'))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reject'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
Reject
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:disabled="isLoading"
|
||||
:options="
|
||||
replyBody
|
||||
? [
|
||||
{
|
||||
id: 'withhold-reply',
|
||||
color: 'danger',
|
||||
action: () => {
|
||||
sendReply('withheld')
|
||||
},
|
||||
action: () =>
|
||||
runBlockingAction('withhold-reply', () => sendReply('withheld')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld',
|
||||
disabled: project.status === 'withheld' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft-reply',
|
||||
action: () => {
|
||||
sendReply('draft')
|
||||
},
|
||||
action: () =>
|
||||
runBlockingAction('set-to-draft-reply', () => sendReply('draft')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft',
|
||||
disabled: project.status === 'draft' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review-reply',
|
||||
action: () => {
|
||||
sendReply('processing', true)
|
||||
},
|
||||
action: () =>
|
||||
runBlockingAction('send-to-review-reply', () =>
|
||||
sendReply('processing', true),
|
||||
),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing',
|
||||
disabled: project.status === 'processing' || isLoading,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'danger',
|
||||
action: () => {
|
||||
setStatus('withheld')
|
||||
},
|
||||
action: () =>
|
||||
runBlockingAction('withhold', () => setStatus('withheld')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld',
|
||||
disabled: project.status === 'withheld' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft',
|
||||
action: () => {
|
||||
setStatus('draft')
|
||||
},
|
||||
action: () =>
|
||||
runBlockingAction('set-to-draft', () => setStatus('draft')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft',
|
||||
disabled: project.status === 'draft' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () => {
|
||||
setStatus('processing')
|
||||
},
|
||||
action: () =>
|
||||
runBlockingAction('send-to-review', () => setStatus('processing')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing',
|
||||
disabled: project.status === 'processing' || isLoading,
|
||||
},
|
||||
]
|
||||
"
|
||||
>
|
||||
<DropdownIcon aria-hidden="true" />
|
||||
<SpinnerIcon v-if="isDropdownLoading" class="animate-spin" aria-hidden="true" />
|
||||
<DropdownIcon v-else aria-hidden="true" />
|
||||
<template #withhold-reply>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold with reply
|
||||
@@ -285,6 +368,7 @@ import {
|
||||
ReplyIcon,
|
||||
ScaleIcon,
|
||||
SendIcon,
|
||||
SpinnerIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
@@ -363,6 +447,30 @@ const sortedMessages = computed(() => {
|
||||
const modalSubmit = ref(null)
|
||||
const modalReply = ref(null)
|
||||
|
||||
const loadingAction = ref(null)
|
||||
const isLoading = computed(() => loadingAction.value !== null)
|
||||
const dropdownActionIds = [
|
||||
'withhold',
|
||||
'withhold-reply',
|
||||
'set-to-draft',
|
||||
'set-to-draft-reply',
|
||||
'send-to-review',
|
||||
'send-to-review-reply',
|
||||
]
|
||||
const isDropdownLoading = computed(() => dropdownActionIds.includes(loadingAction.value))
|
||||
|
||||
async function runBlockingAction(actionId, action) {
|
||||
if (loadingAction.value !== null) {
|
||||
return
|
||||
}
|
||||
loadingAction.value = actionId
|
||||
try {
|
||||
await action()
|
||||
} finally {
|
||||
loadingAction.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function updateThreadLocal() {
|
||||
let threadId = null
|
||||
if (props.project) {
|
||||
@@ -390,8 +498,8 @@ async function onUploadImage(file) {
|
||||
}
|
||||
|
||||
async function sendReplyFromModal(status = null, privateMessage = false) {
|
||||
modalReply.value.hide()
|
||||
await sendReply(status, privateMessage)
|
||||
modalReply.value.hide()
|
||||
}
|
||||
|
||||
async function sendReply(status = null, privateMessage = false) {
|
||||
|
||||
Reference in New Issue
Block a user