forked from AkkomaGang/admin-fe
Merge branch 'develop' into feature/moderate-users-on-status-page
This commit is contained in:
commit
55d645394b
10 changed files with 242 additions and 11 deletions
|
@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Ability to fetch all statuses from a given instance
|
||||
- Grouped reports: now you can view reports, which are grouped by status (pagination is not implemented yet, though)
|
||||
- Ability to confirm users' emails and resend confirmation emails
|
||||
- Report notes
|
||||
- Ability to moderate users on the statuses page
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"driver.js": "0.8.1",
|
||||
"dropzone": "5.2.0",
|
||||
"echarts": "4.1.0",
|
||||
"element-ui": "^2.10.0",
|
||||
"element-ui": "^2.13.0",
|
||||
"file-saver": "1.3.8",
|
||||
"fuse.js": "3.4.2",
|
||||
"js-cookie": "2.2.0",
|
||||
|
|
|
@ -33,4 +33,23 @@ export async function fetchGroupedReports(authHost, token) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function createNote(content, reportID, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/reports/${reportID}/notes`,
|
||||
method: `post`,
|
||||
headers: authHeaders(token),
|
||||
data: { content }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteNote(noteID, reportID, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/reports/${reportID}/notes/${noteID}`,
|
||||
method: `delete`,
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}
|
||||
|
|
|
@ -240,9 +240,10 @@ export default {
|
|||
resendConfirmation: 'Resend confirmation email'
|
||||
},
|
||||
statuses: {
|
||||
statuses: 'Statuses',
|
||||
statuses: 'Statuses by instance',
|
||||
instanceFilter: 'Instance filter',
|
||||
loadMore: 'Load more'
|
||||
loadMore: 'Load more',
|
||||
noInstances: 'No other instances found'
|
||||
},
|
||||
userProfile: {
|
||||
tags: 'Tags',
|
||||
|
@ -308,7 +309,10 @@ export default {
|
|||
actors: 'Actors',
|
||||
content: 'Content',
|
||||
reportedStatus: 'Reported status',
|
||||
statusDeleted: 'This status has been deleted'
|
||||
statusDeleted: 'This status has been deleted',
|
||||
leaveNote: 'Leave a note',
|
||||
postNote: 'Send',
|
||||
deleteNote: 'Delete'
|
||||
},
|
||||
reportsFilter: {
|
||||
inputPlaceholder: 'Select filter',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { changeState, fetchReports, fetchGroupedReports } from '@/api/reports'
|
||||
import { Message } from 'element-ui'
|
||||
import { changeState, fetchReports, fetchGroupedReports, createNote, deleteNote } from '@/api/reports'
|
||||
|
||||
const reports = {
|
||||
state: {
|
||||
|
@ -79,6 +80,50 @@ const reports = {
|
|||
},
|
||||
ToggleReportsGrouping({ commit }) {
|
||||
commit('SET_REPORTS_GROUPING')
|
||||
},
|
||||
CreateReportNote({ commit, getters, state, rootState }, { content, reportID }) {
|
||||
createNote(content, reportID, getters.authHost, getters.token)
|
||||
|
||||
const optimisticNote = {
|
||||
user: {
|
||||
avatar: rootState.user.avatar,
|
||||
display_name: rootState.user.name,
|
||||
url: `${rootState.user.authHost}/${rootState.user.name}`,
|
||||
acct: rootState.user.name
|
||||
},
|
||||
content: content,
|
||||
created_at: new Date().getTime()
|
||||
}
|
||||
|
||||
const updatedReports = state.fetchedReports.map(report => {
|
||||
if (report.id === reportID) {
|
||||
report.notes = [...report.notes, optimisticNote]
|
||||
}
|
||||
|
||||
return report
|
||||
})
|
||||
|
||||
commit('SET_REPORTS', updatedReports)
|
||||
},
|
||||
DeleteReportNote({ commit, getters, state }, { noteID, reportID }) {
|
||||
deleteNote(noteID, reportID, getters.authHost, getters.token)
|
||||
|
||||
const updatedReports = state.fetchedReports.map(report => {
|
||||
if (report.id === reportID) {
|
||||
report.notes = report.notes.filter(note => note.id !== noteID)
|
||||
}
|
||||
|
||||
return report
|
||||
})
|
||||
|
||||
commit('SET_REPORTS', updatedReports)
|
||||
},
|
||||
SuccessMessage(text) {
|
||||
return Message({
|
||||
message: text,
|
||||
type: 'success',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ body {
|
|||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||
background: #FFF;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
label {
|
||||
|
|
119
src/views/reports/components/NoteCard.vue
Normal file
119
src/views/reports/components/NoteCard.vue
Normal file
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<el-card class="note-card">
|
||||
<div slot="header">
|
||||
<div class="note-header">
|
||||
<div class="note-actor-container">
|
||||
<div class="note-actor">
|
||||
<img :src="note.user.avatar" class="note-avatar-img">
|
||||
<h3 class="note-actor-name">{{ note.user.display_name }}</h3>
|
||||
</div>
|
||||
<a :href="note.user.url" target="_blank">
|
||||
@{{ note.user.acct }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<el-popconfirm
|
||||
title="Are you sure to delete this?"
|
||||
confirm-button-text="Yes"
|
||||
cancel-button-text="No"
|
||||
@onConfirm="handleNoteDeletion(note.id, report.id)">
|
||||
<el-button slot="reference" size="mini">
|
||||
{{ $t('reports.deleteNote') }}
|
||||
</el-button>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-body">
|
||||
<span class="note-content" v-html="note.content"/>
|
||||
{{ parseTimestamp(note.created_at) }}
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'NoteCard',
|
||||
props: {
|
||||
report: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('YYYY-MM-DD HH:mm')
|
||||
},
|
||||
handleNoteDeletion(noteID, reportID) {
|
||||
this.$store.dispatch('DeleteReportNote', { noteID, reportID })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel='stylesheet/scss' lang='scss'>
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.el-icon-arrow-right {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.note-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
height: 40px;
|
||||
}
|
||||
.note-actor {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.note-actor-name {
|
||||
margin: 0;
|
||||
height: 22px;
|
||||
}
|
||||
.note-avatar-img {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.note-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.note-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.note-content {
|
||||
font-size: 15px;
|
||||
}
|
||||
.note-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
.el-card__header {
|
||||
padding: 10px 17px;
|
||||
}
|
||||
.note-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 80px;
|
||||
}
|
||||
.note-actor-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.note-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -63,6 +63,23 @@
|
|||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<div class="report-notes">
|
||||
<el-collapse>
|
||||
<el-collapse-item :title="getNotesTitle(report.notes)">
|
||||
<note-card v-for="(note, index) in report.notes" :key="index" :note="note" :report="report"/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<div class="report-note-form">
|
||||
<el-input
|
||||
v-model="notes[report.id]"
|
||||
:placeholder="$t('reports.leaveNote')"
|
||||
type="textarea"
|
||||
rows="3"/>
|
||||
<div class="report-post-note">
|
||||
<el-button @click="handleNewNote(report.id)">{{ $t('reports.postNote') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
@ -81,18 +98,24 @@
|
|||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import NoteCard from './NoteCard'
|
||||
import Status from '@/components/Status'
|
||||
import ModerateUserDropdown from './ModerateUserDropdown'
|
||||
|
||||
export default {
|
||||
name: 'Report',
|
||||
components: { Status, ModerateUserDropdown },
|
||||
components: { Status, ModerateUserDropdown, NoteCard },
|
||||
props: {
|
||||
reports: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notes: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$store.state.reports.loading
|
||||
|
@ -127,11 +150,18 @@ export default {
|
|||
getStatusesTitle(statuses) {
|
||||
return `Reported statuses: ${statuses.length} item(s)`
|
||||
},
|
||||
getNotesTitle(notes = []) {
|
||||
return `Notes: ${notes.length} item(s)`
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.$store.dispatch('FetchReports', page)
|
||||
},
|
||||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('L HH:mm')
|
||||
},
|
||||
handleNewNote(reportID) {
|
||||
this.$store.dispatch('CreateReportNote', { content: this.notes[reportID], reportID })
|
||||
this.notes[reportID] = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,6 +247,13 @@ export default {
|
|||
.report-title {
|
||||
margin: 0;
|
||||
}
|
||||
.report-note-form {
|
||||
margin: 15px 0 0 0;
|
||||
}
|
||||
.report-post-note {
|
||||
margin: 5px 0 0 0;
|
||||
text-align: right;
|
||||
}
|
||||
.reports-pagination {
|
||||
margin: 25px 0;
|
||||
text-align: center;
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
{{ $t('statuses.statuses') }}
|
||||
</h1>
|
||||
<div class="filter-container">
|
||||
<el-select v-model="selectedInstance" :placeholder="$t('statuses.instanceFilter')" @change="handleFilterChange">
|
||||
<el-select
|
||||
v-model="selectedInstance"
|
||||
:placeholder="$t('statuses.instanceFilter')"
|
||||
:no-data-text="$t('statuses.noInstances')"
|
||||
@change="handleFilterChange">
|
||||
<el-option
|
||||
v-for="(instance,index) in instances"
|
||||
:key="index"
|
||||
|
|
|
@ -3456,10 +3456,10 @@ elegant-spinner@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
|
||||
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
|
||||
|
||||
element-ui@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/element-ui/-/element-ui-2.10.0.tgz#e6129f6b6d6ffe0dbad125a4a8d17d447a5f639c"
|
||||
integrity sha512-uthsnJ1CIdQvLWphr67uwFSfSYoRBjxFcEhXhy+2/EwKNsqO7MRN+mYqroNLz5WJuLqVy1aOpJ8Lv4B32qKthQ==
|
||||
element-ui@^2.13.0:
|
||||
version "2.13.0"
|
||||
resolved "https://registry.yarnpkg.com/element-ui/-/element-ui-2.13.0.tgz#f6bb04e5b0a76ea5f62466044b774407ba4ebd2d"
|
||||
integrity sha512-KYsHWsBXYbLELS8cdfvgJTOMSUby3UEjvsPV1V1VmgJ/DdkOAS4z3MiOrPxrT9w2Cc5lZ4eVSQiGhYFR5NVChw==
|
||||
dependencies:
|
||||
async-validator "~1.8.1"
|
||||
babel-helper-vue-jsx-merge-props "^2.0.0"
|
||||
|
|
Loading…
Reference in a new issue