Follows + Reports

This commit is contained in:
Jai A
2021-03-11 18:14:11 -07:00
parent 2bf08787d8
commit 98df1f5312
17 changed files with 17166 additions and 57 deletions

View File

@@ -0,0 +1,3 @@
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -15,6 +15,27 @@
}
}
.iconified-button {
padding: 0.25rem 0.5rem;
font-size: var(--font-size-sm);
display: flex;
align-items: center;
color: var(--color-button-text);
background-color: var(--color-button-bg);
border-radius: var(--size-rounded-control);
&:hover,
&:focus {
background-color: var(--color-button-bg-hover);
}
svg {
width: 1.25rem;
margin-right: 0.5rem;
}
}
.badge {
max-height: 1rem;
border-radius: 1rem;

View File

@@ -287,4 +287,3 @@ button {
@import "~assets/styles/utils.scss";
@import "~assets/styles/components.scss";
@import "~assets/styles/normalize.scss";

View File

@@ -6,4 +6,6 @@
width: 100%;
}
html { margin-left: calc(100vw - 100%); }
html {
overflow-y: scroll;
}

View File

@@ -36,8 +36,35 @@
</p>
</div>
</div>
<div class="buttons">
<nuxt-link
v-if="this.$auth.loggedIn"
:to="`/report/create?id=${mod.id}&t=mod`"
class="iconified-button"
>
<ReportIcon />
Report
</nuxt-link>
<button
v-if="userFollows && !userFollows.includes(mod.id)"
class="iconified-button"
@click="followMod"
>
<FollowIcon />
Follow
</button>
<button
v-if="userFollows && userFollows.includes(mod.id)"
class="iconified-button"
@click="unfollowMod"
>
<FollowIcon fill="currentColor" />
Unfollow
</button>
</div>
</div>
<Advertisement
v-if="mod.status === 'approved' || mod.status === 'unlisted'"
:page-url="
'https://modrinth.com/mod/' + (mod.slug ? mod.slug : mod.id)
"
@@ -105,6 +132,7 @@
<div class="mod-content">
<slot />
<Advertisement
v-if="mod.status === 'approved' || mod.status === 'unlisted'"
:page-url="
'https://modrinth.com/mod/' + (mod.slug ? mod.slug : mod.id)
"
@@ -304,6 +332,7 @@
</div>
</div>
<Advertisement
v-if="mod.status === 'approved' || mod.status === 'unlisted'"
format="rectangle"
:page-url="
'https://modrinth.com/mod/' + (mod.slug ? mod.slug : mod.id)
@@ -328,6 +357,8 @@ import ClientIcon from '~/assets/images/utils/client.svg?inline'
import ServerIcon from '~/assets/images/utils/server.svg?inline'
import FileTextIcon from '~/assets/images/utils/file-text.svg?inline'
import CodeIcon from '~/assets/images/sidebar/mod.svg?inline'
import ReportIcon from '~/assets/images/utils/report.svg?inline'
import FollowIcon from '~/assets/images/utils/heart.svg?inline'
import ExternalIcon from '~/assets/images/utils/external.svg?inline'
@@ -352,6 +383,8 @@ export default {
ServerIcon,
FileTextIcon,
CodeIcon,
ReportIcon,
FollowIcon,
},
props: {
mod: {
@@ -390,6 +423,12 @@ export default {
return []
},
},
userFollows: {
type: Array,
default() {
return null
},
},
},
methods: {
formatNumber(x) {
@@ -418,6 +457,39 @@ export default {
elem.href = url
elem.click()
},
async followMod() {
const config = {
headers: {
Authorization: this.$auth.getToken('local')
? this.$auth.getToken('local')
: '',
},
}
await axios.post(
`https://api.modrinth.com/api/v1/mod/${this.mod.id}/follow`,
{},
config
)
this.userFollows.push(this.mod.id)
},
async unfollowMod() {
const config = {
headers: {
Authorization: this.$auth.getToken('local')
? this.$auth.getToken('local')
: '',
},
}
await axios.delete(
`https://api.modrinth.com/api/v1/mod/${this.mod.id}/follow`,
config
)
this.userFollows.splice(this.userFollows.indexOf(this.mod.id), 1)
},
},
}
</script>
@@ -455,6 +527,15 @@ export default {
}
}
}
.buttons {
@extend %column;
margin: var(--spacing-card-md) var(--spacing-card-md) var(--spacing-card-md)
auto;
button {
margin: 0.2rem 0 0 0;
}
}
}
.mod-navigation {
@@ -470,7 +551,6 @@ export default {
.section {
padding: var(--spacing-card-sm);
@extend %card-spaced-b;
margin-top: var(--spacing-card-lg);
}
h3 {
@@ -481,7 +561,6 @@ export default {
display: flex;
flex-wrap: wrap;
margin-top: 0;
margin-left: 5px;
p {
max-width: 6rem;
overflow: hidden;

View File

@@ -15,7 +15,7 @@
<a v-else :href="pageUrl">{{ name }}</a>
</h2>
<p v-if="author" class="author">
by <a :href="authorUrl">{{ author }}</a>
by <nuxt-link :to="'/user/' + author">{{ author }}</nuxt-link>
</p>
</div>
<p class="description">

16654
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
<h3 class="column-grow-1">Mods</h3>
</div>
<ModCard
v-for="mod in mods"
v-for="(mod, index) in mods"
:id="mod.id"
:key="mod.id"
:author="mod.author"
@@ -24,17 +24,49 @@
>
<button
class="button column approve"
@click="changeModStatus(mod.id, 'approved')"
@click="changeModStatus(mod.id, 'approved', index)"
>
Approve
</button>
<button
class="button column reject"
@click="changeModStatus(mod.id, 'rejected')"
@click="changeModStatus(mod.id, 'rejected', index)"
>
Reject
</button>
</ModCard>
<div class="section-header">
<h3 class="column-grow-1">Reports</h3>
</div>
<div v-for="(report, index) in reports" :key="report.id" class="report">
<div class="header">
<h5 class="title">
Report for {{ report.item_type }}
<nuxt-link
:to="report.item_type + '/' + report.item_id.replace(/\W/g, '')"
>{{ report.item_id }}</nuxt-link
>
</h5>
<p
v-tooltip="
$dayjs(report.created).format(
'[Created at] YYYY-MM-DD [at] HH:mm A'
)
"
class="date"
>
Created {{ $dayjs(report.created).fromNow() }}
</p>
<button class="delete iconified-button" @click="deleteReport(index)">
Delete
</button>
</div>
<div
v-compiled-markdown="report.body"
v-highlightjs
class="markdown-body"
></div>
</div>
</DashboardPage>
</template>
@@ -62,12 +94,17 @@ export default {
await axios.get(`https://api.modrinth.com/api/v1/moderation/mods`, config)
).data
const reports = (
await axios.get(`https://api.modrinth.com/api/v1/report`, config)
).data
return {
mods,
reports,
}
},
methods: {
async changeModStatus(id, status) {
async changeModStatus(id, status, index) {
const config = {
headers: {
Authorization: this.$auth.getToken('local')
@@ -84,7 +121,23 @@ export default {
config
)
await this.$router.go(0)
this.mods.splice(index, 1)
},
async deleteReport(index) {
const config = {
headers: {
Authorization: this.$auth.getToken('local')
? this.$auth.getToken('local')
: '',
},
}
await axios.delete(
`https://api.modrinth.com/api/v1/report/${this.reports[index].id}`,
config
)
this.reports.splice(index, 1)
},
},
}
@@ -94,4 +147,24 @@ export default {
.button {
margin: 0.25rem 0;
}
.report {
@extend %card-spaced-b;
padding: var(--spacing-card-sm) var(--spacing-card-lg);
.header {
display: flex;
align-items: center;
flex-direction: row;
.title {
font-size: var(--font-size-lg);
margin: 0 0.5rem 0 0;
}
.iconified-button {
margin-left: auto;
}
}
}
</style>

View File

@@ -79,12 +79,8 @@ export default {
try {
if (index) {
const config = {
method: Object.keys(
notification.actions[index].action_route
)[0].toLowerCase(),
url: `https://api.modrinth.com/api/v1/${
Object.values(notification.actions[index].action_route)[0]
}`,
method: notification.actions[index].action_route[0].toLowerCase(),
url: `https://api.modrinth.com/api/v1/${notification.actions[index].action_route[1]}`,
headers: {
Authorization: this.$auth.getToken('local'),
},

View File

@@ -5,6 +5,7 @@
:members="members"
:current-member="currentMember"
:link-bar="[['Description', '']]"
:user-follows="userFollows"
>
<div
v-compiled-markdown="mod.body"
@@ -42,12 +43,18 @@ export default {
mod.body = (await axios.get(mod.body_url)).data
}
const [members, featuredVersions] = (
const [members, featuredVersions, userFollows] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/team/${mod.team}/members`),
axios.get(
`https://api.modrinth.com/api/v1/mod/${mod.id}/version?featured=true`
),
axios.get(
data.$auth.loggedIn
? `https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/follows`
: `https://api.modrinth.com`,
config
),
])
).map((it) => it.data)
@@ -75,6 +82,7 @@ export default {
featuredVersions,
members,
currentMember,
userFollows: userFollows.name ? null : userFollows,
}
} catch {
data.error({

View File

@@ -5,6 +5,7 @@
:members="members"
:current-member="currentMember"
:link-bar="[['New Version', 'newversion']]"
:user-follows="userFollows"
>
<div class="new-version">
<div class="controls">
@@ -157,6 +158,7 @@ export default {
featuredVersions,
selectableLoaders,
selectableVersions,
userFollows,
] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/team/${mod.team}/members`),
@@ -165,6 +167,12 @@ export default {
),
axios.get(`https://api.modrinth.com/api/v1/tag/loader`),
axios.get(`https://api.modrinth.com/api/v1/tag/game_version`),
axios.get(
data.$auth.loggedIn
? `https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/follows`
: `https://api.modrinth.com`,
config
),
])
).map((it) => it.data)
@@ -194,6 +202,7 @@ export default {
selectableLoaders,
selectableVersions,
currentMember,
userFollows: userFollows.name ? null : userFollows,
}
} catch {
data.error({

View File

@@ -5,6 +5,7 @@
:current-member="currentMember"
:featured-versions="featuredVersions"
:link-bar="[['Settings', 'settings']]"
:user-follows="userFollows"
>
<div class="section-header columns">
<h3 class="column-grow-1">General</h3>
@@ -261,7 +262,7 @@ export default {
)
).data
const [members, featuredVersions] = (
const [members, featuredVersions, userFollows] = (
await Promise.all([
axios.get(
`https://api.modrinth.com/api/v1/team/${mod.team}/members`,
@@ -270,6 +271,12 @@ export default {
axios.get(
`https://api.modrinth.com/api/v1/mod/${mod.id}/version?featured=true`
),
axios.get(
data.$auth.loggedIn
? `https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/follows`
: `https://api.modrinth.com`,
config
),
])
).map((it) => it.data)
@@ -299,6 +306,7 @@ export default {
featuredVersions,
members,
currentMember,
userFollows: userFollows.name ? null : userFollows,
}
} catch {
data.error({

View File

@@ -10,6 +10,7 @@
[version.name, 'versions/' + version.id],
['Edit Version', 'versions/' + version.id + '/edit'],
]"
:user-follows="userFollows"
>
<div class="new-version">
<div class="controls">
@@ -147,6 +148,7 @@ export default {
featuredVersions,
selectableLoaders,
selectableVersions,
userFollows,
] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/team/${mod.team}/members`),
@@ -156,6 +158,12 @@ export default {
),
axios.get(`https://api.modrinth.com/api/v1/tag/loader`),
axios.get(`https://api.modrinth.com/api/v1/tag/game_version`),
axios.get(
data.$auth.loggedIn
? `https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/follows`
: `https://api.modrinth.com`,
config
),
])
).map((it) => it.data)
@@ -202,6 +210,7 @@ export default {
currentMember,
selectableLoaders,
selectableVersions,
userFollows: userFollows.name ? null : userFollows,
}
} catch {
data.error({

View File

@@ -9,6 +9,7 @@
['Versions', 'versions'],
[version.name, 'versions/' + version.id],
]"
:user-follows="userFollows"
>
<div class="version">
<div class="version-header">
@@ -27,13 +28,25 @@
</span>
<Categories :categories="version.loaders" />
<div class="buttons">
<button v-if="currentMember" class="action" @click="deleteVersion">
<nuxt-link
v-if="this.$auth.loggedIn"
:to="`/report/create?id=${version.id}&t=version`"
class="action iconified-button"
>
<ReportIcon />
Report
</nuxt-link>
<button
v-if="currentMember"
class="action iconified-button"
@click="deleteVersion"
>
<TrashIcon />
Delete
</button>
<nuxt-link
v-if="currentMember"
class="action"
class="action iconified-button"
:to="version.id + '/edit'"
>
<EditIcon />
@@ -42,7 +55,7 @@
<a
v-if="primaryFile"
:href="primaryFile.url"
class="action"
class="action iconified-button"
@click.prevent="
downloadFile(primaryFile.hashes.sha1, primaryFile.url)
"
@@ -125,6 +138,7 @@ import EditIcon from '~/assets/images/utils/edit.svg?inline'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import CalendarIcon from '~/assets/images/utils/calendar.svg?inline'
import TagIcon from '~/assets/images/utils/tag.svg?inline'
import ReportIcon from '~/assets/images/utils/report.svg?inline'
export default {
components: {
@@ -136,6 +150,7 @@ export default {
TagIcon,
TrashIcon,
EditIcon,
ReportIcon,
},
auth: false,
async asyncData(data) {
@@ -155,13 +170,19 @@ export default {
)
).data
const [members, versions, featuredVersions] = (
const [members, versions, featuredVersions, userFollows] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/team/${mod.team}/members`),
axios.get(`https://api.modrinth.com/api/v1/mod/${mod.id}/version`),
axios.get(
`https://api.modrinth.com/api/v1/mod/${mod.id}/version?featured=true`
),
axios.get(
data.$auth.loggedIn
? `https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/follows`
: `https://api.modrinth.com`,
config
),
])
).map((it) => it.data)
@@ -206,6 +227,7 @@ export default {
version,
primaryFile,
currentMember,
userFollows: userFollows.name ? null : userFollows,
}
} catch {
data.error({
@@ -397,22 +419,7 @@ export default {
margin-left: auto;
.action {
padding: 0.5rem;
color: var(--color-button-text);
background-color: var(--color-button-bg);
display: flex;
align-items: center;
border-radius: var(--size-rounded-sm);
margin: 0 0 0 0.5rem;
&:hover,
&:focus {
background-color: var(--color-button-bg-hover);
}
svg {
margin-right: 0.25rem;
}
}
}
}

View File

@@ -6,6 +6,7 @@
:members="members"
:current-member="currentMember"
:link-bar="[['Versions', 'versions']]"
:user-follows="userFollows"
>
<table>
<thead>
@@ -111,13 +112,19 @@ export default {
)
).data
const [members, versions, featuredVersions] = (
const [members, versions, featuredVersions, userFollows] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/team/${mod.team}/members`),
axios.get(`https://api.modrinth.com/api/v1/mod/${mod.id}/version`),
axios.get(
`https://api.modrinth.com/api/v1/mod/${mod.id}/version?featured=true`
),
axios.get(
data.$auth.loggedIn
? `https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/follows`
: `https://api.modrinth.com`,
config
),
])
).map((it) => it.data)
@@ -146,6 +153,7 @@ export default {
featuredVersions,
members,
currentMember,
userFollows: userFollows.name ? null : userFollows,
}
} catch {
data.error({

252
pages/report/create.vue Normal file
View File

@@ -0,0 +1,252 @@
<template>
<div class="page-container">
<div class="page-contents">
<header class="columns">
<h2 class="column-grow-1">File a report</h2>
<button
title="Create"
class="brand-button column"
:disabled="!this.$nuxt.$loading"
@click="createReport"
>
Create
</button>
</header>
<section class="info">
<h3>Item ID</h3>
<label>
<span>
The ID of the item you are reporting. For example, the item ID of a
mod would be it's mod ID.
</span>
<input v-model="itemId" type="text" placeholder="Enter the Item ID" />
</label>
<h3>Item Type</h3>
<label>
<span> The type of the item that is being reported </span>
<multiselect
id="item-type"
v-model="itemType"
:options="['mod', 'version', 'user']"
:multiple="false"
:searchable="false"
:show-no-results="false"
:show-labels="false"
placeholder="Choose item type"
/>
</label>
<h3>Report Type</h3>
<label>
<span>
The type of report. This is the category that this report falls
under.
</span>
<multiselect
id="report-type"
v-model="reportType"
:options="reportTypes"
:multiple="false"
:searchable="false"
:show-no-results="false"
:show-labels="false"
placeholder="Choose report type"
/>
</label>
</section>
<section class="description">
<h3>
<label
for="body"
title="You can type the of the long form of your description here."
>
Body
</label>
</h3>
<span>
You can type the of the long form of your description here. This
editor supports markdown. You can find the syntax
<a
href="https://guides.github.com/features/mastering-markdown/"
target="_blank"
rel="noopener noreferrer"
>here</a
>.
</span>
<div class="columns">
<div class="textarea-wrapper">
<textarea id="body" v-model="body"></textarea>
</div>
<div v-compiled-markdown="body" class="markdown-body"></div>
</div>
</section>
</div>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
import axios from 'axios'
export default {
components: {
Multiselect,
},
fetch() {
if (this.$route.query.id) this.itemId = this.$route.query.id
if (this.$route.query.t) this.itemType = this.$route.query.t
},
async asyncData() {
const reportTypes = (
await axios.get(`https://api.modrinth.com/api/v1/tag/report_type`)
).data
return {
reportTypes,
}
},
data() {
return {
itemId: '',
itemType: '',
reportType: '',
body: '',
reportTypes: ['aaaa'],
}
},
methods: {
async createReport() {
this.$nuxt.$loading.start()
try {
const config = {
headers: {
Authorization: this.$auth.getToken('local')
? this.$auth.getToken('local')
: '',
},
}
const data = {
report_type: this.reportType,
item_id: this.itemId,
item_type: this.itemType,
body: this.body,
}
await axios.post('https://api.modrinth.com/api/v1/report', data, config)
await this.$router.replace(`/${this.itemType}/${this.itemId}`)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}
this.$nuxt.$loading.finish()
},
},
}
</script>
<style lang="scss" scoped>
.title {
* {
display: inline;
}
.button {
margin-left: 1rem;
}
}
label {
display: flex;
span {
flex: 2;
padding-right: var(--spacing-card-lg);
}
input,
.multiselect,
.input-group {
flex: 3;
height: fit-content;
}
}
.textarea-wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
textarea {
flex: 1;
overflow-y: auto;
resize: none;
max-width: 100%;
}
}
.page-contents {
display: grid;
grid-template:
'header header header' auto
'info info info' auto
'description description description' auto
'footer footer footer' auto
/ 4fr 1fr 4fr;
column-gap: var(--spacing-card-md);
row-gap: var(--spacing-card-md);
}
header {
@extend %card;
grid-area: header;
padding: var(--spacing-card-md) var(--spacing-card-lg);
h2 {
margin: auto 0;
color: var(--color-text-dark);
font-weight: var(--font-weight-extrabold);
}
button {
margin-left: 0.5rem;
}
}
section {
@extend %card;
padding: var(--spacing-card-md) var(--spacing-card-lg);
}
section.info {
grid-area: info;
}
section.description {
grid-area: description;
& > .columns {
align-items: stretch;
min-height: 10rem;
max-height: 40rem;
& > * {
flex: 1;
max-width: 50%;
}
}
.markdown-body {
overflow-y: auto;
padding: 0 var(--spacing-card-sm);
}
}
</style>

View File

@@ -17,6 +17,16 @@
</div>
</div>
<p v-if="user.bio" class="bio">{{ user.bio }}</p>
<div class="buttons">
<nuxt-link
v-if="this.$auth.loggedIn"
:to="`/report/create?id=${user.id}&t=user`"
class="iconified-button"
>
<ReportIcon />
Report
</nuxt-link>
</div>
</div>
<div class="card stats">
<div class="stat">
@@ -76,6 +86,7 @@ import axios from 'axios'
import SearchResult from '@/components/ProjectCard'
import MFooter from '@/components/MFooter'
import ReportIcon from '~/assets/images/utils/report.svg?inline'
import CalendarIcon from '~/assets/images/utils/calendar.svg?inline'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import Advertisement from '~/components/Advertisement'
@@ -88,6 +99,7 @@ export default {
CalendarIcon,
DownloadIcon,
MFooter,
ReportIcon,
},
async asyncData(data) {
const config = {
@@ -176,6 +188,13 @@ export default {
}
}
}
.buttons {
@extend %column;
.iconified-button {
max-width: 4.5rem;
}
}
.stats {
display: flex;
flex-wrap: wrap;