Enhance moderation checklist (#3273)
This commit is contained in:
parent
9c2cd868a7
commit
ca63c09a0d
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
39
apps/frontend/src/components/ui/Collapsible.vue
Normal file
39
apps/frontend/src/components/ui/Collapsible.vue
Normal 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>
|
||||||
@ -1,329 +1,362 @@
|
|||||||
<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"
|
||||||
<div v-if="done">
|
:class="collapsed ? `sm:max-w-[300px]` : 'sm:max-w-[600px]'"
|
||||||
<p>You are done moderating this project! There are {{ futureProjects.length }} left.</p>
|
>
|
||||||
|
<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>
|
</div>
|
||||||
<div v-else-if="generatedMessage">
|
<Collapsible base-class="grow" class="flex grow flex-col" :collapsed="collapsed">
|
||||||
<p>
|
<div class="my-4 h-[1px] w-full bg-divider" />
|
||||||
Enter your moderation message here. Remember to check the Moderation tab to answer any
|
<div v-if="done">
|
||||||
questions an author might have!
|
<p>You are done moderating this project! There are {{ futureProjects.length }} left.</p>
|
||||||
</p>
|
|
||||||
<div class="markdown-editor-spacing">
|
|
||||||
<MarkdownEditor v-model="message" :placeholder="'Enter moderation message'" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else-if="generatedMessage">
|
||||||
<div v-else-if="steps[currentStepIndex].id === 'modpack-permissions'">
|
<p>
|
||||||
<h2 v-if="modPackData">
|
Enter your moderation message here. Remember to check the Moderation tab to answer any
|
||||||
Modpack permissions
|
questions an author might have!
|
||||||
<template v-if="modPackIndex + 1 <= modPackData.length">
|
</p>
|
||||||
({{ modPackIndex + 1 }} / {{ modPackData.length }})
|
<div class="markdown-editor-spacing">
|
||||||
</template>
|
<MarkdownEditor v-model="message" :placeholder="'Enter moderation message'" />
|
||||||
</h2>
|
</div>
|
||||||
<div v-if="!modPackData">Loading data...</div>
|
|
||||||
<div v-else-if="modPackData.length === 0">
|
|
||||||
<p>All permissions obtained. You may skip this step!</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!modPackData[modPackIndex]">
|
<div v-else-if="steps[currentStepIndex].id === 'modpack-permissions'">
|
||||||
<p>All permission checks complete!</p>
|
<h2 v-if="modPackData" class="m-0 mb-2 text-lg font-extrabold">
|
||||||
<div class="input-group modpack-buttons">
|
Modpack permissions
|
||||||
<button class="btn" @click="modPackIndex -= 1">
|
<template v-if="modPackIndex + 1 <= modPackData.length">
|
||||||
<LeftArrowIcon aria-hidden="true" />
|
({{ modPackIndex + 1 }} / {{ modPackData.length }})
|
||||||
Previous
|
</template>
|
||||||
</button>
|
</h2>
|
||||||
|
<div v-if="!modPackData">Loading data...</div>
|
||||||
|
<div v-else-if="modPackData.length === 0">
|
||||||
|
<p>All permissions obtained. You may skip this step!</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!modPackData[modPackIndex]">
|
||||||
|
<p>All permission checks complete!</p>
|
||||||
|
<div class="input-group modpack-buttons">
|
||||||
|
<button class="btn" @click="modPackIndex -= 1">
|
||||||
|
<LeftArrowIcon aria-hidden="true" />
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="modPackData[modPackIndex].type === 'unknown'">
|
||||||
|
<p>What is the approval type of {{ modPackData[modPackIndex].file_name }}?</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<button
|
||||||
|
v-for="(option, index) in fileApprovalTypes"
|
||||||
|
:key="index"
|
||||||
|
class="btn"
|
||||||
|
:class="{
|
||||||
|
'option-selected': modPackData[modPackIndex].status === option.id,
|
||||||
|
}"
|
||||||
|
@click="modPackData[modPackIndex].status = option.id"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<template v-if="modPackData[modPackIndex].status !== 'unidentified'">
|
||||||
|
<div class="universal-labels"></div>
|
||||||
|
<label for="proof">
|
||||||
|
<span class="label__title">Proof</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="proof"
|
||||||
|
v-model="modPackData[modPackIndex].proof"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
placeholder="Enter proof of status..."
|
||||||
|
/>
|
||||||
|
<label for="link">
|
||||||
|
<span class="label__title">Link</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="link"
|
||||||
|
v-model="modPackData[modPackIndex].url"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
placeholder="Enter link of project..."
|
||||||
|
/>
|
||||||
|
<label for="title">
|
||||||
|
<span class="label__title">Title</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="title"
|
||||||
|
v-model="modPackData[modPackIndex].title"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
placeholder="Enter title of project..."
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="modPackData[modPackIndex].type === 'flame'">
|
||||||
|
<p>
|
||||||
|
What is the approval type of {{ modPackData[modPackIndex].title }} (<a
|
||||||
|
:href="modPackData[modPackIndex].url"
|
||||||
|
target="_blank"
|
||||||
|
class="text-link"
|
||||||
|
>{{ modPackData[modPackIndex].url }}</a
|
||||||
|
>?
|
||||||
|
</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<button
|
||||||
|
v-for="(option, index) in fileApprovalTypes"
|
||||||
|
:key="index"
|
||||||
|
class="btn"
|
||||||
|
:class="{
|
||||||
|
'option-selected': modPackData[modPackIndex].status === option.id,
|
||||||
|
}"
|
||||||
|
@click="modPackData[modPackIndex].status = option.id"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
['unidentified', 'no', 'with-attribution'].includes(modPackData[modPackIndex].status)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p v-if="modPackData[modPackIndex].status === 'unidentified'">
|
||||||
|
Does this project provide identification and permission for
|
||||||
|
<strong>{{ modPackData[modPackIndex].file_name }}</strong
|
||||||
|
>?
|
||||||
|
</p>
|
||||||
|
<p v-else-if="modPackData[modPackIndex].status === 'with-attribution'">
|
||||||
|
Does this project provide attribution for
|
||||||
|
<strong>{{ modPackData[modPackIndex].file_name }}</strong
|
||||||
|
>?
|
||||||
|
</p>
|
||||||
|
<p v-else>
|
||||||
|
Does this project provide proof of permission for
|
||||||
|
<strong>{{ modPackData[modPackIndex].file_name }}</strong
|
||||||
|
>?
|
||||||
|
</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<button
|
||||||
|
v-for="(option, index) in filePermissionTypes"
|
||||||
|
:key="index"
|
||||||
|
class="btn"
|
||||||
|
:class="{
|
||||||
|
'option-selected': modPackData[modPackIndex].approved === option.id,
|
||||||
|
}"
|
||||||
|
@click="modPackData[modPackIndex].approved = option.id"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex gap-2">
|
||||||
|
<ButtonStyled>
|
||||||
|
<button :disabled="modPackIndex <= 0" @click="modPackIndex -= 1">
|
||||||
|
<LeftArrowIcon aria-hidden="true" />
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled color="blue">
|
||||||
|
<button :disabled="!modPackData[modPackIndex].status" @click="modPackIndex += 1">
|
||||||
|
<RightArrowIcon aria-hidden="true" />
|
||||||
|
Next project
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="modPackData[modPackIndex].type === 'unknown'">
|
<h2 class="m-0 mb-2 text-lg font-extrabold">{{ steps[currentStepIndex].question }}</h2>
|
||||||
<p>What is the approval type of {{ modPackData[modPackIndex].file_name }}?</p>
|
<template v-if="steps[currentStepIndex].rules && steps[currentStepIndex].rules.length > 0">
|
||||||
<div class="input-group">
|
<strong>Guidance:</strong>
|
||||||
<button
|
<ul class="mb-3 mt-2 leading-tight">
|
||||||
v-for="(option, index) in fileApprovalTypes"
|
<li v-for="(rule, index) in steps[currentStepIndex].rules" :key="index">
|
||||||
:key="index"
|
{{ rule }}
|
||||||
class="btn"
|
</li>
|
||||||
:class="{
|
</ul>
|
||||||
'option-selected': modPackData[modPackIndex].status === option.id,
|
</template>
|
||||||
}"
|
<template
|
||||||
@click="modPackData[modPackIndex].status = option.id"
|
v-if="steps[currentStepIndex].examples && steps[currentStepIndex].examples.length > 0"
|
||||||
>
|
>
|
||||||
{{ option.name }}
|
<strong>Reject things like:</strong>
|
||||||
</button>
|
<ul class="mb-3 mt-2 leading-tight">
|
||||||
</div>
|
<li v-for="(example, index) in steps[currentStepIndex].examples" :key="index">
|
||||||
<template v-if="modPackData[modPackIndex].status !== 'unidentified'">
|
{{ example }}
|
||||||
<div class="universal-labels"></div>
|
</li>
|
||||||
<label for="proof">
|
</ul>
|
||||||
<span class="label__title">Proof</span>
|
</template>
|
||||||
</label>
|
<template
|
||||||
<input
|
v-if="steps[currentStepIndex].exceptions && steps[currentStepIndex].exceptions.length > 0"
|
||||||
id="proof"
|
>
|
||||||
v-model="modPackData[modPackIndex].proof"
|
<strong>Exceptions:</strong>
|
||||||
type="text"
|
<ul class="mb-3 mt-2 leading-tight">
|
||||||
autocomplete="off"
|
<li v-for="(exception, index) in steps[currentStepIndex].exceptions" :key="index">
|
||||||
placeholder="Enter proof of status..."
|
{{ exception }}
|
||||||
/>
|
</li>
|
||||||
<label for="link">
|
</ul>
|
||||||
<span class="label__title">Link</span>
|
</template>
|
||||||
</label>
|
<p v-if="steps[currentStepIndex].id === 'title'">
|
||||||
<input
|
<strong>Title:</strong> {{ project.title }}
|
||||||
id="link"
|
</p>
|
||||||
v-model="modPackData[modPackIndex].url"
|
<p v-if="steps[currentStepIndex].id === 'slug'">
|
||||||
type="text"
|
<strong>Slug:</strong> {{ project.slug }}
|
||||||
autocomplete="off"
|
</p>
|
||||||
placeholder="Enter link of project..."
|
<p v-if="steps[currentStepIndex].id === 'summary'">
|
||||||
/>
|
<strong>Summary:</strong> {{ project.description }}
|
||||||
<label for="title">
|
</p>
|
||||||
<span class="label__title">Title</span>
|
<p v-if="steps[currentStepIndex].id === 'links'">
|
||||||
</label>
|
<template v-if="project.issues_url">
|
||||||
<input
|
<strong>Issues: </strong>
|
||||||
id="title"
|
<a class="text-link" :href="project.issues_url">{{ project.issues_url }}</a> <br />
|
||||||
v-model="modPackData[modPackIndex].title"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="Enter title of project..."
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
<template v-if="project.source_url">
|
||||||
<div v-else-if="modPackData[modPackIndex].type === 'flame'">
|
<strong>Source: </strong>
|
||||||
<p>
|
<a class="text-link" :href="project.source_url">{{ project.source_url }}</a> <br />
|
||||||
What is the approval type of {{ modPackData[modPackIndex].title }} (<a
|
</template>
|
||||||
:href="modPackData[modPackIndex].url"
|
<template v-if="project.wiki_url">
|
||||||
target="_blank"
|
<strong>Wiki: </strong>
|
||||||
class="text-link"
|
<a class="text-link" :href="project.wiki_url">{{ project.wiki_url }}</a> <br />
|
||||||
>{{ modPackData[modPackIndex].url }}</a
|
</template>
|
||||||
>?
|
<template v-if="project.discord_url">
|
||||||
</p>
|
<strong>Discord: </strong>
|
||||||
<div class="input-group">
|
<a class="text-link" :href="project.discord_url">{{ project.discord_url }}</a>
|
||||||
<button
|
<br />
|
||||||
v-for="(option, index) in fileApprovalTypes"
|
</template>
|
||||||
:key="index"
|
<template v-for="(donation, index) in project.donation_urls" :key="index">
|
||||||
class="btn"
|
<strong>{{ donation.platform }}: </strong>
|
||||||
:class="{
|
<a class="text-link" :href="donation.url">{{ donation.url }}</a>
|
||||||
'option-selected': modPackData[modPackIndex].status === option.id,
|
<br />
|
||||||
}"
|
</template>
|
||||||
@click="modPackData[modPackIndex].status = option.id"
|
</p>
|
||||||
>
|
<p v-if="steps[currentStepIndex].id === 'categories'">
|
||||||
{{ option.name }}
|
<strong>Categories:</strong>
|
||||||
</button>
|
<Categories
|
||||||
</div>
|
:categories="project.categories.concat(project.additional_categories)"
|
||||||
|
:type="project.actualProjectType"
|
||||||
|
class="categories"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p v-if="steps[currentStepIndex].id === 'side-types'">
|
||||||
|
<strong>Client side:</strong> {{ project.client_side }} <br />
|
||||||
|
<strong>Server side:</strong> {{ project.server_side }}
|
||||||
|
</p>
|
||||||
|
<div class="options input-group">
|
||||||
|
<button
|
||||||
|
v-for="(option, index) in steps[currentStepIndex].options"
|
||||||
|
:key="index"
|
||||||
|
class="btn"
|
||||||
|
:class="{
|
||||||
|
'option-selected':
|
||||||
|
selectedOptions[steps[currentStepIndex].id] &&
|
||||||
|
selectedOptions[steps[currentStepIndex].id].find((x) => x.name === option.name),
|
||||||
|
}"
|
||||||
|
@click="toggleOption(steps[currentStepIndex].id, option)"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
['unidentified', 'no', 'with-attribution'].includes(modPackData[modPackIndex].status)
|
selectedOptions[steps[currentStepIndex].id] &&
|
||||||
|
selectedOptions[steps[currentStepIndex].id].length > 0
|
||||||
"
|
"
|
||||||
|
class="inputs universal-labels"
|
||||||
>
|
>
|
||||||
<p v-if="modPackData[modPackIndex].status === 'unidentified'">
|
<div
|
||||||
Does this project provide identification and permission for
|
v-for="(option, index) in selectedOptions[steps[currentStepIndex].id].filter(
|
||||||
<strong>{{ modPackData[modPackIndex].file_name }}</strong
|
(x) => x.fillers && x.fillers.length > 0,
|
||||||
>?
|
)"
|
||||||
</p>
|
:key="index"
|
||||||
<p v-else-if="modPackData[modPackIndex].status === 'with-attribution'">
|
>
|
||||||
Does this project provide attribution for
|
<div v-for="(filler, idx) in option.fillers" :key="idx">
|
||||||
<strong>{{ modPackData[modPackIndex].file_name }}</strong
|
<label :for="filler.id">
|
||||||
>?
|
<span class="label__title">
|
||||||
</p>
|
{{ filler.question }}
|
||||||
<p v-else>
|
<span v-if="filler.required" class="required">*</span>
|
||||||
Does this project provide proof of permission for
|
</span>
|
||||||
<strong>{{ modPackData[modPackIndex].file_name }}</strong
|
</label>
|
||||||
>?
|
<div v-if="filler.large" class="markdown-editor-spacing">
|
||||||
</p>
|
<MarkdownEditor v-model="filler.value" :placeholder="'Enter moderation message'" />
|
||||||
<div class="input-group">
|
</div>
|
||||||
<button
|
<input v-else :id="filler.id" v-model="filler.value" type="text" autocomplete="off" />
|
||||||
v-for="(option, index) in filePermissionTypes"
|
</div>
|
||||||
:key="index"
|
</div>
|
||||||
class="btn"
|
</div>
|
||||||
:class="{
|
</div>
|
||||||
'option-selected': modPackData[modPackIndex].approved === option.id,
|
<div class="mt-auto">
|
||||||
}"
|
<div
|
||||||
@click="modPackData[modPackIndex].approved = option.id"
|
class="mt-4 flex grow justify-between gap-2 border-0 border-t-[1px] border-solid border-divider pt-4"
|
||||||
>
|
>
|
||||||
{{ option.name }}
|
<div class="flex items-center gap-2">
|
||||||
|
<ButtonStyled v-if="!done">
|
||||||
|
<button aria-label="Skip" @click="goToNextProject">
|
||||||
|
<ExitIcon aria-hidden="true" />
|
||||||
|
<template v-if="futureProjects.length > 0">Skip</template>
|
||||||
|
<template v-else>Exit</template>
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled v-if="currentStepIndex > 0">
|
||||||
|
<button @click="previousPage() && !done">
|
||||||
|
<LeftArrowIcon aria-hidden="true" /> Previous
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ButtonStyled v-if="currentStepIndex < steps.length - 1 && !done" color="brand">
|
||||||
|
<button @click="nextPage()"><RightArrowIcon aria-hidden="true" /> Next</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled v-else-if="!generatedMessage" color="brand">
|
||||||
|
<button :disabled="loadingMessage" @click="generateMessage">
|
||||||
|
<UpdatedIcon aria-hidden="true" /> Generate message
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<template v-if="generatedMessage && !done">
|
||||||
|
<ButtonStyled color="green">
|
||||||
|
<button @click="sendMessage(project.requested_status ?? 'approved')">
|
||||||
|
<CheckIcon aria-hidden="true" /> Approve
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<div class="joined-buttons">
|
||||||
|
<ButtonStyled color="red">
|
||||||
|
<button @click="sendMessage('rejected')">
|
||||||
|
<CrossIcon aria-hidden="true" /> Reject
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled color="red">
|
||||||
|
<OverflowMenu
|
||||||
|
class="btn-dropdown-animation"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
id: 'withhold',
|
||||||
|
color: 'danger',
|
||||||
|
action: () => sendMessage('withheld'),
|
||||||
|
hoverFilled: true,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<DropdownIcon style="rotate: 180deg" />
|
||||||
|
<template #withhold> <EyeOffIcon aria-hidden="true" /> Withhold </template>
|
||||||
|
</OverflowMenu>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<button v-if="done" class="btn btn-primary next-project" @click="goToNextProject">
|
||||||
|
Next project
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group modpack-buttons">
|
|
||||||
<button class="btn" :disabled="modPackIndex <= 0" @click="modPackIndex -= 1">
|
|
||||||
<LeftArrowIcon aria-hidden="true" />
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-blue"
|
|
||||||
:disabled="!modPackData[modPackIndex].status"
|
|
||||||
@click="modPackIndex += 1"
|
|
||||||
>
|
|
||||||
<RightArrowIcon aria-hidden="true" />
|
|
||||||
Next project
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Collapsible>
|
||||||
<div v-else>
|
|
||||||
<h2>{{ steps[currentStepIndex].question }}</h2>
|
|
||||||
<template v-if="steps[currentStepIndex].rules && steps[currentStepIndex].rules.length > 0">
|
|
||||||
<strong>Rules guidance:</strong>
|
|
||||||
<ul>
|
|
||||||
<li v-for="(rule, index) in steps[currentStepIndex].rules" :key="index">
|
|
||||||
{{ rule }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
v-if="steps[currentStepIndex].examples && steps[currentStepIndex].examples.length > 0"
|
|
||||||
>
|
|
||||||
<strong>Examples of what to reject:</strong>
|
|
||||||
<ul>
|
|
||||||
<li v-for="(example, index) in steps[currentStepIndex].examples" :key="index">
|
|
||||||
{{ example }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
v-if="steps[currentStepIndex].exceptions && steps[currentStepIndex].exceptions.length > 0"
|
|
||||||
>
|
|
||||||
<strong>Exceptions:</strong>
|
|
||||||
<ul>
|
|
||||||
<li v-for="(exception, index) in steps[currentStepIndex].exceptions" :key="index">
|
|
||||||
{{ exception }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<p v-if="steps[currentStepIndex].id === 'title'">
|
|
||||||
<strong>Title:</strong> {{ project.title }}
|
|
||||||
</p>
|
|
||||||
<p v-if="steps[currentStepIndex].id === 'slug'"><strong>Slug:</strong> {{ project.slug }}</p>
|
|
||||||
<p v-if="steps[currentStepIndex].id === 'summary'">
|
|
||||||
<strong>Summary:</strong> {{ project.description }}
|
|
||||||
</p>
|
|
||||||
<p v-if="steps[currentStepIndex].id === 'links'">
|
|
||||||
<template v-if="project.issues_url">
|
|
||||||
<strong>Issues: </strong>
|
|
||||||
<a class="text-link" :href="project.issues_url">{{ project.issues_url }}</a> <br />
|
|
||||||
</template>
|
|
||||||
<template v-if="project.source_url">
|
|
||||||
<strong>Source: </strong>
|
|
||||||
<a class="text-link" :href="project.source_url">{{ project.source_url }}</a> <br />
|
|
||||||
</template>
|
|
||||||
<template v-if="project.wiki_url">
|
|
||||||
<strong>Wiki: </strong>
|
|
||||||
<a class="text-link" :href="project.wiki_url">{{ project.wiki_url }}</a> <br />
|
|
||||||
</template>
|
|
||||||
<template v-if="project.discord_url">
|
|
||||||
<strong>Discord: </strong>
|
|
||||||
<a class="text-link" :href="project.discord_url">{{ project.discord_url }}</a>
|
|
||||||
<br />
|
|
||||||
</template>
|
|
||||||
<template v-for="(donation, index) in project.donation_urls" :key="index">
|
|
||||||
<strong>{{ donation.platform }}: </strong>
|
|
||||||
<a class="text-link" :href="donation.url">{{ donation.url }}</a>
|
|
||||||
<br />
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
<p v-if="steps[currentStepIndex].id === 'categories'">
|
|
||||||
<strong>Categories:</strong>
|
|
||||||
<Categories
|
|
||||||
:categories="project.categories.concat(project.additional_categories)"
|
|
||||||
:type="project.actualProjectType"
|
|
||||||
class="categories"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<p v-if="steps[currentStepIndex].id === 'side-types'">
|
|
||||||
<strong>Client side:</strong> {{ project.client_side }} <br />
|
|
||||||
<strong>Server side:</strong> {{ project.server_side }}
|
|
||||||
</p>
|
|
||||||
<div class="options input-group">
|
|
||||||
<button
|
|
||||||
v-for="(option, index) in steps[currentStepIndex].options"
|
|
||||||
:key="index"
|
|
||||||
class="btn"
|
|
||||||
:class="{
|
|
||||||
'option-selected':
|
|
||||||
selectedOptions[steps[currentStepIndex].id] &&
|
|
||||||
selectedOptions[steps[currentStepIndex].id].find((x) => x.name === option.name),
|
|
||||||
}"
|
|
||||||
@click="toggleOption(steps[currentStepIndex].id, option)"
|
|
||||||
>
|
|
||||||
{{ option.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
selectedOptions[steps[currentStepIndex].id] &&
|
|
||||||
selectedOptions[steps[currentStepIndex].id].length > 0
|
|
||||||
"
|
|
||||||
class="inputs universal-labels"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(option, index) in selectedOptions[steps[currentStepIndex].id].filter(
|
|
||||||
(x) => x.fillers && x.fillers.length > 0,
|
|
||||||
)"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<div v-for="(filler, idx) in option.fillers" :key="idx">
|
|
||||||
<label :for="filler.id">
|
|
||||||
<span class="label__title">
|
|
||||||
{{ filler.question }}
|
|
||||||
<span v-if="filler.required" class="required">*</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div v-if="filler.large" class="markdown-editor-spacing">
|
|
||||||
<MarkdownEditor v-model="filler.value" :placeholder="'Enter moderation message'" />
|
|
||||||
</div>
|
|
||||||
<input v-else :id="filler.id" v-model="filler.value" type="text" autocomplete="off" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group modpack-buttons">
|
|
||||||
<button v-if="!done" class="btn skip-btn" aria-label="Skip" @click="goToNextProject">
|
|
||||||
<ExitIcon aria-hidden="true" />
|
|
||||||
<template v-if="futureProjects.length > 0">Skip</template>
|
|
||||||
<template v-else>Exit</template>
|
|
||||||
</button>
|
|
||||||
<button v-if="currentStepIndex > 0" class="btn" @click="previousPage() && !done">
|
|
||||||
<LeftArrowIcon aria-hidden="true" /> Previous
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="currentStepIndex < steps.length - 1 && !done"
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="nextPage()"
|
|
||||||
>
|
|
||||||
<RightArrowIcon aria-hidden="true" /> Next
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else-if="!generatedMessage"
|
|
||||||
class="btn btn-primary"
|
|
||||||
:disabled="loadingMessage"
|
|
||||||
@click="generateMessage"
|
|
||||||
>
|
|
||||||
<UpdatedIcon aria-hidden="true" /> Generate message
|
|
||||||
</button>
|
|
||||||
<template v-if="generatedMessage && !done">
|
|
||||||
<button class="btn btn-green" @click="sendMessage(project.requested_status ?? 'approved')">
|
|
||||||
<CheckIcon aria-hidden="true" /> Approve
|
|
||||||
</button>
|
|
||||||
<div class="joined-buttons">
|
|
||||||
<button class="btn btn-danger" @click="sendMessage('rejected')">
|
|
||||||
<CrossIcon aria-hidden="true" /> Reject
|
|
||||||
</button>
|
|
||||||
<OverflowMenu
|
|
||||||
class="btn btn-danger btn-dropdown-animation icon-only"
|
|
||||||
:options="[
|
|
||||||
{
|
|
||||||
id: 'withhold',
|
|
||||||
color: 'danger',
|
|
||||||
action: () => sendMessage('withheld'),
|
|
||||||
hoverFilled: true,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<DropdownIcon style="rotate: 180deg" />
|
|
||||||
<template #withhold> <EyeOffIcon aria-hidden="true" /> Withhold </template>
|
|
||||||
</OverflowMenu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<button v-if="done" class="btn btn-primary next-project" @click="goToNextProject">
|
|
||||||
Next project
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,13 +809,18 @@
|
|||||||
@delete-version="deleteVersion"
|
@delete-version="deleteVersion"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="normal-page__ultimate-sidebar">
|
||||||
|
<ModerationChecklist
|
||||||
|
v-if="auth.user && tags.staffRoles.includes(auth.user.role) && showModerationChecklist"
|
||||||
|
:project="project"
|
||||||
|
:future-projects="futureProjects"
|
||||||
|
:reset-project="resetProject"
|
||||||
|
:collapsed="collapsedModerationChecklist"
|
||||||
|
@exit="showModerationChecklist = false"
|
||||||
|
@toggle-collapsed="collapsedModerationChecklist = !collapsedModerationChecklist"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ModerationChecklist
|
|
||||||
v-if="auth.user && tags.staffRoles.includes(auth.user.role) && showModerationChecklist"
|
|
||||||
:project="project"
|
|
||||||
:future-projects="futureProjects"
|
|
||||||
:reset-project="resetProject"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user