2019-01-21 15:08:22 +01:00
< template >
2019-10-01 20:10:53 +02:00
< div class = "container" >
2019-12-03 11:29:51 +01:00
< b -loading :active.sync ="$apollo.loading" / >
2019-10-08 22:27:14 +02:00
< transition appear name = "fade" mode = "out-in" >
2019-11-15 18:36:47 +01:00
< div >
2019-10-14 11:41:57 +02:00
< div class = "header-picture" v -if = " event.picture " : style = "`background-image: url('${event.picture.url}')`" / >
< div class = "header-picture-default" v -else / >
2019-12-20 13:04:34 +01:00
< section class = "section" >
2019-10-08 22:27:14 +02:00
< div class = "title-and-participate-button" >
< div class = "title-wrapper" >
< div class = "date-component" >
2019-12-03 11:29:51 +01:00
< date -calendar -icon :date ="event.beginsOn" / >
2019-04-03 17:29:03 +02:00
< / div >
2019-10-11 15:06:58 +02:00
< div class = "title-and-informations" >
< h1 class = "title" > { { event . title } } < / h1 >
< span >
2019-11-08 19:37:14 +01:00
< router -link v-if ="actorIsOrganizer && event.draft === false" :to="{ name: RouteName.PARTICIPATIONS, params: {eventId: event.uuid}}" >
2019-10-25 17:43:37 +02:00
< small v-if ="event.participantStats.going > 0 && !actorIsParticipant" >
{ { $tc ( 'One person is going' , event . participantStats . going , { approved : event . participantStats . going } ) } }
2019-10-16 10:24:02 +02:00
< / small >
2019-10-25 17:43:37 +02:00
< small v -else -if = " event.participantStats.going > 0 && actorIsParticipant " >
{ { $tc ( 'You and one other person are going to this event' , event . participantStats . participant , { approved : event . participantStats . participant } ) } }
2019-10-16 10:24:02 +02:00
< / small >
< / r o u t e r - l i n k >
2019-10-25 17:43:37 +02:00
< small v-if ="event.participantStats.going > 0 && !actorIsParticipant && !actorIsOrganizer" >
{ { $tc ( 'One person is going' , event . participantStats . going , { approved : event . participantStats . going } ) } }
2019-10-11 15:06:58 +02:00
< / small >
2019-10-25 17:43:37 +02:00
< small v -else -if = " event.participantStats.going > 0 && actorIsParticipant && ! actorIsOrganizer " >
{ { $tc ( 'You and one other person are going to this event' , event . participantStats . participant , { approved : event . participantStats . participant } ) } }
2019-10-11 15:06:58 +02:00
< / small >
< small v-if ="event.options.maximumAttendeeCapacity" >
{ { $tc ( 'All the places have already been taken' , numberOfPlacesStillAvailable , { places : numberOfPlacesStillAvailable } ) } }
< / small >
2019-12-20 13:04:34 +01:00
< b -tooltip type = "is-dark" v-if ="!event.local" :label="$t('The actual number of participants may differ, as this event is hosted on another instance.')" >
< b -icon size = "is-small" icon = "help-circle-outline" / >
< / b - t o o l t i p >
2019-10-11 15:06:58 +02:00
< / span >
< / div >
2019-10-08 22:27:14 +02:00
< / div >
2019-10-11 15:06:58 +02:00
< div class = "event-participation has-text-right" v-if ="new Date(endDate) > new Date()" >
2019-10-08 22:27:14 +02:00
< participation -button
2019-12-20 13:04:34 +01:00
v - if = "anonymousParticipation === null && (config.anonymous.participation.allowed || (currentActor.id && !actorIsOrganizer && !event.draft && (eventCapacityOK || actorIsParticipant) && event.status !== EventStatus.CANCELLED))"
2019-10-08 22:27:14 +02:00
: participation = "participations[0]"
2019-12-20 13:04:34 +01:00
: event = "event"
2019-10-08 22:27:14 +02:00
: current - actor = "currentActor"
@ joinEvent = "joinEvent"
@ joinModal = "isJoinModalActive = true"
@ confirmLeave = "confirmLeave"
/ >
2019-12-20 13:04:34 +01:00
< b -button type = "is-text" v-if ="anonymousParticipation !== null" @click="cancelAnonymousParticipation" > {{ $ t ( ' Cancel anonymous participation ' ) }} < / b -button >
< small v-if ="anonymousParticipation" >
{ { $t ( 'You are participating in this event anonymously' ) } }
< b -tooltip : label = "$t('This information is saved only on your computer. Click for details')" >
< router -link : to = "{ name: RouteName.TERMS }" >
< b -icon size = "is-small" icon = "help-circle-outline" / >
< / r o u t e r - l i n k >
< / b - t o o l t i p >
< / small >
< small v -else -if = " anonymousParticipation = = = false " >
{ { $t ( "You are participating in this event anonymously but didn't confirm participation" ) } }
< b -tooltip : label = "$t('This information is saved only on your computer. Click for details')" >
< router -link : to = "{ name: RouteName.TERMS }" >
< b -icon size = "is-small" icon = "help-circle-outline" / >
< / r o u t e r - l i n k >
< / b - t o o l t i p >
< / small >
2019-10-08 22:27:14 +02:00
< / div >
< div v-else >
< button class = "button is-primary" type = "button" slot = "trigger" disabled >
< template >
< span > { { $t ( 'Event already passed' ) } } < / span >
< / template >
2019-12-03 11:29:51 +01:00
< b -icon icon = "menu-down" / >
2019-10-08 22:27:14 +02:00
< / button >
2019-04-03 17:29:03 +02:00
< / div >
< / div >
2019-10-08 22:27:14 +02:00
< div class = "metadata columns" >
< div class = "column is-three-quarters-desktop" >
2019-10-13 13:56:24 +02:00
< p class = "tags" >
2019-10-08 22:27:14 +02:00
< b -tag type = "is-warning" size = "is-medium" v-if ="event.draft" > {{ $ t ( ' Draft ' ) }} < / b -tag >
2019-10-11 15:58:46 +02:00
< span class = "event-status" v-if ="event.status !== EventStatus.CONFIRMED" >
< b -tag type = "is-warning" v-if ="event.status === EventStatus.TENTATIVE" > {{ $ t ( ' Event to be confirmed ' ) }} < / b -tag >
< b -tag type = "is-danger" v-if ="event.status === EventStatus.CANCELLED" > {{ $ t ( ' Event cancelled ' ) }} < / b -tag >
< / span >
< span class = "visibility" v-if ="!event.draft" >
2019-10-08 22:27:14 +02:00
< b -tag type = "is-info" v-if ="event.visibility === EventVisibility.PUBLIC" > {{ $ t ( ' Public event ' ) }} < / b -tag >
< b -tag type = "is-info" v-if ="event.visibility === EventVisibility.UNLISTED" > {{ $ t ( ' Private event ' ) }} < / b -tag >
< / span >
2019-12-03 11:29:51 +01:00
< span v-if ="!event.local" >
2019-12-20 13:04:34 +01:00
< a :href ="event.url" >
< b -tag type = "is-primary" > { { event . organizerActor . domain } } < / b - t a g >
< / a >
2019-12-03 11:29:51 +01:00
< / span >
2019-10-23 12:36:11 +02:00
< router -link
v - if = "event.tags && event.tags.length > 0"
v - for = "tag in event.tags"
: key = "tag.title"
: to = "{ name: RouteName.TAG, params: { tag: tag.title } }"
>
< b -tag type = "is-success" > { { tag . title } } < / b - t a g >
< / r o u t e r - l i n k >
2019-04-03 17:29:03 +02:00
< / p >
2019-10-08 22:27:14 +02:00
< div class = "date-and-add-to-calendar" >
< div class = "date-and-privacy" v-if ="event.beginsOn" >
< b -icon icon = "calendar-clock" / >
2019-10-14 19:29:18 +02:00
< event -full -date :beginsOn ="event.beginsOn" :show-start-time ="event.options.showStartTime" :show-end-time ="event.options.showEndTime" :endsOn ="event.endsOn" / >
2019-10-08 22:27:14 +02:00
< / div >
< a class = "add-to-calendar" @click ="downloadIcsEvent()" v-if ="!event.draft" >
< b -icon icon = "calendar-plus" / >
{ { $t ( 'Add to my calendar' ) } }
2019-09-09 09:31:08 +02:00
< / a >
2019-10-08 22:27:14 +02:00
< / div >
< p class = "slug" >
{ { event . slug } }
2019-09-09 09:31:08 +02:00
< / p >
2019-04-03 17:29:03 +02:00
< / div >
2019-10-08 22:27:14 +02:00
< div class = "column sidebar" >
< div class = "field has-addons" v-if ="currentActor.id" >
< p class = "control" v-if ="actorIsOrganizer || event.draft" >
< router -link
class = "button"
: to = "{ name: RouteName.EDIT_EVENT, params: {eventId: event.uuid}}"
>
{ { $t ( 'Edit' ) } }
< / r o u t e r - l i n k >
< / p >
< p class = "control" v-if ="actorIsOrganizer || event.draft" >
< a class = "button is-danger" @click ="openDeleteEventModalWrapper" >
{ { $t ( 'Delete' ) } }
< / a >
< / p >
< p class = "control" >
< a class = "button is-danger" @ click = "isReportModalActive = true" >
{ { $t ( 'Report' ) } }
< / a >
< / p >
2019-04-03 17:29:03 +02:00
< / div >
2019-10-08 22:27:14 +02:00
< div class = "address-wrapper" >
2019-11-08 19:37:14 +01:00
< span v-if ="!physicalAddress" >
< b -icon icon = "map" / >
{ { $t ( 'No address defined' ) } }
< / span >
< div class = "address" v-if ="physicalAddress" >
< span >
< b -icon :icon ="physicalAddress.poiInfos.poiIcon.icon" / >
< address >
< span class = "addressDescription" :title ="physicalAddress.poiInfos.name" > { { physicalAddress . poiInfos . name } } < / span >
< span > { { physicalAddress . poiInfos . alternativeName } } < / span >
< / address >
< / span >
< span class = "map-show-button" @ click = "showMap = !showMap" v-if ="physicalAddress && physicalAddress.geom" >
2019-10-08 22:27:14 +02:00
{ { $t ( 'Show map' ) } }
< / span >
2019-04-03 17:29:03 +02:00
< / div >
2019-11-08 19:37:14 +01:00
< b -modal v-if ="physicalAddress && physicalAddress.geom" :active.sync="showMap" scroll="keep" >
2019-10-08 22:27:14 +02:00
< div class = "map" >
< map -leaflet
2019-11-08 19:37:14 +01:00
: coords = "physicalAddress.geom"
: marker = "{ text: physicalAddress.fullName, icon: physicalAddress.poiInfos.poiIcon.icon }"
2019-10-08 22:27:14 +02:00
/ >
< / div >
< / b - m o d a l >
< / div >
2019-10-15 09:57:08 +02:00
< span class = "online-address" v-if ="event.onlineAddress && urlToHostname(event.onlineAddress)" >
2019-12-03 11:29:51 +01:00
< b -icon icon = "link" / >
2019-11-04 15:32:55 +01:00
< a
target = "_blank"
rel = "noopener noreferrer"
: href = "event.onlineAddress"
: title = "$t('View page on {hostname} (in a new window)', {hostname: urlToHostname(event.onlineAddress) })"
> { { urlToHostname ( event . onlineAddress ) } } < / a >
2019-10-14 14:38:21 +02:00
< / span >
2019-10-08 22:27:14 +02:00
< div class = "organizer" >
< span >
< span v-if ="event.organizerActor" >
2020-02-18 08:47:41 +01:00
{ { $t ( 'By @{username}' , { username : event . organizerActor . preferredUsername } ) } }
2019-10-08 22:27:14 +02:00
< / span >
< figure v-if ="event.organizerActor.avatar" class="image is-48x48" >
< img
class = "is-rounded"
: src = "event.organizerActor.avatar.url"
: alt = "event.organizerActor.avatar.alt" / >
< / figure >
2019-09-12 11:34:01 +02:00
< / span >
2019-10-08 22:27:14 +02:00
< / div >
2019-04-03 17:29:03 +02:00
< / div >
< / div >
2019-10-08 22:27:14 +02:00
< / section >
2019-12-20 13:04:34 +01:00
< section class = "description section" : class = "{ exists: event.description }" >
2019-10-08 22:27:14 +02:00
< div class = "description-container container" >
2020-02-18 08:47:41 +01:00
< subtitle >
2019-10-08 22:27:14 +02:00
{ { $t ( 'About this event' ) } }
2020-02-18 08:47:41 +01:00
< / subtitle >
2019-10-08 22:27:14 +02:00
< p v-if ="!event.description" >
{ { $t ( "The event organizer didn't add any description." ) } }
< / p >
< div class = "columns" v-else >
2019-10-23 12:36:11 +02:00
< div class = "column is-half description-content" ref = "eventDescriptionElement" v-html ="event.description" >
2019-10-08 22:27:14 +02:00
< / div >
2019-04-03 17:29:03 +02:00
< / div >
< / div >
2019-12-20 13:04:34 +01:00
< / section >
< section class = "comments section" ref = "commentsObserver" >
2019-11-15 18:36:47 +01:00
< a href = "#comments" >
2020-02-18 08:47:41 +01:00
< subtitle id = "comments" > { { $t ( 'Comments' ) } } < / subtitle >
2019-11-15 18:36:47 +01:00
< / a >
< comment -tree v -if = " loadComments " :event ="event" / >
< / section >
2019-12-20 13:04:34 +01:00
< section class = "share section" v-if ="!event.draft" >
2019-10-08 22:27:14 +02:00
< div class = "container" >
2019-11-05 15:51:34 +01:00
< div class = "columns is-centered is-multiline" >
< div class = "column is-half-widescreen has-text-centered" >
2019-10-08 22:27:14 +02:00
< h3 class = "title" > { { $t ( 'Share this event' ) } } < / h3 >
2019-10-11 15:06:58 +02:00
< small class = "maximumNumberOfPlacesWarning" v-if ="!eventCapacityOK" >
{ { $t ( 'All the places have already been taken' ) } }
< / small >
2019-10-08 22:27:14 +02:00
< div >
2019-10-14 14:39:31 +02:00
<!-- < b -icon icon = "mastodon" size = "is-large" type = "is-primary" / > -- >
2019-11-19 12:04:35 +01:00
2019-10-08 22:27:14 +02:00
< a :href ="twitterShareUrl" target = "_blank" rel = "nofollow noopener" > < b -icon icon = "twitter" size = "is-large" type = "is-primary" / > < / a >
2019-11-19 12:04:35 +01:00
< a :href ="facebookShareUrl" target = "_blank" rel = "nofollow noopener" > < b -icon icon = "facebook" size = "is-large" type = "is-primary" / > < / a >
< a :href ="linkedInShareUrl" target = "_blank" rel = "nofollow noopener" > < b -icon icon = "linkedin" size = "is-large" type = "is-primary" / > < / a >
< a :href ="diasporaShareUrl" class = "diaspora" target = "_blank" rel = "nofollow noopener" >
< span data -v -5e15e80a = " " class = "icon has-text-primary is-large" >
< img svg -inline src = "../../assets/diaspora-icon.svg" alt = "diaspora-logo" / >
< / span >
< / a >
2019-10-08 22:27:14 +02:00
< a :href ="emailShareUrl" target = "_blank" rel = "nofollow noopener" > < b -icon icon = "email" size = "is-large" type = "is-primary" / > < / a >
<!-- TODO : mailto : links are not used anymore , we should provide a popup to redact a message instead -- >
< / div >
< / div >
< hr / >
2019-11-05 15:51:34 +01:00
< div class = "column is-half-widescreen has-text-right add-to-calendar" >
2019-11-15 18:36:47 +01:00
< img src = "../../assets/undraw_events.svg" class = "is-hidden-mobile is-hidden-tablet-only" alt = "" / >
2019-10-08 22:27:14 +02:00
< h3 @click ="downloadIcsEvent()" >
{ { $t ( 'Add to my calendar' ) } }
< / h3 >
2019-04-12 17:00:55 +02:00
< / div >
2019-04-03 17:29:03 +02:00
< / div >
< / div >
2019-10-08 22:27:14 +02:00
< / section >
2019-12-20 13:04:34 +01:00
< section class = "more-events section container" v-if ="event.relatedEvents.length > 0" >
2019-10-08 22:27:14 +02:00
< h3 class = "title has-text-centered" > { { $t ( 'These events may interest you' ) } } < / h3 >
< div class = "columns" >
< div class = "column is-one-third-desktop" v-for ="relatedEvent in event.relatedEvents" :key="relatedEvent.uuid" >
< EventCard :event ="relatedEvent" / >
< / div >
2019-01-21 15:08:22 +01:00
< / div >
2019-10-08 22:27:14 +02:00
< / section >
< b -modal :active.sync ="isReportModalActive" has -modal -card ref = "reportModal" >
< report -modal :on-confirm ="reportEvent" : title = "$t('Report this event')" :outside-domain ="event.organizerActor.domain" @close ="$refs.reportModal.close()" / >
< / b - m o d a l >
< b -modal :active.sync ="isJoinModalActive" has -modal -card ref = "participationModal" >
< identity -picker v-model ="identity" >
< template v -slot : footer >
< footer class = "modal-card-foot" >
< button
class = "button"
ref = "cancelButton"
@ click = "isJoinModalActive = false" >
{ { $t ( 'Cancel' ) } }
< / button >
< button
class = "button is-primary"
ref = "confirmButton"
@ click = "joinEvent(identity)" >
{ { $t ( 'Confirm my particpation' ) } }
< / button >
< / footer >
< / template >
< / i d e n t i t y - p i c k e r >
< / b - m o d a l >
2019-01-21 15:08:22 +01:00
< / div >
2019-10-08 22:27:14 +02:00
< / transition >
< / div >
2019-01-21 15:08:22 +01:00
< / template >
< script lang = "ts" >
2019-12-03 11:29:51 +01:00
import {
EVENT _PERSON _PARTICIPATION ,
EVENT _PERSON _PARTICIPATION _SUBSCRIPTION _CHANGED ,
FETCH _EVENT ,
JOIN _EVENT ,
LEAVE _EVENT ,
} from '@/graphql/event' ;
import { Component , Prop , Watch } from 'vue-property-decorator' ;
2019-09-11 09:59:01 +02:00
import { CURRENT _ACTOR _CLIENT } from '@/graphql/actor' ;
2019-11-15 18:36:47 +01:00
import { EventModel , EventStatus , EventVisibility , IEvent , IParticipant , ParticipantRole } from '@/types/event.model' ;
2019-09-26 16:38:58 +02:00
import { IPerson , Person } from '@/types/actor' ;
2019-03-21 20:23:42 +01:00
import { GRAPHQL _API _ENDPOINT } from '@/api/_entrypoint' ;
2019-04-03 17:29:03 +02:00
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue' ;
import BIcon from 'buefy/src/components/icon/Icon.vue' ;
import EventCard from '@/components/Event/EventCard.vue' ;
import EventFullDate from '@/components/Event/EventFullDate.vue' ;
2019-08-09 11:32:14 +02:00
import ActorLink from '@/components/Account/ActorLink.vue' ;
2019-09-09 09:31:08 +02:00
import ReportModal from '@/components/Report/ReportModal.vue' ;
import { IReport } from '@/types/report.model' ;
import { CREATE _REPORT } from '@/graphql/report' ;
2019-09-18 17:32:37 +02:00
import EventMixin from '@/mixins/event' ;
2019-09-26 16:38:58 +02:00
import IdentityPicker from '@/views/Account/IdentityPicker.vue' ;
import ParticipationButton from '@/components/Event/ParticipationButton.vue' ;
2019-10-02 19:14:39 +02:00
import { GraphQLError } from 'graphql' ;
import { RouteName } from '@/router' ;
2019-11-08 19:37:14 +01:00
import { Address } from '@/types/address.model' ;
2019-11-15 18:36:47 +01:00
import CommentTree from '@/components/Comment/CommentTree.vue' ;
import 'intersection-observer' ;
2019-12-20 13:04:34 +01:00
import { CONFIG } from '@/graphql/config' ;
import {
AnonymousParticipationNotFoundError ,
getLeaveTokenForParticipation ,
isParticipatingInThisEvent ,
removeAnonymousParticipation ,
} from '@/services/AnonymousParticipationStorage' ;
import { IConfig } from '@/types/config.model' ;
2020-02-18 08:47:41 +01:00
import Subtitle from '@/components/Utils/Subtitle.vue' ;
2019-01-21 15:08:22 +01:00
@ Component ( {
2019-03-22 17:35:07 +01:00
components : {
2020-02-18 08:47:41 +01:00
Subtitle ,
2019-08-09 11:32:14 +02:00
ActorLink ,
2019-04-03 17:29:03 +02:00
EventFullDate ,
EventCard ,
BIcon ,
DateCalendarIcon ,
2019-09-09 09:31:08 +02:00
ReportModal ,
2019-09-26 16:38:58 +02:00
IdentityPicker ,
ParticipationButton ,
2019-11-15 18:36:47 +01:00
CommentTree ,
2019-05-31 15:13:07 +02:00
// tslint:disable:space-in-parens
'map-leaflet' : ( ) => import ( /* webpackChunkName: "map" */ '@/components/Map.vue' ) ,
// tslint:enable
2019-03-22 17:35:07 +01:00
} ,
2019-01-21 15:08:22 +01:00
apollo : {
event : {
query : FETCH _EVENT ,
variables ( ) {
return {
2019-03-22 10:57:14 +01:00
uuid : this . uuid ,
2019-01-21 15:08:22 +01:00
} ;
2019-03-22 10:57:14 +01:00
} ,
2019-10-02 19:14:39 +02:00
error ( { graphQLErrors } ) {
this . handleErrors ( graphQLErrors ) ;
} ,
2019-01-21 15:08:22 +01:00
} ,
2019-09-11 09:59:01 +02:00
currentActor : {
query : CURRENT _ACTOR _CLIENT ,
2019-03-22 10:57:14 +01:00
} ,
2019-09-26 16:38:58 +02:00
participations : {
query : EVENT _PERSON _PARTICIPATION ,
variables ( ) {
return {
eventId : this . event . id ,
2019-10-04 18:28:25 +02:00
actorId : this . currentActor . id ,
2019-09-26 16:38:58 +02:00
} ;
} ,
2019-12-03 11:29:51 +01:00
subscribeToMore : {
document : EVENT _PERSON _PARTICIPATION _SUBSCRIPTION _CHANGED ,
variables ( ) {
return {
eventId : this . event . id ,
actorId : this . currentActor . id ,
} ;
} ,
} ,
2019-09-26 16:38:58 +02:00
update : ( data ) => {
if ( data && data . person ) return data . person . participations ;
return [ ] ;
} ,
2019-10-04 18:28:25 +02:00
skip ( ) {
2019-10-07 16:48:13 +02:00
return ! this . currentActor || ! this . event || ! this . event . id || ! this . currentActor . id ;
2019-10-04 18:28:25 +02:00
} ,
2019-09-26 16:38:58 +02:00
} ,
2019-12-20 13:04:34 +01:00
config : CONFIG ,
2019-03-22 10:57:14 +01:00
} ,
2019-10-10 16:47:38 +02:00
metaInfo ( ) {
return {
// if no subcomponents specify a metaInfo.title, this title will be used
// @ts-ignore
title : this . eventTitle ,
// all titles will be injected into this template
titleTemplate : '%s | Mobilizon' ,
2019-10-14 12:56:37 +02:00
meta : [
// @ts-ignore
{ name : 'description' , content : this . eventDescription } ,
] ,
2019-10-10 16:47:38 +02:00
} ;
} ,
2019-01-21 15:08:22 +01:00
} )
2019-09-18 17:32:37 +02:00
export default class Event extends EventMixin {
2019-01-21 15:08:22 +01:00
@ Prop ( { type : String , required : true } ) uuid ! : string ;
2019-11-15 18:36:47 +01:00
event : IEvent = new EventModel ( ) ;
2019-09-11 09:59:01 +02:00
currentActor ! : IPerson ;
2019-09-26 16:38:58 +02:00
identity : IPerson = new Person ( ) ;
2019-12-20 13:04:34 +01:00
config ! : IConfig ;
2019-09-26 16:38:58 +02:00
participations : IParticipant [ ] = [ ] ;
2019-12-03 11:29:51 +01:00
oldParticipationRole ! : String ;
2019-04-03 17:29:03 +02:00
showMap : boolean = false ;
2019-09-09 09:31:08 +02:00
isReportModalActive : boolean = false ;
2019-09-11 09:59:01 +02:00
isJoinModalActive : boolean = false ;
2019-04-03 17:29:03 +02:00
EventVisibility = EventVisibility ;
2019-10-11 15:58:46 +02:00
EventStatus = EventStatus ;
2019-10-03 12:32:20 +02:00
RouteName = RouteName ;
2019-11-15 18:36:47 +01:00
observer ! : IntersectionObserver ;
loadComments : boolean = false ;
2019-12-20 13:04:34 +01:00
anonymousParticipation : boolean | null = null ;
2019-09-26 16:38:58 +02:00
2019-10-10 16:47:38 +02:00
get eventTitle ( ) {
if ( ! this . event ) return undefined ;
return this . event . title ;
}
2019-10-14 12:56:37 +02:00
get eventDescription ( ) {
if ( ! this . event ) return undefined ;
return this . event . description ;
}
2019-12-20 13:04:34 +01:00
async mounted ( ) {
2019-09-26 16:38:58 +02:00
this . identity = this . currentActor ;
2019-11-15 18:36:47 +01:00
if ( this . $route . hash . includes ( '#comment-' ) ) {
this . loadComments = true ;
}
2019-12-20 13:04:34 +01:00
try {
this . anonymousParticipation = await this . anonymousParticipationConfirmed ( ) ;
} catch ( e ) {
if ( e instanceof AnonymousParticipationNotFoundError ) {
this . anonymousParticipation = null ;
} else {
console . error ( e ) ;
}
}
2019-11-15 18:36:47 +01:00
this . observer = new IntersectionObserver ( ( entries ) => {
for ( const entry of entries ) {
if ( entry ) {
this . loadComments = entry . isIntersecting || this . loadComments ;
}
}
} , {
rootMargin : '-50px 0px -50px' ,
} ) ;
this . observer . observe ( this . $refs . commentsObserver as Element ) ;
2019-10-23 12:36:11 +02:00
this . $watch ( 'eventDescription' , function ( eventDescription ) {
if ( ! eventDescription ) return ;
2019-11-15 18:36:47 +01:00
const eventDescriptionElement = this . $refs . eventDescriptionElement as HTMLElement ;
2019-10-23 12:36:11 +02:00
eventDescriptionElement . addEventListener ( 'click' , ( $event ) => {
// TODO: Find the right type for target
let { target } : { target : any } = $event ;
while ( target && target . tagName !== 'A' ) target = target . parentNode ;
// handle only links that occur inside the component and do not reference external resources
if ( target && target . matches ( '.hashtag' ) && target . href ) {
// some sanity checks taken from vue-router:
// https://github.com/vuejs/vue-router/blob/dev/src/components/link.js#L106
const { altKey , ctrlKey , metaKey , shiftKey , button , defaultPrevented } = $event ;
// don't handle with control keys
if ( metaKey || altKey || ctrlKey || shiftKey ) return ;
// don't handle when preventDefault called
if ( defaultPrevented ) return ;
// don't handle right clicks
if ( button !== undefined && button !== 0 ) return ;
// don't handle if `target="_blank"`
if ( target && target . getAttribute ) {
const linkTarget = target . getAttribute ( 'target' ) ;
if ( /\b_blank\b/i . test ( linkTarget ) ) return ;
}
// don't handle same page links/anchors
const url = new URL ( target . href ) ;
const to = url . pathname ;
if ( window . location . pathname !== to && $event . preventDefault ) {
$event . preventDefault ( ) ;
this . $router . push ( to ) ;
}
}
} ) ;
} ) ;
2019-09-26 16:38:58 +02:00
}
2019-01-21 15:08:22 +01:00
2019-09-18 17:32:37 +02:00
/ * *
* Delete the event , then redirect to home .
* /
async openDeleteEventModalWrapper ( ) {
await this . openDeleteEventModal ( this . event , this . currentActor ) ;
2019-01-21 15:08:22 +01:00
}
2019-09-09 09:31:08 +02:00
async reportEvent ( content : string , forward : boolean ) {
this . isReportModalActive = false ;
2019-09-11 09:59:01 +02:00
if ( ! this . event . organizerActor ) return ;
2019-09-09 09:31:08 +02:00
const eventTitle = this . event . title ;
try {
await this . $apollo . mutate < IReport > ( {
mutation : CREATE _REPORT ,
variables : {
eventId : this . event . id ,
2019-11-15 18:36:47 +01:00
reporterId : this . currentActor . id ,
reportedId : this . event . organizerActor . id ,
2019-09-09 09:31:08 +02:00
content ,
2019-12-03 11:29:51 +01:00
forward ,
2019-09-09 09:31:08 +02:00
} ,
} ) ;
2019-12-03 11:29:51 +01:00
this . $notifier . success ( this . $t ( 'Event {eventTitle} reported' , { eventTitle } ) as string ) ;
2019-09-09 09:31:08 +02:00
} catch ( error ) {
console . error ( error ) ;
}
}
2019-09-11 09:59:01 +02:00
async joinEvent ( identity : IPerson ) {
this . isJoinModalActive = false ;
2019-01-21 15:08:22 +01:00
try {
2019-10-13 10:51:22 +02:00
const { data } = await this . $apollo . mutate < { joinEvent : IParticipant } > ( {
2019-02-22 11:24:41 +01:00
mutation : JOIN _EVENT ,
variables : {
2019-02-25 17:20:06 +01:00
eventId : this . event . id ,
2019-09-11 09:59:01 +02:00
actorId : identity . id ,
2019-02-22 11:24:41 +01:00
} ,
2019-09-02 18:52:23 +02:00
update : ( store , { data } ) => {
if ( data == null ) return ;
2019-09-26 16:38:58 +02:00
const participationCachedData = store . readQuery < { person : IPerson } > ( {
query : EVENT _PERSON _PARTICIPATION ,
2019-10-11 15:06:58 +02:00
variables : { eventId : this . event . id , actorId : identity . id } ,
2019-09-26 16:38:58 +02:00
} ) ;
if ( participationCachedData == null ) return ;
const { person } = participationCachedData ;
if ( person === null ) {
console . error ( 'Cannot update participation cache, because of null value.' ) ;
return ;
}
person . participations . push ( data . joinEvent ) ;
store . writeQuery ( {
query : EVENT _PERSON _PARTICIPATION ,
2019-10-11 15:06:58 +02:00
variables : { eventId : this . event . id , actorId : identity . id } ,
2019-09-26 16:38:58 +02:00
data : { person } ,
} ) ;
2019-09-02 18:52:23 +02:00
const cachedData = store . readQuery < { event : IEvent } > ( { query : FETCH _EVENT , variables : { uuid : this . event . uuid } } ) ;
if ( cachedData == null ) return ;
const { event } = cachedData ;
2019-02-22 11:24:41 +01:00
if ( event === null ) {
2019-02-25 17:20:06 +01:00
console . error ( 'Cannot update event participant cache, because of null value.' ) ;
2019-03-22 10:57:14 +01:00
return ;
2019-02-22 11:24:41 +01:00
}
2019-09-26 16:38:58 +02:00
if ( data . joinEvent . role === ParticipantRole . NOT _APPROVED ) {
2019-10-25 17:43:37 +02:00
event . participantStats . notApproved = event . participantStats . notApproved + 1 ;
2019-09-26 16:38:58 +02:00
} else {
2019-10-25 17:43:37 +02:00
event . participantStats . going = event . participantStats . going + 1 ;
event . participantStats . participant = event . participantStats . participant + 1 ;
2019-09-26 16:38:58 +02:00
}
2019-02-22 11:24:41 +01:00
2019-09-26 16:38:58 +02:00
store . writeQuery ( { query : FETCH _EVENT , variables : { uuid : this . uuid } , data : { event } } ) ;
2019-03-22 10:57:14 +01:00
} ,
2019-01-21 15:08:22 +01:00
} ) ;
2019-10-13 10:51:22 +02:00
if ( data ) {
2019-12-03 11:29:51 +01:00
if ( data . joinEvent . role === ParticipantRole . NOT _APPROVED ) {
this . participationRequestedMessage ( ) ;
} else {
this . participationConfirmedMessage ( ) ;
}
2019-10-13 10:51:22 +02:00
}
2019-01-21 15:08:22 +01:00
} catch ( error ) {
console . error ( error ) ;
}
}
2019-09-11 09:59:01 +02:00
confirmLeave ( ) {
this . $buefy . dialog . confirm ( {
2019-09-20 18:22:03 +02:00
title : this . $t ( 'Leaving event "{title}"' , { title : this . event . title } ) as string ,
message : this . $t ( 'Are you sure you want to cancel your participation at event "{title}"?' , { title : this . event . title } ) as string ,
confirmText : this . $t ( 'Leave event' ) as string ,
cancelText : this . $t ( 'Cancel' ) as string ,
2019-09-11 09:59:01 +02:00
type : 'is-danger' ,
hasIcon : true ,
2019-12-20 13:04:34 +01:00
onConfirm : ( ) => {
if ( this . currentActor . id ) {
this . leaveEvent ( this . event , this . currentActor . id ) ;
}
} ,
2019-09-11 09:59:01 +02:00
} ) ;
}
2019-12-03 11:29:51 +01:00
@ Watch ( 'participations' )
watchParticipations ( ) {
if ( this . participations . length > 0 ) {
if ( this . oldParticipationRole
&& this . participations [ 0 ] . role !== ParticipantRole . NOT _APPROVED
&& this . oldParticipationRole !== this . participations [ 0 ] . role ) {
switch ( this . participations [ 0 ] . role ) {
case ParticipantRole . PARTICIPANT :
this . participationConfirmedMessage ( ) ;
break ;
case ParticipantRole . REJECTED :
this . participationRejectedMessage ( ) ;
break ;
default :
this . participationChangedMessage ( ) ;
break ;
}
}
this . oldParticipationRole = this . participations [ 0 ] . role ;
}
}
private participationConfirmedMessage ( ) {
this . $notifier . success ( this . $t ( 'Your participation has been confirmed' ) as string ) ;
}
private participationRequestedMessage ( ) {
this . $notifier . success ( this . $t ( 'Your participation has been requested' ) as string ) ;
}
private participationRejectedMessage ( ) {
this . $notifier . error ( this . $t ( 'Your participation has been rejected' ) as string ) ;
}
private participationChangedMessage ( ) {
this . $notifier . info ( this . $t ( 'Your participation status has been changed' ) as string ) ;
}
2019-03-21 20:23:42 +01:00
async downloadIcsEvent ( ) {
const data = await ( await fetch ( ` ${ GRAPHQL _API _ENDPOINT } /events/ ${ this . uuid } /export/ics ` ) ) . text ( ) ;
const blob = new Blob ( [ data ] , { type : 'text/calendar' } ) ;
const link = document . createElement ( 'a' ) ;
link . href = window . URL . createObjectURL ( blob ) ;
link . download = ` ${ this . event . title } .ics ` ;
document . body . appendChild ( link ) ;
link . click ( ) ;
document . body . removeChild ( link ) ;
2019-01-21 15:08:22 +01:00
}
2019-10-02 19:14:39 +02:00
async handleErrors ( errors : GraphQLError ) {
2019-10-13 13:56:24 +02:00
if ( errors [ 0 ] . message . includes ( 'not found' ) || errors [ 0 ] . message . includes ( 'has invalid value $uuid' ) ) {
2019-10-02 19:14:39 +02:00
await this . $router . push ( { name : RouteName . PAGE _NOT _FOUND } ) ;
}
}
2019-09-26 16:38:58 +02:00
get actorIsParticipant ( ) {
if ( this . actorIsOrganizer ) return true ;
2019-02-22 11:24:41 +01:00
2019-09-26 16:38:58 +02:00
return this . participations . length > 0 && this . participations [ 0 ] . role === ParticipantRole . PARTICIPANT ;
2019-01-21 15:08:22 +01:00
}
2019-02-22 11:24:41 +01:00
2019-09-26 16:38:58 +02:00
get actorIsOrganizer ( ) {
return this . participations . length > 0 && this . participations [ 0 ] . role === ParticipantRole . CREATOR ;
2019-01-21 15:08:22 +01:00
}
2019-04-03 17:29:03 +02:00
2019-10-02 17:59:07 +02:00
get endDate ( ) {
return this . event . endsOn !== null && this . event . endsOn > this . event . beginsOn ? this . event . endsOn : this . event . beginsOn ;
}
2019-04-03 17:29:03 +02:00
get twitterShareUrl ( ) : string {
return ` https://twitter.com/intent/tweet?url= ${ encodeURIComponent ( this . event . url ) } &text= ${ this . event . title } ` ;
}
get facebookShareUrl ( ) : string {
return ` https://www.facebook.com/sharer/sharer.php?u= ${ encodeURIComponent ( this . event . url ) } ` ;
}
get linkedInShareUrl ( ) : string {
return ` https://www.linkedin.com/shareArticle?mini=true&url= ${ encodeURIComponent ( this . event . url ) } &title= ${ this . event . title } ` ;
}
get emailShareUrl ( ) : string {
2019-10-10 12:25:32 +02:00
return ` mailto:?to=&body= ${ this . event . url } ${ encodeURIComponent ( '\n\n' ) } ${ this . textDescription } &subject= ${ this . event . title } ` ;
}
2019-11-19 12:04:35 +01:00
get diasporaShareUrl ( ) : string {
return ` https://share.diasporafoundation.org/?title= ${ encodeURIComponent ( this . event . title ) } &url= ${ encodeURIComponent ( this . event . url ) } ` ;
}
2019-10-10 12:25:32 +02:00
get textDescription ( ) : string {
const meta = document . querySelector ( "meta[property='og:description']" ) ;
if ( ! meta ) return '' ;
2019-10-14 12:56:37 +02:00
const desc = meta . getAttribute ( 'content' ) || '' ;
return desc . substring ( 0 , 1000 ) ;
2019-04-03 17:29:03 +02:00
}
2019-09-09 15:49:31 +02:00
2019-10-11 15:06:58 +02:00
get eventCapacityOK ( ) : boolean {
2019-11-08 19:37:14 +01:00
if ( this . event . draft ) return true ;
2019-10-11 15:06:58 +02:00
if ( ! this . event . options . maximumAttendeeCapacity ) return true ;
2019-10-25 17:43:37 +02:00
return this . event . options . maximumAttendeeCapacity > this . event . participantStats . participant ;
2019-10-11 15:06:58 +02:00
}
get numberOfPlacesStillAvailable ( ) : number {
2019-11-08 19:37:14 +01:00
if ( this . event . draft ) return this . event . options . maximumAttendeeCapacity ;
2019-10-25 17:43:37 +02:00
return this . event . options . maximumAttendeeCapacity - this . event . participantStats . participant ;
2019-10-11 15:06:58 +02:00
}
2019-10-15 09:57:08 +02:00
urlToHostname ( url : string ) : string | null {
try {
return ( new URL ( url ) ) . hostname ;
} catch ( e ) {
return null ;
}
2019-10-14 14:38:21 +02:00
}
2019-11-08 19:37:14 +01:00
get physicalAddress ( ) : Address | null {
if ( ! this . event . physicalAddress ) return null ;
return new Address ( this . event . physicalAddress ) ;
}
2019-12-20 13:04:34 +01:00
async anonymousParticipationConfirmed ( ) : Promise < boolean > {
return await isParticipatingInThisEvent ( this . uuid ) ;
}
async cancelAnonymousParticipation ( ) {
const token = await getLeaveTokenForParticipation ( this . uuid ) as String ;
await this . leaveEvent ( this . event , this . config . anonymous . actorId , token ) ;
await removeAnonymousParticipation ( this . uuid ) ;
this . anonymousParticipation = null ;
}
2019-01-21 15:08:22 +01:00
}
< / script >
2019-04-03 17:29:03 +02:00
< style lang = "scss" scoped >
@ import "../../variables" ;
2019-12-20 13:04:34 +01:00
. section {
padding : 1 rem 1.5 rem ;
}
main > . container {
background : $white ;
}
2019-10-08 22:27:14 +02:00
. fade - enter - active , . fade - leave - active {
transition : opacity .5 s ;
}
. fade - enter , . fade - leave - to {
opacity : 0 ;
}
2019-10-14 11:41:57 +02:00
. header - picture , . header - picture - default {
2019-10-10 14:50:44 +02:00
height : 400 px ;
background - size : cover ;
2019-10-14 11:41:57 +02:00
background - position : center ;
2019-10-10 14:50:44 +02:00
background - repeat : no - repeat ;
}
2019-10-14 11:41:57 +02:00
. header - picture - default {
background - image : url ( '/img/mobilizon_default_card.png' ) ;
}
2019-04-03 17:29:03 +02:00
div . sidebar {
display : flex ;
flex - wrap : wrap ;
flex - direction : column ;
position : relative ;
& : : before {
content : "" ;
background : # B3B3B2 ;
position : absolute ;
bottom : 30 px ;
top : 30 px ;
left : 0 ;
height : calc ( 100 % - 60 px ) ;
width : 1 px ;
}
div . address - wrapper {
display : flex ;
flex : 1 ;
flex - wrap : wrap ;
div . address {
flex : 1 ;
. map - show - button {
cursor : pointer ;
}
2019-11-08 19:37:14 +01:00
span : first - child {
2019-04-03 17:29:03 +02:00
display : flex ;
2019-11-08 19:37:14 +01:00
span . icon {
align - self : center ;
2019-04-03 17:29:03 +02:00
}
2019-11-08 19:37:14 +01:00
address {
font - style : normal ;
flex - wrap : wrap ;
display : flex ;
justify - content : flex - start ;
span . addressDescription {
text - overflow : ellipsis ;
white - space : nowrap ;
flex : 1 0 auto ;
min - width : 100 % ;
max - width : 4 rem ;
overflow : hidden ;
}
: not ( . addressDescription ) {
color : rgba ( 46 , 62 , 72 , .6 ) ;
flex : 1 ;
min - width : 100 % ;
}
2019-04-03 17:29:03 +02:00
}
}
}
div . map {
height : 900 px ;
width : 100 % ;
padding : 25 px 5 px 0 ;
}
}
2019-10-14 14:38:21 +02:00
span . online - address {
display : flex ;
}
2019-04-03 17:29:03 +02:00
div . organizer {
display : inline - flex ;
padding - top : 10 px ;
a {
color : # 4 a4a4a ;
span {
line - height : 2.7 rem ;
padding - right : 6 px ;
}
}
}
}
div . title - and - participate - button {
display : flex ;
2019-12-20 13:04:34 +01:00
// flex-wrap: wrap;
2019-04-03 17:29:03 +02:00
/*flex-flow: row wrap;*/
justify - content : space - between ;
/*align-self: center;*/
align - items : stretch ;
/*align-content: space-around;*/
2019-10-11 15:06:58 +02:00
padding : 7.5 px 10 px 0 ;
2019-12-20 13:04:34 +01:00
margin - bottom : 0.5 rem ;
2019-04-03 17:29:03 +02:00
div . title - wrapper {
display : flex ;
flex : 1 1 auto ;
2019-10-11 15:06:58 +02:00
div . title - and - informations {
h1 . title {
font - weight : normal ;
word - break : break - word ;
font - size : 1.7 em ;
margin - bottom : 0 ;
2020-02-18 08:47:41 +01:00
margin - top : 0 ;
2019-10-11 15:06:58 +02:00
}
span small {
& : not ( : last - child ) : after {
content : '⋅'
}
& : not ( : first - child ) {
padding - left : 3 px ;
}
}
}
2019-04-03 17:29:03 +02:00
div . date - component {
margin - right : 16 px ;
}
2019-10-11 15:06:58 +02:00
}
2019-04-03 17:29:03 +02:00
2019-10-11 15:06:58 +02:00
. event - participation small {
display : block ;
2019-04-03 17:29:03 +02:00
}
. participate - button {
flex : 0 1 auto ;
display : inline - flex ;
a . button {
margin : 0 auto ;
}
}
}
div . metadata {
padding : 0 10 px ;
div . date - and - add - to - calendar {
display : flex ;
flex - wrap : wrap ;
span . icon {
margin - right : 5 px ;
}
div . date - and - privacy {
color : $primary ;
padding : 0.3 rem ;
background : $secondary ;
font - weight : bold ;
}
a . add - to - calendar {
flex : 0 0 auto ;
margin - left : 10 px ;
color : # 484849 ;
& : hover {
text - decoration : underline ;
}
}
}
}
p . tags {
span {
2019-10-11 15:06:58 +02:00
& . tag {
2019-12-20 13:04:34 +01:00
margin : 0 2 px ;
2019-10-11 15:06:58 +02:00
& . is - success {
& : : before {
content : '#' ;
}
text - transform : uppercase ;
color : # 111111 ;
2019-04-03 17:29:03 +02:00
}
}
margin : auto 5 px ;
}
2019-12-20 13:04:34 +01:00
//margin-bottom: 1rem;
2019-04-03 17:29:03 +02:00
}
h3 . title {
2019-11-15 18:36:47 +01:00
font - weight : 300 ;
2019-04-03 17:29:03 +02:00
}
. description {
2019-12-20 13:04:34 +01:00
//padding: 10px 0;
2019-11-15 18:36:47 +01:00
min - height : 7 rem ;
& . exists {
min - height : 40 rem ;
}
2019-10-14 12:56:37 +02:00
@ media screen and ( min - width : 1216 px ) {
background - repeat : no - repeat ;
background - size : 600 px ;
background - position : 95 % 101 % ;
2019-11-15 18:36:47 +01:00
& . exists {
background - image : url ( '../../assets/texting.svg' ) ;
}
2019-10-14 12:56:37 +02:00
}
2019-12-20 13:04:34 +01:00
border - top : solid 1 px lighten ( $primary , 60 % ) ;
border - bottom : solid 1 px lighten ( $primary , 60 % ) ;
2019-04-03 17:29:03 +02:00
2019-10-10 11:05:53 +02:00
. description - content {
/deep/ h1 {
font - size : 2 rem ;
}
2019-04-03 17:29:03 +02:00
2019-10-10 11:05:53 +02:00
/deep/ h2 {
font - size : 1.5 rem ;
}
/deep/ h3 {
font - size : 1.25 rem ;
}
/deep/ ul {
list - style - type : disc ;
}
2019-10-15 11:40:25 +02:00
/deep/ li {
margin : 10 px auto 10 px 2 rem ;
}
2019-10-10 11:05:53 +02:00
/deep/ blockquote {
border - left : .2 em solid # 333 ;
display : block ;
padding - left : 1 em ;
}
/deep/ p {
margin : 10 px auto ;
a {
display : inline - block ;
padding : 0.3 rem ;
background : $secondary ;
color : # 111 ;
2019-10-23 12:36:11 +02:00
& : empty {
display : none ;
}
2019-10-10 11:05:53 +02:00
}
2019-04-03 17:29:03 +02:00
}
}
}
2019-11-15 18:36:47 +01:00
. comments {
a h3 # comments {
margin - bottom : 5 px ;
}
}
2019-04-03 17:29:03 +02:00
. share {
2019-11-15 18:36:47 +01:00
border - bottom : solid 1 px $primary ;
border - top : solid 1 px $primary ;
2019-04-03 17:29:03 +02:00
2019-11-19 12:04:35 +01:00
. diaspora span svg {
height : 2 rem ;
width : 2 rem ;
}
2019-04-03 17:29:03 +02:00
. columns {
& > * {
2019-11-15 18:36:47 +01:00
padding : 2 rem 0 ;
2019-04-03 17:29:03 +02:00
}
2019-10-11 15:06:58 +02:00
h3 {
display : block ;
color : $primary ;
font - size : 3 rem ;
text - decoration : underline ;
text - decoration - color : $secondary ;
max - width : 20 rem ;
}
. column : first - child {
h3 {
margin : 0 auto 1 rem ;
font - weight : normal ;
}
small . maximumNumberOfPlacesWarning {
margin : 0 auto 1 rem ;
display : block ;
}
}
. column : last - child {
h3 {
margin - right : 0 ;
}
}
2019-04-03 17:29:03 +02:00
. add - to - calendar {
2019-11-05 15:51:34 +01:00
display : flex ;
2019-11-04 16:58:24 +01:00
h3 {
2019-11-05 15:51:34 +01:00
margin - left : 0 ;
2019-11-04 16:58:24 +01:00
cursor : pointer ;
}
2019-04-03 17:29:03 +02:00
2019-11-05 15:51:34 +01:00
img {
2019-11-15 18:36:47 +01:00
max - width : 250 px ;
2019-11-05 15:51:34 +01:00
}
2019-04-03 17:29:03 +02:00
& : : before {
content : "" ;
background : # B3B3B2 ;
position : absolute ;
bottom : 25 % ;
height : 40 % ;
width : 1 px ;
}
}
}
}
. more - events {
margin : 50 px auto ;
2019-03-22 17:35:07 +01:00
}
< / style >