2019-09-09 09:31:08 +02:00
< template >
2020-06-25 11:36:35 +02:00
< div >
< nav class = "breadcrumb" aria -label = " breadcrumbs " v-if ="report" >
< ul >
< li >
< router -link : to = "{ name: RouteName.MODERATION }" > { { $t ( "Moderation" ) } } < / r o u t e r - l i n k >
< / li >
< li >
< router -link : to = "{ name: RouteName.REPORTS }" > { { $t ( "Reports" ) } } < / r o u t e r - l i n k >
< / li >
< li class = "is-active" >
< router -link : to = "{ name: RouteName.REPORT, params: { id: report.id } }" > { {
$t ( "Report #{reportNumber}" , { reportNumber : report . id } )
} } < / r o u t e r - l i n k >
< / li >
< / ul >
< / nav >
< section >
< b -message title = "Error" type = "is-danger" v-for ="error in errors" :key ="error" >
{ { error } }
< / b - m e s s a g e >
< div class = "container" v-if ="report" >
< div class = "buttons" >
< b -button
v - if = "report.status !== ReportStatusEnum.RESOLVED"
@ click = "updateReport(ReportStatusEnum.RESOLVED)"
type = "is-primary"
> { { $t ( "Mark as resolved" ) } } < / b - b u t t o n
>
< b -button
v - if = "report.status !== ReportStatusEnum.OPEN"
@ click = "updateReport(ReportStatusEnum.OPEN)"
type = "is-success"
> { { $t ( "Reopen" ) } } < / b - b u t t o n
>
< b -button
v - if = "report.status !== ReportStatusEnum.CLOSED"
@ click = "updateReport(ReportStatusEnum.CLOSED)"
type = "is-danger"
> { { $t ( "Close" ) } } < / b - b u t t o n
>
< / div >
< div class = "table-container" >
< table class = "table is-striped is-fullwidth" >
< tbody >
< tr >
< td > { { $t ( "Reported identity" ) } } < / td >
< td >
< router -link
: to = " {
name : RouteName . ADMIN _PROFILE ,
params : { id : report . reported . id } ,
} "
>
< img
v - if = "report.reported.avatar"
class = "image"
: src = "report.reported.avatar.url"
alt = ""
/ >
@ { { report . reported . preferredUsername } }
< / r o u t e r - l i n k >
< / td >
< / tr >
< tr >
< td > { { $t ( "Reported by" ) } } < / td >
< td v-if ="report.reporter.type === ActorType.APPLICATION" >
{ { report . reporter . domain } }
< / td >
< td v-else >
< router -link
: to = " {
name : RouteName . ADMIN _PROFILE ,
params : { id : report . reporter . id } ,
} "
>
< img
v - if = "report.reporter.avatar"
class = "image"
: src = "report.reporter.avatar.url"
alt = ""
/ >
@ { { report . reporter . preferredUsername } }
< / r o u t e r - l i n k >
< / td >
< / tr >
< tr >
< td > { { $t ( "Reported" ) } } < / td >
< td > { { report . insertedAt | formatDateTimeString } } < / td >
< / tr >
< tr v-if ="report.updatedAt !== report.insertedAt" >
< td > { { $t ( "Updated" ) } } < / td >
< td > { { report . updatedAt | formatDateTimeString } } < / td >
< / tr >
< tr >
< td > { { $t ( "Status" ) } } < / td >
< td >
< span v-if ="report.status === ReportStatusEnum.OPEN" > {{ $ t ( " Open " ) }} < / span >
< span v -else -if = " report.status = = = ReportStatusEnum.CLOSED " >
{ { $t ( "Closed" ) } }
< / span >
< span v -else -if = " report.status = = = ReportStatusEnum.RESOLVED " >
{ { $t ( "Resolved" ) } }
< / span >
< span v-else > {{ $ t ( " Unknown " ) }} < / span >
< / td >
< / tr >
< tr v-if ="report.event && report.comments.length > 0" >
< td > { { $t ( "Event" ) } } < / td >
< td >
< router -link : to = "{ name: RouteName.EVENT, params: { uuid: report.event.uuid } }" >
{ { report . event . title } }
< / r o u t e r - l i n k >
< span class = "is-pulled-right" >
<!-- < b -button - - >
<!-- tag = "router-link" -- >
<!-- type = "is-primary" -- >
<!-- : to = "{ name: RouteName.EDIT_EVENT, params: {eventId: report.event.uuid } }" -- >
<!-- icon - left = "pencil" -- >
<!-- size = "is-small" > { { $t ( 'Edit' ) } } < / b - b u t t o n > - - >
< b -button
type = "is-danger"
@ click = "confirmEventDelete()"
icon - left = "delete"
size = "is-small"
> { { $t ( "Delete" ) } } < / b - b u t t o n
>
< / span >
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< div class = "box report-content" >
< p v -if = " report.content " v -html = " nl2br ( report.content ) " / >
< p v-else > {{ $ t ( " No comment " ) }} < / p >
< / div >
< div class = "box" v-if ="report.event && report.comments.length === 0" >
< router -link : to = "{ name: RouteName.EVENT, params: { uuid: report.event.uuid } }" >
< h3 class = "title" > { { report . event . title } } < / h3 >
< p v -html = " report.event.description " / >
< / r o u t e r - l i n k >
<!-- < b -button - - >
<!-- tag = "router-link" -- >
<!-- type = "is-primary" -- >
<!-- : to = "{ name: RouteName.EDIT_EVENT, params: {eventId: report.event.uuid } }" -- >
<!-- icon - left = "pencil" -- >
<!-- size = "is-small" > { { $t ( 'Edit' ) } } < / b - b u t t o n > - - >
< b -button
type = "is-danger"
@ click = "confirmEventDelete()"
icon - left = "delete"
size = "is-small"
> { { $t ( "Delete" ) } } < / b - b u t t o n
>
< / div >
< ul v-for ="comment in report.comments" v-if="report.comments.length > 0" :key="comment.id" >
< li >
< div class = "box" v-if ="comment" >
< article class = "media" >
< div class = "media-left" >
< figure class = "image is-48x48" v-if ="comment.actor && comment.actor.avatar" >
< img :src ="comment.actor.avatar.url" alt = "Image" / >
< / figure >
< b -icon class = "media-left" v -else size = "is-large" icon = "account-circle" / >
< / div >
< div class = "media-content" >
< div class = "content" >
< span v-if ="comment.actor" >
< strong > { { comment . actor . name } } < / strong >
< small > @ { { comment . actor . preferredUsername } } < / small >
< / span >
< span v-else > {{ $ t ( " Unknown actor " ) }} < / span >
< br / >
< p v -html = " comment.text " / >
< / div >
2020-02-18 08:57:00 +01:00
< b -button
type = "is-danger"
2020-06-25 11:36:35 +02:00
@ click = "confirmCommentDelete(comment)"
2020-02-18 08:57:00 +01:00
icon - left = "delete"
size = "is-small"
> { { $t ( "Delete" ) } } < / b - b u t t o n
>
< / div >
2020-06-25 11:36:35 +02:00
< / article >
< / div >
< / li >
< / ul >
2019-11-15 18:36:47 +01:00
2020-06-25 11:36:35 +02:00
< h2 class = "title" v-if ="report.notes.length > 0" > {{ $ t ( " Notes " ) }} < / h2 >
< div class = "box note" v-for ="note in report.notes" :id="`note-${note.id}`" :key="note.id" >
< p > { { note . content } } < / p >
< router -link : to = "{ name: RouteName.ADMIN_PROFILE, params: { id: note.moderator.id } }" >
< img alt class = "image" :src ="note.moderator.avatar.url" v -if = " note.moderator.avatar " / >
@ { { note . moderator . preferredUsername } }
< / r o u t e r - l i n k >
< br / >
< small >
< a :href ="`#note-${note.id}`" v-if ="note.insertedAt" >
{ { note . insertedAt | formatDateTimeString } }
< / a >
< / small >
< / div >
2019-09-09 09:31:08 +02:00
2020-06-25 11:36:35 +02:00
< form @submit ="addNote()" >
< b -field : label = "$t('New note')" label -for = " newNoteInput " >
< b -input type = "textarea" v-model ="noteContent" id="newNoteInput" > < / b -input >
< / b - f i e l d >
< b -button type = "submit" @click ="addNote" > {{ $ t ( " Add a note " ) }} < / b -button >
< / form >
< / div >
< / section >
< / div >
2019-09-09 09:31:08 +02:00
< / template >
< script lang = "ts" >
2020-02-18 08:57:00 +01:00
import { Component , Prop , Vue } from "vue-property-decorator" ;
import { CREATE _REPORT _NOTE , REPORT , UPDATE _REPORT } from "@/graphql/report" ;
import { IReport , IReportNote , ReportStatusEnum } from "@/types/report.model" ;
import { CURRENT _ACTOR _CLIENT } from "@/graphql/actor" ;
import { IPerson , ActorType } from "@/types/actor" ;
import { DELETE _EVENT } from "@/graphql/event" ;
import { uniq } from "lodash" ;
import { nl2br } from "@/utils/html" ;
import { DELETE _COMMENT } from "@/graphql/comment" ;
import { IComment } from "@/types/comment.model" ;
import RouteName from "../../router/name" ;
2019-09-09 09:31:08 +02:00
@ Component ( {
apollo : {
report : {
query : REPORT ,
variables ( ) {
return {
id : this . reportId ,
} ;
} ,
error ( { graphQLErrors } ) {
this . errors = uniq ( graphQLErrors . map ( ( { message } ) => message ) ) ;
} ,
} ,
2019-09-11 09:59:01 +02:00
currentActor : {
query : CURRENT _ACTOR _CLIENT ,
2019-09-09 09:31:08 +02:00
} ,
} ,
2019-12-03 11:29:51 +01:00
metaInfo ( ) {
return {
2020-02-18 08:57:00 +01:00
title : this . $t ( "Report" ) as string ,
titleTemplate : "%s | Mobilizon" ,
2019-12-03 11:29:51 +01:00
} ;
} ,
2019-09-09 09:31:08 +02:00
} )
export default class Report extends Vue {
@ Prop ( { required : true } ) reportId ! : number ;
2020-02-18 08:57:00 +01:00
2019-09-09 09:31:08 +02:00
report ! : IReport ;
2020-02-18 08:57:00 +01:00
2019-09-11 09:59:01 +02:00
currentActor ! : IPerson ;
2020-02-18 08:57:00 +01:00
2019-09-09 09:31:08 +02:00
errors : string [ ] = [ ] ;
ReportStatusEnum = ReportStatusEnum ;
2020-02-18 08:57:00 +01:00
2019-10-03 12:32:20 +02:00
RouteName = RouteName ;
2020-02-18 08:57:00 +01:00
2019-12-03 11:29:51 +01:00
ActorType = ActorType ;
2020-02-18 08:57:00 +01:00
2019-11-15 18:36:47 +01:00
nl2br = nl2br ;
2019-09-09 09:31:08 +02:00
2020-02-18 08:57:00 +01:00
noteContent = "" ;
2019-09-09 09:31:08 +02:00
addNote ( ) {
try {
this . $apollo . mutate < { createReportNote : IReportNote } > ( {
mutation : CREATE _REPORT _NOTE ,
variables : {
reportId : this . report . id ,
2019-09-11 09:59:01 +02:00
moderatorId : this . currentActor . id ,
2019-09-09 09:31:08 +02:00
content : this . noteContent ,
} ,
update : ( store , { data } ) => {
if ( data == null ) return ;
2020-02-18 08:57:00 +01:00
const cachedData = store . readQuery < { report : IReport } > ( {
query : REPORT ,
variables : { id : this . report . id } ,
} ) ;
2019-09-09 09:31:08 +02:00
if ( cachedData == null ) return ;
const { report } = cachedData ;
if ( report === null ) {
2020-02-18 08:57:00 +01:00
console . error ( "Cannot update event notes cache, because of null value." ) ;
2019-09-09 09:31:08 +02:00
return ;
}
const note = data . createReportNote ;
2019-09-11 09:59:01 +02:00
note . moderator = this . currentActor ;
2019-09-09 09:31:08 +02:00
report . notes = report . notes . concat ( [ note ] ) ;
2020-02-18 08:57:00 +01:00
store . writeQuery ( {
query : REPORT ,
variables : { id : this . report . id } ,
data : { report } ,
} ) ;
2019-09-09 09:31:08 +02:00
} ,
} ) ;
2020-02-18 08:57:00 +01:00
this . noteContent = "" ;
2019-09-09 09:31:08 +02:00
} catch ( error ) {
console . error ( error ) ;
}
}
2019-12-03 11:29:51 +01:00
confirmEventDelete ( ) {
2019-09-09 09:31:08 +02:00
this . $buefy . dialog . confirm ( {
2020-02-18 08:57:00 +01:00
title : this . $t ( "Deleting event" ) as string ,
message : this . $t (
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead."
) as string ,
confirmText : this . $t ( "Delete Event" ) as string ,
type : "is-danger" ,
2019-09-09 09:31:08 +02:00
hasIcon : true ,
onConfirm : ( ) => this . deleteEvent ( ) ,
} ) ;
}
2019-12-03 11:29:51 +01:00
confirmCommentDelete ( comment : IComment ) {
this . $buefy . dialog . confirm ( {
2020-02-18 08:57:00 +01:00
title : this . $t ( "Deleting comment" ) as string ,
message : this . $t (
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone."
) as string ,
confirmText : this . $t ( "Delete Comment" ) as string ,
type : "is-danger" ,
2019-12-03 11:29:51 +01:00
hasIcon : true ,
onConfirm : ( ) => this . deleteComment ( comment ) ,
} ) ;
}
2019-09-09 09:31:08 +02:00
async deleteEvent ( ) {
if ( ! this . report . event || ! this . report . event . id ) return ;
const eventTitle = this . report . event . title ;
try {
await this . $apollo . mutate ( {
mutation : DELETE _EVENT ,
variables : {
eventId : this . report . event . id . toString ( ) ,
2019-09-11 09:59:01 +02:00
actorId : this . currentActor . id ,
2019-09-09 09:31:08 +02:00
} ,
} ) ;
this . $buefy . notification . open ( {
2020-02-18 08:57:00 +01:00
message : this . $t ( "Event {eventTitle} deleted" , {
eventTitle ,
} ) as string ,
type : "is-success" ,
position : "is-bottom-right" ,
2019-09-09 09:31:08 +02:00
duration : 5000 ,
} ) ;
} catch ( error ) {
console . error ( error ) ;
}
}
2019-12-03 11:29:51 +01:00
async deleteComment ( comment : IComment ) {
try {
await this . $apollo . mutate ( {
mutation : DELETE _COMMENT ,
variables : {
commentId : comment . id ,
actorId : this . currentActor . id ,
} ,
} ) ;
2020-02-18 08:57:00 +01:00
this . $notifier . success ( this . $t ( "Comment deleted" ) as string ) ;
2019-12-03 11:29:51 +01:00
} catch ( error ) {
console . error ( error ) ;
}
}
2019-09-09 09:31:08 +02:00
async updateReport ( status : ReportStatusEnum ) {
try {
await this . $apollo . mutate ( {
mutation : UPDATE _REPORT ,
variables : {
reportId : this . report . id ,
2019-09-11 09:59:01 +02:00
moderatorId : this . currentActor . id ,
2019-09-09 09:31:08 +02:00
status ,
} ,
update : ( store , { data } ) => {
if ( data == null ) return ;
2020-02-18 08:57:00 +01:00
const reportCachedData = store . readQuery < { report : IReport } > ( {
query : REPORT ,
variables : { id : this . report . id } ,
} ) ;
2019-09-09 09:31:08 +02:00
if ( reportCachedData == null ) return ;
const { report } = reportCachedData ;
if ( report === null ) {
2020-02-18 08:57:00 +01:00
console . error ( "Cannot update event notes cache, because of null value." ) ;
2019-09-09 09:31:08 +02:00
return ;
}
const updatedReport = data . updateReportStatus ;
report . status = updatedReport . status ;
2020-02-18 08:57:00 +01:00
store . writeQuery ( {
query : REPORT ,
variables : { id : this . report . id } ,
data : { report } ,
} ) ;
2019-09-09 09:31:08 +02:00
} ,
} ) ;
2019-11-15 18:36:47 +01:00
await this . $router . push ( { name : RouteName . REPORTS } ) ;
2019-09-09 09:31:08 +02:00
} catch ( error ) {
console . error ( error ) ;
}
}
2020-02-18 08:57:00 +01:00
// TODO make me a global function
formatDate ( value : string ) {
return value
? new Date ( value ) . toLocaleString ( undefined , {
weekday : "long" ,
year : "numeric" ,
month : "long" ,
day : "numeric" ,
} )
: null ;
2019-09-09 09:31:08 +02:00
}
2020-02-18 08:57:00 +01:00
formatTime ( value : string ) {
return value
? new Date ( value ) . toLocaleTimeString ( undefined , {
hour : "numeric" ,
minute : "numeric" ,
} )
: null ;
2019-09-09 09:31:08 +02:00
}
}
< / script >
2019-11-15 18:36:47 +01:00
< style lang = "scss" scoped >
2020-02-18 08:57:00 +01:00
@ import "@/variables.scss" ;
2019-11-15 18:36:47 +01:00
2020-02-18 08:57:00 +01:00
tbody td img . image ,
. note img . image {
display : inline ;
height : 1.5 em ;
vertical - align : text - bottom ;
}
2019-09-09 09:31:08 +02:00
2020-02-18 08:57:00 +01:00
. dialog . modal - card - foot {
justify - content : flex - end ;
}
2019-11-15 18:36:47 +01:00
2020-02-18 08:57:00 +01:00
. report - content {
border - left : 4 px solid $primary ;
}
2020-06-18 16:57:11 +02:00
. box a {
text - decoration : none ;
color : inherit ;
}
td > a {
text - decoration : none ;
}
2020-02-18 08:57:00 +01:00
< / style >