Merge branch 'bug/various-fixes' into 'master'
Bug/various fixes Closes #191, #189, #188 et #187 See merge request framasoft/mobilizon!228
This commit is contained in:
commit
e12bda7e67
@ -2,7 +2,9 @@
|
|||||||
<div id="mobilizon">
|
<div id="mobilizon">
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<main>
|
<main>
|
||||||
<router-view />
|
<transition name="fade" mode="out-in">
|
||||||
|
<router-view />
|
||||||
|
</transition>
|
||||||
</main>
|
</main>
|
||||||
<mobilizon-footer />
|
<mobilizon-footer />
|
||||||
</div>
|
</div>
|
||||||
@ -71,18 +73,10 @@ export default class App extends Vue {
|
|||||||
/* Buefy imports */
|
/* Buefy imports */
|
||||||
@import "~buefy/src/scss/buefy";
|
@import "~buefy/src/scss/buefy";
|
||||||
|
|
||||||
.router-enter-active,
|
.fade-enter-active, .fade-leave-active {
|
||||||
.router-leave-active {
|
transition: opacity .5s;
|
||||||
transition-property: opacity;
|
|
||||||
transition-duration: 0.25s;
|
|
||||||
}
|
}
|
||||||
|
.fade-enter, .fade-leave-to {
|
||||||
.router-enter-active {
|
|
||||||
transition-delay: 0.25s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.router-enter,
|
|
||||||
.router-leave-active {
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<search-field />
|
<search-field />
|
||||||
</b-navbar-item>
|
</b-navbar-item>
|
||||||
|
|
||||||
<b-navbar-dropdown v-if="currentUser.isLoggedIn" right>
|
<b-navbar-dropdown v-if="currentActor.id && currentUser.isLoggedIn" right>
|
||||||
<template slot="label" v-if="currentActor" class="navbar-dropdown-profile">
|
<template slot="label" v-if="currentActor" class="navbar-dropdown-profile">
|
||||||
<figure class="image is-32x32" v-if="currentActor.avatar">
|
<figure class="image is-32x32" v-if="currentActor.avatar">
|
||||||
<img class="is-rounded" alt="avatarUrl" :src="currentActor.avatar.url">
|
<img class="is-rounded" alt="avatarUrl" :src="currentActor.avatar.url">
|
||||||
@ -82,6 +82,7 @@ import { ICurrentUser, ICurrentUserRole } from '@/types/current-user.model';
|
|||||||
import Logo from '@/components/Logo.vue';
|
import Logo from '@/components/Logo.vue';
|
||||||
import SearchField from '@/components/SearchField.vue';
|
import SearchField from '@/components/SearchField.vue';
|
||||||
import { RouteName } from '@/router';
|
import { RouteName } from '@/router';
|
||||||
|
import { GraphQLError } from 'graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
apollo: {
|
apollo: {
|
||||||
@ -97,6 +98,7 @@ import { RouteName } from '@/router';
|
|||||||
skip() {
|
skip() {
|
||||||
return this.currentUser.isLoggedIn === false;
|
return this.currentUser.isLoggedIn === false;
|
||||||
},
|
},
|
||||||
|
error({ graphQLErrors }) { this.handleErrors(graphQLErrors); },
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
query: CONFIG,
|
query: CONFIG,
|
||||||
@ -135,6 +137,12 @@ export default class NavBar extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleErrors(errors: GraphQLError) {
|
||||||
|
if (errors[0].message === 'You need to be logged-in to view your list of identities') {
|
||||||
|
await this.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
await logout(this.$apollo.provider.defaultClient);
|
await logout(this.$apollo.provider.defaultClient);
|
||||||
this.$buefy.notification.open({
|
this.$buefy.notification.open({
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
export const CREATE_USER = gql`
|
export const CREATE_USER = gql`
|
||||||
mutation CreateUser($email: String!, $password: String!) {
|
mutation CreateUser($email: String!, $password: String!, $locale: String) {
|
||||||
createUser(email: $email, password: $password) {
|
createUser(email: $email, password: $password, locale: $locale) {
|
||||||
email,
|
email,
|
||||||
confirmationSentAt
|
confirmationSentAt
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@
|
|||||||
"Past events": "Passed events",
|
"Past events": "Passed events",
|
||||||
"Pick an identity": "Pick an identity",
|
"Pick an identity": "Pick an identity",
|
||||||
"Please be nice to each other": "Please be nice to each other",
|
"Please be nice to each other": "Please be nice to each other",
|
||||||
"Please check you spam folder if you didn't receive the email.": "Please check you spam folder if you didn't receive the email.",
|
"Please check your spam folder if you didn't receive the email.": "Please check your spam folder if you didn't receive the email.",
|
||||||
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.",
|
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.",
|
||||||
"Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.",
|
"Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.",
|
||||||
"Please read the full rules": "Please read the full rules",
|
"Please read the full rules": "Please read the full rules",
|
||||||
|
@ -159,7 +159,7 @@
|
|||||||
"Past events": "Événements passés",
|
"Past events": "Événements passés",
|
||||||
"Pick an identity": "Choisissez une identité",
|
"Pick an identity": "Choisissez une identité",
|
||||||
"Please be nice to each other": "Soyez sympas entre vous",
|
"Please be nice to each other": "Soyez sympas entre vous",
|
||||||
"Please check you spam folder if you didn't receive the email.": "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email.",
|
"Please check your spam folder if you didn't receive the email.": "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email.",
|
||||||
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Veuillez contacter l'administrateur de cette instance Mobilizon si vous pensez qu’il s’agit d’une erreur.",
|
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Veuillez contacter l'administrateur de cette instance Mobilizon si vous pensez qu’il s’agit d’une erreur.",
|
||||||
"Please make sure the address is correct and that the page hasn't been moved.": "Assurez‐vous que l’adresse est correcte et que la page n’a pas été déplacée.",
|
"Please make sure the address is correct and that the page hasn't been moved.": "Assurez‐vous que l’adresse est correcte et que la page n’a pas été déplacée.",
|
||||||
"Please read the full rules": "Merci de lire les règles complètes",
|
"Please read the full rules": "Merci de lire les règles complètes",
|
||||||
|
@ -224,7 +224,7 @@
|
|||||||
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "L’organizator de l’eveniment causèt d’aprovar manualament las participacions d’aqueste eveniment. Recebretz una notificacion quand serà aprovada",
|
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "L’organizator de l’eveniment causèt d’aprovar manualament las participacions d’aqueste eveniment. Recebretz una notificacion quand serà aprovada",
|
||||||
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "L’eveniment ven d’una autra instància. Vòstra participacion serà confirmada aprèp qu’ajam recebut la confirmacion de l’autra instància.",
|
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "L’eveniment ven d’una autra instància. Vòstra participacion serà confirmada aprèp qu’ajam recebut la confirmacion de l’autra instància.",
|
||||||
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Volgatz contactar l’administrator d’aquesta instància Mobilizon se pensatz qu’es una error.",
|
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Volgatz contactar l’administrator d’aquesta instància Mobilizon se pensatz qu’es una error.",
|
||||||
"Please check you spam folder if you didn't receive the email.": "Mercés de verificar vòstre dorsièr de messatges indesirables s’avètz pas recebut lo corrièl.",
|
"Please check your spam folder if you didn't receive the email.": "Mercés de verificar vòstre dorsièr de messatges indesirables s’avètz pas recebut lo corrièl.",
|
||||||
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "S’aquesta identitat es l’unica que pòt administrar unes grops, vos cal los suprimir d’en primièr per dire de poder suprimir aquesta identitat.",
|
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "S’aquesta identitat es l’unica que pòt administrar unes grops, vos cal los suprimir d’en primièr per dire de poder suprimir aquesta identitat.",
|
||||||
"The content came from another server. Transfer an anonymous copy of the report?": "Lo contengut ven d’una autra instància. Transferir una còpia anonima del senhalament ?",
|
"The content came from another server. Transfer an anonymous copy of the report?": "Lo contengut ven d’una autra instància. Transferir una còpia anonima del senhalament ?",
|
||||||
"Please make sure the address is correct and that the page hasn't been moved.": "Asseguratz-vos que l’adreça es corrècta e que la pagina es pas estada desplaçada.",
|
"Please make sure the address is correct and that the page hasn't been moved.": "Asseguratz-vos que l’adreça es corrècta e que la pagina es pas estada desplaçada.",
|
||||||
|
@ -22,3 +22,17 @@ export function buildFileVariable<T>(file: File | null, name: string, alt?: stri
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readFileAsync(file: File): Promise<string|ArrayBuffer|null> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve(reader.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = reject;
|
||||||
|
|
||||||
|
reader.readAsBinaryString(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -94,7 +94,7 @@ import PictureUpload from '@/components/PictureUpload.vue';
|
|||||||
import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint';
|
import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint';
|
||||||
import { Dialog } from 'buefy/dist/components/dialog';
|
import { Dialog } from 'buefy/dist/components/dialog';
|
||||||
import { RouteName } from '@/router';
|
import { RouteName } from '@/router';
|
||||||
import { buildFileFromIPicture, buildFileVariable } from '@/utils/image';
|
import { buildFileFromIPicture, buildFileVariable, readFileAsync } from '@/utils/image';
|
||||||
import { changeIdentity } from '@/utils/auth';
|
import { changeIdentity } from '@/utils/auth';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -198,9 +198,11 @@ export default class EditIdentity extends Vue {
|
|||||||
|
|
||||||
async updateIdentity() {
|
async updateIdentity() {
|
||||||
try {
|
try {
|
||||||
|
const variables = await this.buildVariables();
|
||||||
|
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: UPDATE_PERSON,
|
mutation: UPDATE_PERSON,
|
||||||
variables: this.buildVariables(),
|
variables,
|
||||||
update: (store, { data: { updatePerson } }) => {
|
update: (store, { data: { updatePerson } }) => {
|
||||||
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
|
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
|
||||||
|
|
||||||
@ -225,9 +227,11 @@ export default class EditIdentity extends Vue {
|
|||||||
|
|
||||||
async createIdentity() {
|
async createIdentity() {
|
||||||
try {
|
try {
|
||||||
|
const variables = await this.buildVariables();
|
||||||
|
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: CREATE_PERSON,
|
mutation: CREATE_PERSON,
|
||||||
variables: this.buildVariables(),
|
variables,
|
||||||
update: (store, { data: { createPerson } }) => {
|
update: (store, { data: { createPerson } }) => {
|
||||||
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
|
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
|
||||||
|
|
||||||
@ -305,10 +309,21 @@ export default class EditIdentity extends Vue {
|
|||||||
.replace(/[^a-z0-9._]/g, '');
|
.replace(/[^a-z0-9._]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildVariables() {
|
private async buildVariables() {
|
||||||
const avatarObj = buildFileVariable(this.avatarFile, 'avatar', `${this.identity.preferredUsername}'s avatar`);
|
const avatarObj = buildFileVariable(this.avatarFile, 'avatar', `${this.identity.preferredUsername}'s avatar`);
|
||||||
|
const res = Object.assign({}, this.identity, avatarObj);
|
||||||
return Object.assign({}, this.identity, avatarObj);
|
/**
|
||||||
|
* If the avatar didn't change, no need to try reuploading it
|
||||||
|
*/
|
||||||
|
if (this.identity.avatar) {
|
||||||
|
const oldAvatarFile = await buildFileFromIPicture(this.identity.avatar) as File;
|
||||||
|
const oldAvatarFileContent = await readFileAsync(oldAvatarFile);
|
||||||
|
const newAvatarFileContent = await readFileAsync(this.avatarFile as File);
|
||||||
|
if (oldAvatarFileContent === newAvatarFileContent) {
|
||||||
|
res.avatar = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async redirectIfNoIdentitySelected (identityParam?: string) {
|
private async redirectIfNoIdentitySelected (identityParam?: string) {
|
||||||
|
@ -1,204 +1,204 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||||
<div v-if="event">
|
<transition appear name="fade" mode="out-in">
|
||||||
<div class="header-picture container">
|
<div v-if="event">
|
||||||
<figure class="image is-3by1" v-if="event.picture">
|
<div class="header-picture container">
|
||||||
<img :src="event.picture.url">
|
<figure class="image is-3by1" v-if="event.picture">
|
||||||
</figure>
|
<img :src="event.picture.url">
|
||||||
<figure class="image is-3by1" v-else>
|
</figure>
|
||||||
<img src="https://picsum.photos/600/200/">
|
<figure class="image is-3by1" v-else>
|
||||||
</figure>
|
<img src="https://picsum.photos/600/200/">
|
||||||
</div>
|
</figure>
|
||||||
<section>
|
</div>
|
||||||
<div class="title-and-participate-button">
|
<section>
|
||||||
<div class="title-wrapper">
|
<div class="title-and-participate-button">
|
||||||
<div class="date-component">
|
<div class="title-wrapper">
|
||||||
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
|
<div class="date-component">
|
||||||
|
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
|
||||||
|
</div>
|
||||||
|
<h1 class="title">{{ event.title }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="has-text-right" v-if="new Date(endDate) > new Date()">
|
||||||
|
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
||||||
|
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
||||||
|
</small>
|
||||||
|
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
|
||||||
|
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }}
|
||||||
|
</small>
|
||||||
|
<participation-button
|
||||||
|
v-if="currentActor.id && !actorIsOrganizer && !event.draft"
|
||||||
|
:participation="participations[0]"
|
||||||
|
:current-actor="currentActor"
|
||||||
|
@joinEvent="joinEvent"
|
||||||
|
@joinModal="isJoinModalActive = true"
|
||||||
|
@confirmLeave="confirmLeave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<button class="button is-primary" type="button" slot="trigger" disabled>
|
||||||
|
<template>
|
||||||
|
<span>{{ $t('Event already passed')}}</span>
|
||||||
|
</template>
|
||||||
|
<b-icon icon="menu-down"></b-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="title">{{ event.title }}</h1>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="has-text-right" v-if="new Date(endDate) > new Date()">
|
<div class="metadata columns">
|
||||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
<div class="column is-three-quarters-desktop">
|
||||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
<p class="tags" v-if="event.category || event.tags.length > 0">
|
||||||
</small>
|
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
|
||||||
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
|
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
|
||||||
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }}
|
<span v-if="event.tags > 0">⋅</span>
|
||||||
</small>
|
<span class="visibility" v-if="!event.draft">
|
||||||
<participation-button
|
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
|
||||||
v-if="currentActor.id && !actorIsOrganizer && !event.draft"
|
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
|
||||||
:participation="participations[0]"
|
</span>
|
||||||
:current-actor="currentActor"
|
</p>
|
||||||
@joinEvent="joinEvent"
|
<div class="date-and-add-to-calendar">
|
||||||
@joinModal="isJoinModalActive = true"
|
<div class="date-and-privacy" v-if="event.beginsOn">
|
||||||
@confirmLeave="confirmLeave"
|
<b-icon icon="calendar-clock" />
|
||||||
/>
|
<event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
|
||||||
|
</div>
|
||||||
|
<a class="add-to-calendar" @click="downloadIcsEvent()" v-if="!event.draft">
|
||||||
|
<b-icon icon="calendar-plus" />
|
||||||
|
{{ $t('Add to my calendar') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p class="slug">
|
||||||
|
{{ event.slug }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<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') }}
|
||||||
|
</router-link>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
<div class="address-wrapper">
|
||||||
|
<b-icon icon="map" />
|
||||||
|
<span v-if="!event.physicalAddress">{{ $t('No address defined') }}</span>
|
||||||
|
<div class="address" v-if="event.physicalAddress">
|
||||||
|
<address>
|
||||||
|
<span class="addressDescription" :title="event.physicalAddress.description">{{ event.physicalAddress.description }}</span>
|
||||||
|
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span>
|
||||||
|
<span>{{ event.physicalAddress.postalCode }} {{ event.physicalAddress.locality }}</span>
|
||||||
|
</address>
|
||||||
|
<span class="map-show-button" @click="showMap = !showMap" v-if="event.physicalAddress && event.physicalAddress.geom">
|
||||||
|
{{ $t('Show map') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<b-modal v-if="event.physicalAddress && event.physicalAddress.geom" :active.sync="showMap" scroll="keep">
|
||||||
|
<div class="map">
|
||||||
|
<map-leaflet
|
||||||
|
:coords="event.physicalAddress.geom"
|
||||||
|
:popup="event.physicalAddress.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</div>
|
||||||
|
<div class="organizer">
|
||||||
|
<span>
|
||||||
|
<span v-if="event.organizerActor">
|
||||||
|
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
|
||||||
|
</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>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
</section>
|
||||||
<button class="button is-primary" type="button" slot="trigger" disabled>
|
<div class="description">
|
||||||
<template>
|
<div class="description-container container">
|
||||||
<span>{{ $t('Event already passed')}}</span>
|
<h3 class="title">
|
||||||
</template>
|
{{ $t('About this event') }}
|
||||||
<b-icon icon="menu-down"></b-icon>
|
</h3>
|
||||||
</button>
|
<p v-if="!event.description">
|
||||||
|
{{ $t("The event organizer didn't add any description.") }}
|
||||||
|
</p>
|
||||||
|
<div class="columns" v-else>
|
||||||
|
<div class="column is-half" v-html="event.description">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="metadata columns">
|
<section class="share" v-if="!event.draft">
|
||||||
<div class="column is-three-quarters-desktop">
|
<div class="container">
|
||||||
<p class="tags" v-if="event.category || event.tags.length > 0">
|
<div class="columns">
|
||||||
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
|
<div class="column is-half has-text-centered">
|
||||||
<!-- <span class="tag" v-if="event.category">{{ event.category }}</span>-->
|
<h3 class="title">{{ $t('Share this event') }}</h3>
|
||||||
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
|
<div>
|
||||||
<span v-if="event.tags > 0">⋅</span>
|
<b-icon icon="mastodon" size="is-large" type="is-primary" />
|
||||||
<span class="visibility" v-if="!event.draft">
|
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
|
||||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
|
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
|
||||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
|
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="email" size="is-large" type="is-primary" /></a>
|
||||||
</span>
|
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
|
||||||
</p>
|
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
|
||||||
<div class="date-and-add-to-calendar">
|
|
||||||
<div class="date-and-privacy" v-if="event.beginsOn">
|
|
||||||
<b-icon icon="calendar-clock" />
|
|
||||||
<event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
|
|
||||||
</div>
|
</div>
|
||||||
<a class="add-to-calendar" @click="downloadIcsEvent()" v-if="!event.draft">
|
</div>
|
||||||
<b-icon icon="calendar-plus" />
|
<hr />
|
||||||
|
<div class="column is-half has-text-right add-to-calendar">
|
||||||
|
<h3 @click="downloadIcsEvent()">
|
||||||
{{ $t('Add to my calendar') }}
|
{{ $t('Add to my calendar') }}
|
||||||
</a>
|
</h3>
|
||||||
</div>
|
|
||||||
<p class="slug">
|
|
||||||
{{ event.slug }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<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') }}
|
|
||||||
</router-link>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
<div class="address-wrapper">
|
|
||||||
<b-icon icon="map" />
|
|
||||||
<span v-if="!event.physicalAddress">{{ $t('No address defined') }}</span>
|
|
||||||
<div class="address" v-if="event.physicalAddress">
|
|
||||||
<address>
|
|
||||||
<span class="addressDescription" :title="event.physicalAddress.description">{{ event.physicalAddress.description }}</span>
|
|
||||||
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span>
|
|
||||||
<span>{{ event.physicalAddress.postalCode }} {{ event.physicalAddress.locality }}</span>
|
|
||||||
<!-- <span>{{ event.physicalAddress.region }} {{ event.physicalAddress.country }}</span>-->
|
|
||||||
</address>
|
|
||||||
<span class="map-show-button" @click="showMap = !showMap" v-if="event.physicalAddress && event.physicalAddress.geom">
|
|
||||||
{{ $t('Show map') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<b-modal v-if="event.physicalAddress && event.physicalAddress.geom" :active.sync="showMap" scroll="keep">
|
|
||||||
<div class="map">
|
|
||||||
<map-leaflet
|
|
||||||
:coords="event.physicalAddress.geom"
|
|
||||||
:popup="event.physicalAddress.description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
</div>
|
|
||||||
<div class="organizer">
|
|
||||||
<span>
|
|
||||||
<span v-if="event.organizerActor">
|
|
||||||
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
|
|
||||||
</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>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="description">
|
<section class="more-events container" v-if="event.relatedEvents.length > 0">
|
||||||
<div class="description-container container">
|
<h3 class="title has-text-centered">{{ $t('These events may interest you') }}</h3>
|
||||||
<h3 class="title">
|
|
||||||
{{ $t('About this event') }}
|
|
||||||
</h3>
|
|
||||||
<p v-if="!event.description">
|
|
||||||
{{ $t("The event organizer didn't add any description.") }}
|
|
||||||
</p>
|
|
||||||
<div class="columns" v-else>
|
|
||||||
<div class="column is-half" v-html="event.description">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<section class="share" v-if="!event.draft">
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half has-text-centered">
|
<div class="column is-one-third-desktop" v-for="relatedEvent in event.relatedEvents" :key="relatedEvent.uuid">
|
||||||
<h3 class="title">{{ $t('Share this event') }}</h3>
|
<EventCard :event="relatedEvent" />
|
||||||
<div>
|
|
||||||
<b-icon icon="mastodon" size="is-large" type="is-primary" />
|
|
||||||
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
|
|
||||||
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
|
|
||||||
<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 -->
|
|
||||||
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="column is-half has-text-right add-to-calendar">
|
|
||||||
<h3 @click="downloadIcsEvent()">
|
|
||||||
{{ $t('Add to my calendar') }}
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</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-modal>
|
||||||
|
<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>
|
||||||
|
</identity-picker>
|
||||||
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</transition>
|
||||||
<section class="more-events container" v-if="event.relatedEvents.length > 0">
|
</div>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</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-modal>
|
|
||||||
<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>
|
|
||||||
</identity-picker>
|
|
||||||
</b-modal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -482,6 +482,13 @@ export default class Event extends EventMixin {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../variables";
|
@import "../../variables";
|
||||||
|
|
||||||
|
.fade-enter-active, .fade-leave-active {
|
||||||
|
transition: opacity .5s;
|
||||||
|
}
|
||||||
|
.fade-enter, .fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
div.sidebar {
|
div.sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
{{ $t('Welcome back {username}', { username: currentActor.displayName() }) }}
|
{{ $t('Welcome back {username}', { username: currentActor.displayName() }) }}
|
||||||
</b-message>
|
</b-message>
|
||||||
</section>
|
</section>
|
||||||
<section v-if="currentActor && goingToEvents.size > 0" class="container">
|
<section v-if="currentActor.id && goingToEvents.size > 0" class="container">
|
||||||
<h3 class="title">
|
<h3 class="title">
|
||||||
{{ $t("Upcoming") }}
|
{{ $t("Upcoming") }}
|
||||||
</h3>
|
</h3>
|
||||||
@ -81,8 +81,8 @@
|
|||||||
<section class="events-featured">
|
<section class="events-featured">
|
||||||
<h3 class="title">{{ $t('Featured events') }}</h3>
|
<h3 class="title">{{ $t('Featured events') }}</h3>
|
||||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||||
<div v-if="events.length > 0" class="columns is-multiline">
|
<div v-if="filteredFeaturedEvents.length > 0" class="columns is-multiline">
|
||||||
<div class="column is-one-third-desktop" v-for="event in events.slice(0, 6)" :key="event.uuid">
|
<div class="column is-one-third-desktop" v-for="event in filteredFeaturedEvents.slice(0, 6)" :key="event.uuid">
|
||||||
<EventCard
|
<EventCard
|
||||||
:event="event"
|
:event="event"
|
||||||
/>
|
/>
|
||||||
@ -152,7 +152,7 @@ import { IConfig } from '@/types/config.model';
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class Home extends Vue {
|
export default class Home extends Vue {
|
||||||
events: Event[] = [];
|
events: IEvent[] = [];
|
||||||
locations = [];
|
locations = [];
|
||||||
city = { name: null };
|
city = { name: null };
|
||||||
country = { name: null };
|
country = { name: null };
|
||||||
@ -224,6 +224,11 @@ export default class Home extends Vue {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get filteredFeaturedEvents() {
|
||||||
|
if (!this.currentUser.isLoggedIn || !this.currentActor.id) return this.events;
|
||||||
|
return this.events.filter(event => event.organizerActor && event.organizerActor.id !== this.currentActor.id);
|
||||||
|
}
|
||||||
|
|
||||||
geoLocalize() {
|
geoLocalize() {
|
||||||
const router = this.$router;
|
const router = this.$router;
|
||||||
const sessionCity = sessionStorage.getItem('City');
|
const sessionCity = sessionStorage.getItem('City');
|
||||||
|
@ -115,6 +115,7 @@ export default class Register extends Vue {
|
|||||||
credentials = {
|
credentials = {
|
||||||
email: this.email,
|
email: this.email,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
|
locale: 'en',
|
||||||
};
|
};
|
||||||
errors: object = {};
|
errors: object = {};
|
||||||
sendingValidation: boolean = false;
|
sendingValidation: boolean = false;
|
||||||
@ -122,6 +123,7 @@ export default class Register extends Vue {
|
|||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
|
this.credentials.locale = this.$i18n.locale;
|
||||||
try {
|
try {
|
||||||
this.sendingValidation = true;
|
this.sendingValidation = true;
|
||||||
this.errors = {};
|
this.errors = {};
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
{{ $t('If an account with this email exists, we just sent another confirmation email to {email}', {email: credentials.email}) }}
|
{{ $t('If an account with this email exists, we just sent another confirmation email to {email}', {email: credentials.email}) }}
|
||||||
</b-message>
|
</b-message>
|
||||||
<b-message type="is-info">
|
<b-message type="is-info">
|
||||||
{{ $t("Please check you spam folder if you didn't receive the email.") }}
|
{{ $t("Please check your spam folder if you didn't receive the email.") }}
|
||||||
</b-message>
|
</b-message>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
{{ $t('We just sent an email to {email}', {email: credentials.email}) }}
|
{{ $t('We just sent an email to {email}', {email: credentials.email}) }}
|
||||||
</b-message>
|
</b-message>
|
||||||
<b-message type="is-info">
|
<b-message type="is-info">
|
||||||
{{ $t("Please check you spam folder if you didn't receive the email.") }}
|
{{ $t("Please check your spam folder if you didn't receive the email.") }}
|
||||||
</b-message>
|
</b-message>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,6 +39,7 @@ describe('Registration', () => {
|
|||||||
cy.get('form').contains('button.button.is-primary', 'Register').click();
|
cy.get('form').contains('button.button.is-primary', 'Register').click();
|
||||||
|
|
||||||
cy.url().should('include', '/register/profile');
|
cy.url().should('include', '/register/profile');
|
||||||
|
cy.wait(1000);
|
||||||
cy.get('form .field').first().contains('label', 'Username').parent().find('input').type('tester');
|
cy.get('form .field').first().contains('label', 'Username').parent().find('input').type('tester');
|
||||||
cy.get('form .field').eq(2).contains('label', 'Displayed name').parent().find('input').type('tester account');
|
cy.get('form .field').eq(2).contains('label', 'Displayed name').parent().find('input').type('tester account');
|
||||||
cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This is a test account');
|
cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This is a test account');
|
||||||
|
@ -31,11 +31,11 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||||||
{:error, :events_max_limit_reached}
|
{:error, :events_max_limit_reached}
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_event(
|
defp find_private_event(
|
||||||
_parent,
|
_parent,
|
||||||
%{uuid: uuid},
|
%{uuid: uuid},
|
||||||
%{context: %{current_user: %User{id: user_id}}} = _resolution
|
%{context: %{current_user: %User{id: user_id}}} = _resolution
|
||||||
) do
|
) do
|
||||||
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
|
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
|
||||||
{:has_event, %Event{} = event} ->
|
{:has_event, %Event{} = event} ->
|
||||||
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
||||||
@ -45,13 +45,16 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_event(_parent, %{uuid: uuid}, _resolution) do
|
defp find_private_event(_parent, %{uuid: uuid}, _resolution),
|
||||||
|
do: {:error, "Event with UUID #{uuid} not found"}
|
||||||
|
|
||||||
|
def find_event(parent, %{uuid: uuid} = args, resolution) do
|
||||||
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
|
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
|
||||||
{:has_event, %Event{} = event} ->
|
{:has_event, %Event{} = event} ->
|
||||||
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
||||||
|
|
||||||
{:has_event, _} ->
|
{:has_event, _} ->
|
||||||
{:error, "Event with UUID #{uuid} not found"}
|
find_private_event(parent, args, resolution)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user