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:
Thomas Citharel 2019-10-08 22:42:45 +02:00
commit f756c1bc37
15 changed files with 269 additions and 220 deletions

View File

@ -2,7 +2,9 @@
<div id="mobilizon">
<NavBar />
<main>
<transition name="fade" mode="out-in">
<router-view />
</transition>
</main>
<mobilizon-footer />
</div>
@ -71,18 +73,10 @@ export default class App extends Vue {
/* Buefy imports */
@import "~buefy/src/scss/buefy";
.router-enter-active,
.router-leave-active {
transition-property: opacity;
transition-duration: 0.25s;
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.router-enter-active {
transition-delay: 0.25s;
}
.router-enter,
.router-leave-active {
.fade-enter, .fade-leave-to {
opacity: 0;
}

View File

@ -15,7 +15,7 @@
<search-field />
</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">
<figure class="image is-32x32" v-if="currentActor.avatar">
<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 SearchField from '@/components/SearchField.vue';
import { RouteName } from '@/router';
import { GraphQLError } from 'graphql';
@Component({
apollo: {
@ -97,6 +98,7 @@ import { RouteName } from '@/router';
skip() {
return this.currentUser.isLoggedIn === false;
},
error({ graphQLErrors }) { this.handleErrors(graphQLErrors); },
},
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() {
await logout(this.$apollo.provider.defaultClient);
this.$buefy.notification.open({

View File

@ -1,8 +1,8 @@
import gql from 'graphql-tag';
export const CREATE_USER = gql`
mutation CreateUser($email: String!, $password: String!) {
createUser(email: $email, password: $password) {
mutation CreateUser($email: String!, $password: String!, $locale: String) {
createUser(email: $email, password: $password, locale: $locale) {
email,
confirmationSentAt
}

View File

@ -173,7 +173,7 @@
"Past events": "Passed events",
"Pick an identity": "Pick an identity",
"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 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",

View File

@ -159,7 +159,7 @@
"Past events": "Événements passés",
"Pick an identity": "Choisissez une identité",
"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 quil sagit dune erreur.",
"Please make sure the address is correct and that the page hasn't been moved.": "Assurezvous que ladresse est correcte et que la page na pas été déplacée.",
"Please read the full rules": "Merci de lire les règles complètes",

View File

@ -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": "Lorganizator de leveniment causèt daprovar manualament las participacions daqueste 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.": "Leveniment ven duna autra instància. Vòstra participacion serà confirmada aprèp quajam recebut la confirmacion de lautra instància.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Volgatz contactar ladministrator daquesta instància Mobilizon se pensatz ques 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 savè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 savè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.": "Saquesta identitat es lunica que pòt administrar unes grops, vos cal los suprimir den 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 duna 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 ladreça es corrècta e que la pagina es pas estada desplaçada.",

View File

@ -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);
});
}

View File

@ -94,7 +94,7 @@ import PictureUpload from '@/components/PictureUpload.vue';
import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint';
import { Dialog } from 'buefy/dist/components/dialog';
import { RouteName } from '@/router';
import { buildFileFromIPicture, buildFileVariable } from '@/utils/image';
import { buildFileFromIPicture, buildFileVariable, readFileAsync } from '@/utils/image';
import { changeIdentity } from '@/utils/auth';
@Component({
@ -198,9 +198,11 @@ export default class EditIdentity extends Vue {
async updateIdentity() {
try {
const variables = await this.buildVariables();
await this.$apollo.mutate({
mutation: UPDATE_PERSON,
variables: this.buildVariables(),
variables,
update: (store, { data: { updatePerson } }) => {
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
@ -225,9 +227,11 @@ export default class EditIdentity extends Vue {
async createIdentity() {
try {
const variables = await this.buildVariables();
await this.$apollo.mutate({
mutation: CREATE_PERSON,
variables: this.buildVariables(),
variables,
update: (store, { data: { createPerson } }) => {
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
@ -305,10 +309,21 @@ export default class EditIdentity extends Vue {
.replace(/[^a-z0-9._]/g, '');
}
private buildVariables() {
private async buildVariables() {
const avatarObj = buildFileVariable(this.avatarFile, 'avatar', `${this.identity.preferredUsername}'s avatar`);
return Object.assign({}, this.identity, avatarObj);
const res = 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) {

View File

@ -1,6 +1,7 @@
<template>
<div class="container">
<b-loading :active.sync="$apollo.loading"></b-loading>
<transition appear name="fade" mode="out-in">
<div v-if="event">
<div class="header-picture container">
<figure class="image is-3by1" v-if="event.picture">
@ -47,7 +48,6 @@
<div class="column is-three-quarters-desktop">
<p class="tags" v-if="event.category || event.tags.length > 0">
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
<!-- <span class="tag" v-if="event.category">{{ event.category }}</span>-->
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
<span v-if="event.tags > 0"></span>
<span class="visibility" v-if="!event.draft">
@ -98,7 +98,6 @@
<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') }}
@ -198,6 +197,7 @@
</identity-picker>
</b-modal>
</div>
</transition>
</div>
</template>
@ -482,6 +482,13 @@ export default class Event extends EventMixin {
<style lang="scss" scoped>
@import "../../variables";
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
div.sidebar {
display: flex;
flex-wrap: wrap;

View File

@ -30,7 +30,7 @@
{{ $t('Welcome back {username}', { username: currentActor.displayName() }) }}
</b-message>
</section>
<section v-if="currentActor && goingToEvents.size > 0" class="container">
<section v-if="currentActor.id && goingToEvents.size > 0" class="container">
<h3 class="title">
{{ $t("Upcoming") }}
</h3>
@ -81,8 +81,8 @@
<section class="events-featured">
<h3 class="title">{{ $t('Featured events') }}</h3>
<b-loading :active.sync="$apollo.loading"></b-loading>
<div v-if="events.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 v-if="filteredFeaturedEvents.length > 0" class="columns is-multiline">
<div class="column is-one-third-desktop" v-for="event in filteredFeaturedEvents.slice(0, 6)" :key="event.uuid">
<EventCard
:event="event"
/>
@ -152,7 +152,7 @@ import { IConfig } from '@/types/config.model';
},
})
export default class Home extends Vue {
events: Event[] = [];
events: IEvent[] = [];
locations = [];
city = { name: null };
country = { name: null };
@ -224,6 +224,11 @@ export default class Home extends Vue {
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() {
const router = this.$router;
const sessionCity = sessionStorage.getItem('City');

View File

@ -115,6 +115,7 @@ export default class Register extends Vue {
credentials = {
email: this.email,
password: this.password,
locale: 'en',
};
errors: object = {};
sendingValidation: boolean = false;
@ -122,6 +123,7 @@ export default class Register extends Vue {
RouteName = RouteName;
async submit() {
this.credentials.locale = this.$i18n.locale;
try {
this.sendingValidation = true;
this.errors = {};

View File

@ -17,7 +17,7 @@
{{ $t('If an account with this email exists, we just sent another confirmation email to {email}', {email: credentials.email}) }}
</b-message>
<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>
</div>
</div>

View File

@ -18,7 +18,7 @@
{{ $t('We just sent an email to {email}', {email: credentials.email}) }}
</b-message>
<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>
</div>
</div>

View File

@ -39,6 +39,7 @@ describe('Registration', () => {
cy.get('form').contains('button.button.is-primary', 'Register').click();
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').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');

View File

@ -31,7 +31,7 @@ defmodule MobilizonWeb.Resolvers.Event do
{:error, :events_max_limit_reached}
end
def find_event(
defp find_private_event(
_parent,
%{uuid: uuid},
%{context: %{current_user: %User{id: user_id}}} = _resolution
@ -45,13 +45,16 @@ defmodule MobilizonWeb.Resolvers.Event do
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
{:has_event, %Event{} = event} ->
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
{:has_event, _} ->
{:error, "Event with UUID #{uuid} not found"}
find_private_event(parent, args, resolution)
end
end