Newer
Older
<template>
<div class="ui fluid report card">
<div class="content">
<div class="header">
<router-link :to="{name: 'manage.moderation.reports.detail', params: {id: obj.uuid}}">
<translate translate-context="Content/Moderation/Card/Short" :translate-params="{id: obj.uuid.substring(0, 8)}">Report %{ id }</translate>
</router-link>
<collapse-link class="right floated" v-model="isCollapsed"></collapse-link>
</div>
<div class="content">
<div class="ui hidden divider"></div>
<div class="ui stackable two column grid">
<div class="column">
<table class="ui very basic unstackable table">
<tbody>
<tr>
<td>
<translate translate-context="Content/Moderation/*">Submitted by</translate>
</td>
<td>
<div v-if="obj.submitter">
<actor-link :admin="true" :actor="obj.submitter" />
</div>
<div v-else="obj.submitter_email">
{{ obj.submitter_email }}
</div>
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*">Category</translate>
</td>
<td>
<report-category-dropdown
:value="obj.type"
Eliot Berriot
committed
@input="update({type: $event})">
 
<action-feedback :is-loading="updating.type"></action-feedback>
</report-category-dropdown>
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*/Noun">Creation date</translate>
</td>
<td>
<human-date :date="obj.creation_date" :icon="true"></human-date>
</td>
</tr>
</tbody>
</table>
</div>
<div class="column">
<table class="ui very basic unstackable table">
<tbody>
<tr>
<td>
<translate translate-context="*/*/*">Status</translate>
</td>
<td v-if="obj.is_handled">
<span v-if="obj.is_handled">
<i class="green check icon"></i>
<translate translate-context="Content/*/*/Short">Resolved</translate>
</span>
</td>
<td v-else>
<i class="red x icon"></i>
<translate translate-context="Content/*/*/Short">Unresolved</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="Content/Moderation/*">Assigned to</translate>
</td>
<td>
<div v-if="obj.assigned_to">
<actor-link :admin="true" :actor="obj.assigned_to" />
</div>
<translate v-else translate-context="*/*/*">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="Content/*/*/Noun">Resolution date</translate>
</td>
<td>
<human-date v-if="obj.handled_date" :date="obj.handled_date" :icon="true"></human-date>
<translate v-else translate-context="*/*/*">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="Content/*/*/Noun">Internal notes</translate>
</td>
<td>
<i class="comment icon"></i>
{{ obj.notes.length }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="main content" v-if="!isCollapsed">
<div class="ui stackable two column grid">
<div class="column">
<h3>
<translate translate-context="Content/*/*/Short">Message</translate>
</h3>
<expandable-div v-if="obj.summary" class="summary" :content="obj.summary">
<div v-html="markdown.makeHtml(obj.summary)"></div>
</expandable-div>
</div>
<aside class="column">
<h3>
<translate translate-context="Content/*/*/Short">Reported object</translate>
</h3>
<div v-if="!obj.target" class="ui warning message">
<translate translate-context="Content/Moderation/Message">The object associated with this report was deleted.</translate>
</div>
<router-link class="ui basic button" v-if="target && configs[target.type].urls.getAdminDetail" :to="configs[target.type].urls.getAdminDetail(obj.target_state)">
<i class="wrench icon"></i>
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
</router-link>
<table class="ui very basic unstackable table">
<tbody>
<tr v-if="target">
<td>
<translate translate-context="*/*/*">Type</translate>
</td>
<i :class="[configs[target.type].icon, 'icon']"></i>
<translate translate-context="*/*/*">{{ configs[target.type].label }}</translate>
</td>
</tr>
Eliot Berriot
committed
<tr v-if="obj.target_owner && (!target || target.type !== 'account')">
<td>
<translate translate-context="*/*/*">Owner</translate>
</td>
<td>
<actor-link :admin="true" :actor="obj.target_owner"></actor-link>
</td>
Eliot Berriot
committed
<td>
<instance-policy-modal
v-if="!obj.target_owner.is_local"
class="right floated mini basic" type="actor" :target="obj.target_owner.full_username" />
</td>
</tr>
<tr v-if="target && target.type === 'account'">
<td>
<translate translate-context="*/*/*">Account</translate>
</td>
<td>
<actor-link :admin="true" :actor="obj.target_owner"></actor-link>
</td>
<td>
<instance-policy-modal
v-if="!obj.target_owner.is_local"
class="right floated mini basic" type="actor" :target="obj.target_owner.full_username" />
Eliot Berriot
committed
</td>
<tr v-if="obj.target_state.is_local">
<td>
<translate translate-context="Content/Moderation/*/Noun">Domain</translate>
</td>
<i class="home icon"></i>
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
</td>
</tr>
<tr v-else-if="obj.target_state.domain">
<td>
<router-link :to="{name: 'manage.moderation.domains.detail', params: {id: obj.target_state.domain }}">
<translate translate-context="Content/Moderation/*/Noun">Domain</translate>
</router-link>
</td>
<td>
{{ obj.target_state.domain }}
</td>
Eliot Berriot
committed
<td>
<instance-policy-modal class="right floated mini basic" type="domain" :target="obj.target_state.domain" />
Eliot Berriot
committed
</td>
</tr>
<tr v-for="field in targetFields" :key="field.id">
<td>{{ field.label }}</td>
<td colspan="2" v-if="field.repr">{{ field.repr }}</td>
<td colspan="2" v-else>
<translate translate-context="*/*/*">N/A</translate>
</td>
</tr>
</tbody>
</table>
</aside>
</div>
<div class="ui stackable two column grid">
<div class="column">
<h3>
<translate translate-context="Content/*/*/Noun">Internal notes</translate>
</h3>
<notes-thread @deleted="handleRemovedNote($event)" :notes="obj.notes" />
<note-form @created="obj.notes.push($event)" :target="{type: 'report', uuid: obj.uuid}" />
</div>
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
<div class="column">
<h3>
<translate translate-context="*/*/*">Actions</translate>
</h3>
<div class="ui labelled icon basic buttons">
<button
v-if="obj.is_handled === false"
@click="resolve(true)"
:class="['ui', {loading: isLoading}, 'button']">
<i class="green check icon"></i>
<translate translate-context="Content/*/Button.Label/Verb">Resolve</translate>
</button>
<button
v-if="obj.is_handled === true"
@click="resolve(false)"
:class="['ui', {loading: isLoading}, 'button']">
<i class="yellow redo icon"></i>
<translate translate-context="Content/*/Button.Label">Unresolve</translate>
</button>
<template v-for="action in actions">
<dangerous-button
v-if="action.dangerous"
:class="['ui', {loading: isLoading}, 'button']"
color=""
:action="action.handler">
<i :class="[action.iconColor, action.icon, 'icon']"></i>
{{ action.label }}
<p slot="modal-header">{{ action.modalHeader}}</p>
<div slot="modal-content">
<p>{{ action.modalContent }}</p>
</div>
<p slot="modal-confirm">{{ action.modalConfirmLabel }}</p>
</dangerous-button>
</template>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { diffWordsWithSpace } from 'diff'
import NoteForm from '@/components/manage/moderation/NoteForm'
import NotesThread from '@/components/manage/moderation/NotesThread'
import ReportCategoryDropdown from '@/components/moderation/ReportCategoryDropdown'
Eliot Berriot
committed
import InstancePolicyModal from '@/components/manage/moderation/InstancePolicyModal'
import entities from '@/entities'
Eliot Berriot
committed
import {setUpdate} from '@/utils'
import showdown from 'showdown'
function castValue (value) {
if (value === null || value === undefined) {
return ''
}
return String(value)
}
export default {
props: {
obj: {required: true},
currentState: {required: false}
},
components: {
NoteForm,
NotesThread,
Eliot Berriot
committed
InstancePolicyModal,
data () {
markdown: new showdown.Converter(),
isLoading: false,
isCollapsed: false,
Eliot Berriot
committed
updating: {
type: false,
}
}
},
computed: {
configs: entities.getConfigs,
previousState () {
if (this.obj.is_applied) {
// mutation was applied, we use the previous state that is stored
// on the mutation itself
return this.obj.previous_state
}
// mutation is not applied yet, so we use the current state that was
// passed to the component, if any
return this.currentState
},
detailUrl () {
if (!this.target) {
return ''
}
let namespace
let id = this.target.id
if (this.target.type === 'track') {
namespace = 'library.tracks.edit.detail'
}
if (this.target.type === 'album') {
namespace = 'library.albums.edit.detail'
}
if (this.target.type === 'artist') {
namespace = 'library.artists.edit.detail'
}
return this.$router.resolve({name: namespace, params: {id, editId: this.obj.uuid}}).href
},
targetFields () {
if (!this.target) {
return []
}
let payload = this.obj.target_state
let fields = this.configs[this.target.type].moderatedFields
let self = this
return fields.map((fieldConfig) => {
let dummyRepr = (v) => { return v }
let getValueRepr = fieldConfig.getValueRepr || dummyRepr
let d = {
id: fieldConfig.id,
label: fieldConfig.label,
value: payload[fieldConfig.id],
repr: castValue(getValueRepr(payload[fieldConfig.id])),
}
return d
})
},
target () {
if (this.obj.target) {
return this.obj.target
} else {
return this.obj.target_state._target
}
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
actions () {
if (!this.target) {
return []
}
let self = this
let actions = []
let typeConfig = this.configs[this.target.type]
if (typeConfig.getDeleteUrl) {
let deleteUrl = typeConfig.getDeleteUrl(this.target)
actions.push({
label: this.$pgettext('Content/Moderation/Button/Verb', 'Delete reported object'),
modalHeader: this.$pgettext('Content/Moderation/Popup/Header', 'Delete reported object?'),
modalContent: this.$pgettext('Content/Moderation/Popup,Paragraph', 'This will delete the object associated with this report. This action is irreversible.'),
modalConfirmLabel: this.$pgettext('*/*/*/Verb', 'Delete'),
icon: 'x',
iconColor: 'red',
dangerous: true,
handler: () => {
axios.delete(deleteUrl).then((response) => {
console.log('Target deleted')
self.obj.target = null
}, error => {
console.log('Error while deleting target')
})
}
})
}
return actions
}
},
methods: {
update (payload) {
let url = `manage/moderation/reports/${this.obj.uuid}/`
let self = this
this.isLoading = true
Eliot Berriot
committed
setUpdate(payload, this.updating, true)
axios.patch(url, payload).then((response) => {
self.$emit('updated', payload)
Eliot Berriot
committed
Object.assign(self.obj, payload)
Eliot Berriot
committed
setUpdate(payload, self.updating, false)
}, error => {
self.isLoading = false
Eliot Berriot
committed
setUpdate(payload, self.updating, false)
resolve (v) {
let url = `manage/moderation/reports/${this.obj.uuid}/`
let self = this
this.isLoading = true
axios.patch(url, {is_handled: v}).then((response) => {
self.$emit('handled', v)
self.isLoading = false
self.obj.is_handled = v
let increment
if (v) {
self.isCollapsed = true
increment = -1
} else {
increment = 1
}
self.$store.commit('ui/incrementNotifications', {count: increment, type: 'pendingReviewReports'})
}, error => {
self.isLoading = false
})
},
handleRemovedNote (uuid) {
this.obj.notes = this.obj.notes.filter((note) => {
return note.uuid != uuid
})
}
}
</script>