Enhance moderation checklist (#3273)

This commit is contained in:
Prospector 2025-02-19 22:00:52 -08:00 committed by GitHub
parent 9c2cd868a7
commit ca63c09a0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 475 additions and 333 deletions

View File

@ -133,6 +133,19 @@
"sidebar" "sidebar"
/ 100%; / 100%;
.normal-page__ultimate-sidebar {
grid-area: ultimate-sidebar;
position: fixed;
bottom: 1rem;
right: 1rem;
z-index: 100;
max-width: calc(100% - 2rem);
> div {
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
}
}
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
&.sidebar { &.sidebar {
grid-template: grid-template:
@ -156,6 +169,45 @@
} }
} }
@media screen and (min-width: 1400px) {
&.ultimate-sidebar {
max-width: calc(80rem + 0.75rem + 600px);
grid-template:
"header header ultimate-sidebar" auto
"content sidebar ultimate-sidebar" auto
"content dummy ultimate-sidebar" 1fr
/ 1fr 18.75rem auto;
.normal-page__header {
max-width: 80rem;
}
.normal-page__ultimate-sidebar {
position: sticky;
top: 4.5rem;
bottom: unset;
right: unset;
z-index: unset;
align-self: start;
display: flex;
height: calc(100vh - 4.5rem * 2);
> div {
box-shadow: none;
}
}
&.alt-layout {
grid-template:
"ultimate-sidebar header header" auto
"ultimate-sidebar sidebar content" auto
"ultimate-sidebar dummy content" 1fr
/ auto 18.75rem 1fr;
}
}
}
.normal-page__sidebar { .normal-page__sidebar {
grid-area: sidebar; grid-area: sidebar;
} }

View File

@ -0,0 +1,39 @@
<template>
<div class="accordion-content" :class="(baseClass ?? ``) + (collapsed ? `` : ` open`)">
<div v-bind="$attrs" :inert="collapsed">
<slot />
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
baseClass?: string;
collapsed: boolean;
}>();
defineOptions({
inheritAttrs: false,
});
</script>
<style scoped>
.accordion-content {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s ease-in-out;
}
@media (prefers-reduced-motion) {
.accordion-content {
transition: none !important;
}
}
.accordion-content.open {
grid-template-rows: 1fr;
}
.accordion-content > div {
overflow: hidden;
}
</style>

View File

