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
class="field"
:value="obj.type"
@input="update({type: $event})"></report-category-dropdown>
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
</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/*">Assignee</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>
<td>
<i :class="[configs[target.type].icon, 'icon']"></i>
<translate translate-context="*/*/*">{{ configs[target.type].label }}</translate>
</td>
</tr>
<tr v-if="target && target.type !== 'account'">
<td>
<translate translate-context="*/*/*">Owner</translate>
</td>
<td>
<actor-link :admin="true" :actor="obj.target_owner"></actor-link>
</td>
</tr>
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<tr v-if="obj.target_state.is_local">
<td>
<translate translate-context="Content/Moderation/*/Noun">Domain</translate>
</td>
<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>
</tr>
<tr v-for="field in targetFields" :key="field.id">
<td>{{ field.label }}</td>
<td>{{ field.repr }}</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>
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<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'
import entities from '@/entities'
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,
data () {
markdown: new showdown.Converter(),
isLoading: false,
isCollapsed: 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
}
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
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
axios.patch(url, payload).then((response) => {
self.$emit('updated', payload)
self.isLoading = false
}, error => {
self.isLoading = 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>