@ -1,6 +1,25 @@
<template> <template>
<div class="card moderation-checklist"> <div
<h1>Moderation checklist</h1> class="moderation-checklist flex w-[600px] max-w-full flex-col rounded-2xl border-[1px] border-solid border-orange bg-bg-raised p-4 transition-all delay-200 duration-200 ease-in-out"
:class="collapsed ? `sm:max-w-[300px]` : 'sm:max-w-[600px]'"
>
<div class="flex grow-0 items-center gap-2">
<h1 class="m-0 mr-auto flex items-center gap-2 text-2xl font-extrabold text-contrast">
<ScaleIcon class="text-orange" /> Moderation
</h1>
<ButtonStyled circular color="red" color-fill="none" hover-color-fill="background">
<button v-tooltip="`Exit moderation`" @click="exitModeration">
<CrossIcon />
</button>
</ButtonStyled>
<ButtonStyled circular>
<button v-tooltip="collapsed ? `Expand` : `Collapse`" @click="emit('toggleCollapsed')">
<DropdownIcon class="transition-transform" :class="{ 'rotate-180': collapsed }" />
</button>
</ButtonStyled>
</div>
<Collapsible base-class="grow" class="flex grow flex-col" :collapsed="collapsed">
<div class="my-4 h-[1px] w-full bg-divider" />
<div v-if="done"> <div v-if="done">
<p>You are done moderating this project! There are {{ futureProjects.length }} left.</p> <p>You are done moderating this project! There are {{ futureProjects.length }} left.</p>
</div> </div>
@ -14,7 +33,7 @@
</div> </div>
</div> </div>
<div v-else-if="steps[currentStepIndex].id === 'modpack-permissions'"> <div v-else-if="steps[currentStepIndex].id === 'modpack-permissions'">
<h2 v-if="modPackData"> <h2 v-if="modPackData" class="m-0 mb-2 text-lg font-extrabold">
Modpack permissions Modpack permissions
<template v-if="modPackIndex + 1 <= modPackData.length"> <template v-if="modPackIndex + 1 <= modPackData.length">
({{ modPackIndex + 1 }} / {{ modPackData.length }}) ({{ modPackIndex + 1 }} / {{ modPackData.length }})
@ -140,27 +159,27 @@
</button> </button>
</div> </div>
</div> </div>
<div class="input-group modpack-buttons"> <div class="mt-4 flex gap-2">
<button class="btn" :disabled="modPackIndex <= 0" @click="modPackIndex -= 1"> <ButtonStyled>
<button :disabled="modPackIndex <= 0" @click="modPackIndex -= 1">
<LeftArrowIcon aria-hidden="true" /> <LeftArrowIcon aria-hidden="true" />
Previous Previous
</button> </button>
<button </ButtonStyled>
class="btn btn-blue" <ButtonStyled color="blue">
:disabled="!modPackData[modPackIndex].status" <button :disabled="!modPackData[modPackIndex].status" @click="modPackIndex += 1">
@click="modPackIndex += 1"
>
<RightArrowIcon aria-hidden="true" /> <RightArrowIcon aria-hidden="true" />
Next project Next project
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<h2>{{ steps[currentStepIndex].question }}</h2> <h2 class="m-0 mb-2 text-lg font-extrabold">{{ steps[currentStepIndex].question }}</h2>
<template v-if="steps[currentStepIndex].rules && steps[currentStepIndex].rules.length > 0"> <template v-if="steps[currentStepIndex].rules && steps[currentStepIndex].rules.length > 0">
<strong>Rules guidance:</strong> <strong>Guidance:</strong>
<ul> <ul class="mb-3 mt-2 leading-tight">
<li v-for="(rule, index) in steps[currentStepIndex].rules" :key="index"> <li v-for="(rule, index) in steps[currentStepIndex].rules" :key="index">
{{ rule }} {{ rule }}
</li> </li>
@ -169,8 +188,8 @@
<template <template
v-if="steps[currentStepIndex].examples && steps[currentStepIndex].examples.length > 0" v-if="steps[currentStepIndex].examples && steps[currentStepIndex].examples.length > 0"
> >
<strong>Examples of what to reject:</strong> <strong>Reject things like:</strong>
<ul> <ul class="mb-3 mt-2 leading-tight">
<li v-for="(example, index) in steps[currentStepIndex].examples" :key="index"> <li v-for="(example, index) in steps[currentStepIndex].examples" :key="index">
{{ example }} {{ example }}
</li> </li>
@ -180,7 +199,7 @@
v-if="steps[currentStepIndex].exceptions && steps[currentStepIndex].exceptions.length > 0" v-if="steps[currentStepIndex].exceptions && steps[currentStepIndex].exceptions.length > 0"
> >
<strong>Exceptions:</strong> <strong>Exceptions:</strong>
<ul> <ul class="mb-3 mt-2 leading-tight">
<li v-for="(exception, index) in steps[currentStepIndex].exceptions" :key="index"> <li v-for="(exception, index) in steps[currentStepIndex].exceptions" :key="index">
{{ exception }} {{ exception }}
</li> </li>
@ -189,7 +208,9 @@
<p v-if="steps[currentStepIndex].id === 'title'"> <p v-if="steps[currentStepIndex].id === 'title'">
<strong>Title:</strong> {{ project.title }} <strong>Title:</strong> {{ project.title }}
</p> </p>
<p v-if="steps[currentStepIndex].id === 'slug'"><strong>Slug:</strong> {{ project.slug }}</p> <p v-if="steps[currentStepIndex].id === 'slug'">
<strong>Slug:</strong> {{ project.slug }}
</p>
<p v-if="steps[currentStepIndex].id === 'summary'"> <p v-if="steps[currentStepIndex].id === 'summary'">
<strong>Summary:</strong> {{ project.description }} <strong>Summary:</strong> {{ project.description }}
</p> </p>
@ -272,40 +293,48 @@
</div> </div>
</div> </div>
</div> </div>
<div class="input-group modpack-buttons"> <div class="mt-auto">
<button v-if="!done" class="btn skip-btn" aria-label="Skip" @click="goToNextProject"> <div
class="mt-4 flex grow justify-between gap-2 border-0 border-t-[1px] border-solid border-divider pt-4"
>
<div class="flex items-center gap-2">
<ButtonStyled v-if="!done">
<button aria-label="Skip" @click="goToNextProject">
<ExitIcon aria-hidden="true" /> <ExitIcon aria-hidden="true" />
<template v-if="futureProjects.length > 0">Skip</template> <template v-if="futureProjects.length > 0">Skip</template>
<template v-else>Exit</template> <template v-else>Exit</template>
</button> </button>
<button v-if="currentStepIndex > 0" class="btn" @click="previousPage() && !done"> </ButtonStyled>
<ButtonStyled v-if="currentStepIndex > 0">
<button @click="previousPage() && !done">
<LeftArrowIcon aria-hidden="true" /> Previous <LeftArrowIcon aria-hidden="true" /> Previous
</button> </button>
<button </ButtonStyled>
v-if="currentStepIndex < steps.length - 1 && !done" </div>
class="btn btn-primary" <div class="flex items-center gap-2">
@click="nextPage()" <ButtonStyled v-if="currentStepIndex < steps.length - 1 && !done" color="brand">
> <button @click="nextPage()"><RightArrowIcon aria-hidden="true" /> Next</button>
<RightArrowIcon aria-hidden="true" /> Next </ButtonStyled>
</button> <ButtonStyled v-else-if="!generatedMessage" color="brand">
<button <button :disabled="loadingMessage" @click="generateMessage">
v-else-if="!generatedMessage"
class="btn btn-primary"
:disabled="loadingMessage"
@click="generateMessage"
>
<UpdatedIcon aria-hidden="true" /> Generate message <UpdatedIcon aria-hidden="true" /> Generate message
</button> </button>
</ButtonStyled>
<template v-if="generatedMessage && !done"> <template v-if="generatedMessage && !done">
<button class="btn btn-green" @click="sendMessage(project.requested_status ?? 'approved')"> <ButtonStyled color="green">
<button @click="sendMessage(project.requested_status ?? 'approved')">
<CheckIcon aria-hidden="true" /> Approve <CheckIcon aria-hidden="true" /> Approve
</button> </button>
</ButtonStyled>
<div class="joined-buttons"> <div class="joined-buttons">
<button class="btn btn-danger" @click="sendMessage('rejected')"> <ButtonStyled color="red">
<button @click="sendMessage('rejected')">
<CrossIcon aria-hidden="true" /> Reject <CrossIcon aria-hidden="true" /> Reject
</button> </button>
</ButtonStyled>
<ButtonStyled color="red">
<OverflowMenu <OverflowMenu
class="btn btn-danger btn-dropdown-animation icon-only" class="btn-dropdown-animation"
:options="[ :options="[
{ {
id: 'withhold', id: 'withhold',
@ -318,6 +347,7 @@
<DropdownIcon style="rotate: 180deg" /> <DropdownIcon style="rotate: 180deg" />
<template #withhold> <EyeOffIcon aria-hidden="true" /> Withhold </template> <template #withhold> <EyeOffIcon aria-hidden="true" /> Withhold </template>
</OverflowMenu> </OverflowMenu>
</ButtonStyled>
</div> </div>
</template> </template>
<button v-if="done" class="btn btn-primary next-project" @click="goToNextProject"> <button v-if="done" class="btn btn-primary next-project" @click="goToNextProject">
@ -325,6 +355,9 @@
</button> </button>
</div> </div>
</div> </div>
</div>
</Collapsible>
</div>
</template> </template>
<script setup> <script setup>
@ -337,9 +370,11 @@ import {
XIcon as CrossIcon, XIcon as CrossIcon,
EyeOffIcon, EyeOffIcon,
ExitIcon, ExitIcon,
ScaleIcon,
} from "@modrinth/assets"; } from "@modrinth/assets";
import { MarkdownEditor, OverflowMenu } from "@modrinth/ui"; import { ButtonStyled, MarkdownEditor, OverflowMenu } from "@modrinth/ui";
import Categories from "~/components/ui/search/Categories.vue"; import Categories from "~/components/ui/search/Categories.vue";
import Collapsible from "~/components/ui/Collapsible.vue";
const props = defineProps({ const props = defineProps({
project: { project: {
@ -355,8 +390,14 @@ const props = defineProps({
required: true, required: true,
default: () => {}, default: () => {},
}, },
collapsed: {
type: Boolean,
default: false,
},
}); });
const emit = defineEmits(["exit", "toggleCollapsed"]);
const steps = computed(() => const steps = computed(() =>
[ [
{ {
@ -1008,6 +1049,20 @@ async function sendMessage(status) {
const router = useNativeRouter(); const router = useNativeRouter();
async function exitModeration() {
await router.push({
name: "type-id",
params: {
type: "project",
id: props.project.id,
},
state: {
showChecklist: false,
},
});
emit("exit");
}
async function goToNextProject() { async function goToNextProject() {
const project = props.futureProjects[0]; const project = props.futureProjects[0];
@ -1031,23 +1086,8 @@ async function goToNextProject() {
<style scoped lang="scss"> <style scoped lang="scss">
.moderation-checklist { .moderation-checklist {
position: sticky; @media (prefers-reduced-motion) {
bottom: 0; transition: none !important;
left: 100vw;
z-index: 100;
border: 1px solid var(--color-bg-inverted);
width: 600px;
.skip-btn {
margin-right: auto;
}
.next-project {
margin-left: auto;
}
.modpack-buttons {
margin-top: 1rem;
} }
.option-selected { .option-selected {

View File

@ -21,6 +21,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
developerMode: false, developerMode: false,
showVersionFilesInTable: false, showVersionFilesInTable: false,
showAdsWithPlus: false, showAdsWithPlus: false,
alwaysShowChecklistAsPopup: true,
// Feature toggles // Feature toggles
projectTypesPrimaryNav: false, projectTypesPrimaryNav: false,

View File

@ -460,6 +460,10 @@
class="new-page sidebar" class="new-page sidebar"
:class="{ :class="{
'alt-layout': cosmetics.leftContentLayout, 'alt-layout': cosmetics.leftContentLayout,
'ultimate-sidebar':
showModerationChecklist &&
!collapsedModerationChecklist &&
!flags.alwaysShowChecklistAsPopup,
}" }"
> >
<div class="normal-page__header relative my-4"> <div class="normal-page__header relative my-4">
@ -805,14 +809,19 @@
@delete-version="deleteVersion" @delete-version="deleteVersion"
/> />
</div> </div>
</div> <div class="normal-page__ultimate-sidebar">
<ModerationChecklist <ModerationChecklist
v-if="auth.user && tags.staffRoles.includes(auth.user.role) && showModerationChecklist" v-if="auth.user && tags.staffRoles.includes(auth.user.role) && showModerationChecklist"
:project="project" :project="project"
:future-projects="futureProjects" :future-projects="futureProjects"
:reset-project="resetProject" :reset-project="resetProject"
:collapsed="collapsedModerationChecklist"
@exit="showModerationChecklist = false"
@toggle-collapsed="collapsedModerationChecklist = !collapsedModerationChecklist"
/> />
</div> </div>
</div>
</div>
</template> </template>
<script setup> <script setup>
import { import {
@ -1431,6 +1440,7 @@ async function copyId() {
const collapsedChecklist = ref(false); const collapsedChecklist = ref(false);
const showModerationChecklist = ref(false); const showModerationChecklist = ref(false);
const collapsedModerationChecklist = ref(false);
const futureProjects = ref([]); const futureProjects = ref([]);
if (import.meta.client && history && history.state && history.state.showChecklist) { if (import.meta.client && history && history.state && history.state.showChecklist) {
showModerationChecklist.value = true; showModerationChecklist.value = true;