Front-end fixes and updates
Especially Join/Leave event, Vue-Markdown replacement Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
3507438f17
commit
c4e327508b
1581
js/package-lock.json
generated
1581
js/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -13,8 +13,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apollo-absinthe-upload-link": "^1.5.0",
|
"apollo-absinthe-upload-link": "^1.5.0",
|
||||||
"apollo-cache-inmemory": "^1.4.2",
|
"apollo-cache-inmemory": "^1.4.3",
|
||||||
"apollo-client": "^2.4.12",
|
"apollo-client": "^2.4.13",
|
||||||
"apollo-link": "^1.2.8",
|
"apollo-link": "^1.2.8",
|
||||||
"apollo-link-http": "^1.5.11",
|
"apollo-link-http": "^1.5.11",
|
||||||
"apollo-link-state": "^0.4.2",
|
"apollo-link-state": "^0.4.2",
|
||||||
|
@ -25,37 +25,37 @@
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"material-design-icons": "^3.0.1",
|
"material-design-icons": "^3.0.1",
|
||||||
"ngeohash": "^0.6.3",
|
"ngeohash": "^0.6.3",
|
||||||
"register-service-worker": "^1.6.1",
|
"register-service-worker": "^1.6.2",
|
||||||
"vue": "^2.6.3",
|
"vue": "^2.6.7",
|
||||||
"vue-apollo": "^3.0.0-beta.28",
|
"vue-apollo": "^3.0.0-beta.28",
|
||||||
"vue-class-component": "^6.3.2",
|
"vue-class-component": "^7.0.1",
|
||||||
"vue-gettext": "^2.1.2",
|
"vue-gettext": "^2.1.2",
|
||||||
"vue-markdown": "^2.2.4",
|
|
||||||
"vue-property-decorator": "^7.3.0",
|
"vue-property-decorator": "^7.3.0",
|
||||||
"vue-router": "^3.0.2",
|
"vue-router": "^3.0.2",
|
||||||
|
"vue-simple-markdown": "^1.0.8",
|
||||||
"vuex": "^3.1.0"
|
"vuex": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/lodash": "^4.14.120",
|
"@types/lodash": "^4.14.121",
|
||||||
"@types/mocha": "^5.2.5",
|
"@types/mocha": "^5.2.6",
|
||||||
"@vue/cli-plugin-babel": "^3.4.0",
|
"@vue/cli-plugin-babel": "^3.4.1",
|
||||||
"@vue/cli-plugin-e2e-nightwatch": "^3.4.0",
|
"@vue/cli-plugin-e2e-nightwatch": "^3.4.1",
|
||||||
"@vue/cli-plugin-pwa": "^3.4.0",
|
"@vue/cli-plugin-pwa": "^3.4.1",
|
||||||
"@vue/cli-plugin-typescript": "^3.4.0",
|
"@vue/cli-plugin-typescript": "^3.4.1",
|
||||||
"@vue/cli-plugin-unit-mocha": "^3.4.0",
|
"@vue/cli-plugin-unit-mocha": "^3.4.1",
|
||||||
"@vue/cli-service": "^3.4.0",
|
"@vue/cli-service": "^3.4.1",
|
||||||
"@vue/eslint-config-typescript": "^4.0.0",
|
"@vue/eslint-config-typescript": "^4.0.0",
|
||||||
"@vue/test-utils": "^1.0.0-beta.29",
|
"@vue/test-utils": "^1.0.0-beta.29",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"dotenv-webpack": "^1.7.0",
|
"dotenv-webpack": "^1.7.0",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.11.0",
|
||||||
"patch-package": "^6.0.2",
|
"patch-package": "^6.0.4",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"tslint-config-airbnb": "^5.11.1",
|
"tslint-config-airbnb": "^5.11.1",
|
||||||
"typescript": "^3.3.3",
|
"typescript": "^3.3.3333",
|
||||||
"vue-template-compiler": "^2.6.3",
|
"vue-template-compiler": "^2.6.7",
|
||||||
"webpack-bundle-analyzer": "^3.0.3"
|
"webpack-bundle-analyzer": "^3.0.4"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|
|
@ -5,7 +5,8 @@ const participantQuery = `
|
||||||
actor {
|
actor {
|
||||||
preferredUsername,
|
preferredUsername,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
name
|
name,
|
||||||
|
id
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -117,23 +118,20 @@ export const EDIT_EVENT = gql`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const JOIN_EVENT = gql`
|
export const JOIN_EVENT = gql`
|
||||||
mutation JoinEvent($id: Int!, $actorId: Int!) {
|
mutation JoinEvent($eventId: Int!, $actorId: Int!) {
|
||||||
joinEvent(
|
joinEvent(
|
||||||
id: $id,
|
eventId: $eventId,
|
||||||
actorId: $actorId
|
actorId: $actorId
|
||||||
) {
|
) {
|
||||||
actor {
|
|
||||||
${participantQuery}
|
${participantQuery}
|
||||||
},
|
|
||||||
role
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LEAVE_EVENT = gql`
|
export const LEAVE_EVENT = gql`
|
||||||
mutation LeaveEvent($id: Int!, $actorId: Int!) {
|
mutation LeaveEvent($eventId: Int!, $actorId: Int!) {
|
||||||
leaveEvent(
|
leaveEvent(
|
||||||
id: $id,
|
eventId: $eventId,
|
||||||
actorId: $actorId
|
actorId: $actorId
|
||||||
) {
|
) {
|
||||||
actor {
|
actor {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
// import * as VueGoogleMaps from 'vue2-google-maps';
|
// import * as VueGoogleMaps from 'vue2-google-maps';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import VueSimpleMarkdown from 'vue-simple-markdown';
|
||||||
import Buefy from 'buefy'
|
import Buefy from 'buefy'
|
||||||
import 'buefy/dist/buefy.css';
|
import 'buefy/dist/buefy.css';
|
||||||
import GetTextPlugin from 'vue-gettext';
|
import GetTextPlugin from 'vue-gettext';
|
||||||
|
@ -14,7 +14,7 @@ const translations = require('@/i18n/translations.json');
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
Vue.use(VueMarkdown);
|
Vue.use(VueSimpleMarkdown);
|
||||||
Vue.use(Buefy, {
|
Vue.use(Buefy, {
|
||||||
defaultContainerElement: '#mobilizon'
|
defaultContainerElement: '#mobilizon'
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,8 @@ export const userRoutes = [
|
||||||
path: '/register/profile',
|
path: '/register/profile',
|
||||||
name: UserRouteName.REGISTER_PROFILE,
|
name: UserRouteName.REGISTER_PROFILE,
|
||||||
component: RegisterProfile,
|
component: RegisterProfile,
|
||||||
props: true,
|
// We can only pass string values through params, therefore
|
||||||
|
props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true' }),
|
||||||
meta: { requiredAuth: false },
|
meta: { requiredAuth: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -56,8 +57,7 @@ export const userRoutes = [
|
||||||
path: '/validate/:token',
|
path: '/validate/:token',
|
||||||
name: UserRouteName.VALIDATE,
|
name: UserRouteName.VALIDATE,
|
||||||
component: Validate,
|
component: Validate,
|
||||||
// We can only pass string values through params, therefore
|
props: true,
|
||||||
props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true' }),
|
|
||||||
meta: { requiresAuth: false },
|
meta: { requiresAuth: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,16 +12,6 @@
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<form v-if="!validationSent">
|
<form v-if="!validationSent">
|
||||||
<div class="columns is-mobile is-centered">
|
|
||||||
<div class="column is-narrow">
|
|
||||||
<figure class="image is-64x64">
|
|
||||||
<transition name="avatar">
|
|
||||||
<v-gravatar v-bind="{email: email}" default-img="mp"></v-gravatar>
|
|
||||||
</transition>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<b-field
|
<b-field
|
||||||
:label="$gettext('Username')"
|
:label="$gettext('Username')"
|
||||||
:type="errors.preferred_username ? 'is-danger' : null"
|
:type="errors.preferred_username ? 'is-danger' : null"
|
||||||
|
|
|
@ -34,18 +34,13 @@ import {EventJoinOptions} from "../../types/event.model";
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import Location from '@/components/Location';
|
// import Location from '@/components/Location';
|
||||||
import VueMarkdown from "vue-markdown";
|
|
||||||
import {CREATE_EVENT, EDIT_EVENT} from "@/graphql/event";
|
import {CREATE_EVENT, EDIT_EVENT} from "@/graphql/event";
|
||||||
import {Component, Prop, Vue} from "vue-property-decorator";
|
import {Component, Prop, Vue} from "vue-property-decorator";
|
||||||
import {Category, EventJoinOptions, EventStatus, EventVisibility, IEvent} from "@/types/event.model";
|
import {Category, EventJoinOptions, EventStatus, EventVisibility, IEvent} from "@/types/event.model";
|
||||||
import {LOGGED_PERSON} from "@/graphql/actor";
|
import {LOGGED_PERSON} from "@/graphql/actor";
|
||||||
import {IPerson} from "@/types/actor.model";
|
import {IPerson} from "@/types/actor.model";
|
||||||
|
|
||||||
@Component({
|
@Component({})
|
||||||
components: {
|
|
||||||
VueMarkdown
|
|
||||||
}
|
|
||||||
})
|
|
||||||
export default class CreateEvent extends Vue {
|
export default class CreateEvent extends Vue {
|
||||||
@Prop({ required: false, type: String }) uuid!: string;
|
@Prop({ required: false, type: String }) uuid!: string;
|
||||||
|
|
||||||
|
|
|
@ -47,15 +47,22 @@
|
||||||
<span>{{ event.begins_on | formatDate }} - {{ event.ends_on | formatDate }}</span>
|
<span>{{ event.begins_on | formatDate }} - {{ event.ends_on | formatDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="actorIsOrganizer()">
|
<p v-if="actorIsOrganizer()">
|
||||||
<translate>Vous êtes organisateur de cet événement.</translate>
|
<translate>You are an organizer.</translate>
|
||||||
</p>
|
</p>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p v-if="actorIsParticipant()">
|
<p v-if="actorIsParticipant()">
|
||||||
<translate>Vous avez annoncé aller à cet événement.</translate>
|
<translate>You announced that you're going to this event.</translate>
|
||||||
</p>
|
</p>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
Vous y allez ?
|
<translate>Are you going to this event?</translate><br />
|
||||||
<span>{{ event.participants.length }} personnes y vont.</span>
|
<span>
|
||||||
|
<translate
|
||||||
|
:translate-n="event.participants.length"
|
||||||
|
translate-plural="%{event.participants.length} persons are going"
|
||||||
|
>
|
||||||
|
One person is going.
|
||||||
|
</translate>
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!actorIsOrganizer()">
|
<div v-if="!actorIsOrganizer()">
|
||||||
|
@ -66,7 +73,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h2 class="subtitle">Details</h2>
|
<h2 class="subtitle">Details</h2>
|
||||||
<p v-if="event.description">
|
<p v-if="event.description">
|
||||||
<vue-markdown :source="event.description"></vue-markdown>
|
<vue-simple-markdown :source="event.description"></vue-simple-markdown>
|
||||||
</p>
|
</p>
|
||||||
<h2 class="subtitle">Participants</h2>
|
<h2 class="subtitle">Participants</h2>
|
||||||
<span v-if="event.participants.length === 0">No participants yet.</span>
|
<span v-if="event.participants.length === 0">No participants yet.</span>
|
||||||
|
@ -99,15 +106,10 @@ import { LOGGED_PERSON } from '@/graphql/actor';
|
||||||
import { IEvent, IParticipant } from '@/types/event.model';
|
import { IEvent, IParticipant } from '@/types/event.model';
|
||||||
import { JOIN_EVENT } from '@/graphql/event';
|
import { JOIN_EVENT } from '@/graphql/event';
|
||||||
import { IPerson } from '@/types/actor.model';
|
import { IPerson } from '@/types/actor.model';
|
||||||
import { RouteName } from '@/router'
|
import { RouteName } from '@/router';
|
||||||
|
import 'vue-simple-markdown/dist/vue-simple-markdown.css';
|
||||||
// No typings for this component, so we use require
|
|
||||||
const VueMarkdown = require('vue-markdown');
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
|
||||||
VueMarkdown
|
|
||||||
},
|
|
||||||
apollo: {
|
apollo: {
|
||||||
event: {
|
event: {
|
||||||
query: FETCH_EVENT,
|
query: FETCH_EVENT,
|
||||||
|
@ -152,13 +154,13 @@ export default class Event extends Vue {
|
||||||
await this.$apollo.mutate<IParticipant>({
|
await this.$apollo.mutate<IParticipant>({
|
||||||
mutation: JOIN_EVENT,
|
mutation: JOIN_EVENT,
|
||||||
variables: {
|
variables: {
|
||||||
id: this.event.id,
|
eventId: this.event.id,
|
||||||
actorId: this.loggedPerson.id,
|
actorId: this.loggedPerson.id,
|
||||||
},
|
},
|
||||||
update: (store, { data: { joinEvent } }) => {
|
update: (store, { data: { joinEvent } }) => {
|
||||||
const event = store.readQuery<IEvent>({ query: FETCH_EVENT });
|
const event = store.readQuery<IEvent>({ query: FETCH_EVENT });
|
||||||
if (event === null) {
|
if (event === null) {
|
||||||
console.error('Cannot update event participant cache, because of null value.')
|
console.error('Cannot update event participant cache, because of null value.');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +179,7 @@ export default class Event extends Vue {
|
||||||
await this.$apollo.mutate<IParticipant>({
|
await this.$apollo.mutate<IParticipant>({
|
||||||
mutation: LEAVE_EVENT,
|
mutation: LEAVE_EVENT,
|
||||||
variables: {
|
variables: {
|
||||||
id: this.event.id,
|
eventId: this.event.id,
|
||||||
actorId: this.loggedPerson.id,
|
actorId: this.loggedPerson.id,
|
||||||
},
|
},
|
||||||
update: (store, { data: { leaveEvent } }) => {
|
update: (store, { data: { leaveEvent } }) => {
|
||||||
|
|
|
@ -19,17 +19,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ngeohash from 'ngeohash';
|
|
||||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||||
import EventCard from '@/components/Event/EventCard.vue';
|
import EventCard from '@/components/Event/EventCard.vue';
|
||||||
import { RouteName } from '@/router';
|
import { RouteName } from '@/router';
|
||||||
|
|
||||||
// VueMarkdown is untyped
|
const ngeohash = require('ngeohash');
|
||||||
const VueMarkdown = require('vue-markdown');
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
VueMarkdown,
|
|
||||||
EventCard
|
EventCard
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,14 +28,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
// VueMarkdown is untyped
|
@Component({})
|
||||||
const VueMarkdown = require('vue-markdown')
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
components: {
|
|
||||||
VueMarkdown
|
|
||||||
}
|
|
||||||
})
|
|
||||||
export default class CreateGroup extends Vue {
|
export default class CreateGroup extends Vue {
|
||||||
e1 = 0;
|
e1 = 0;
|
||||||
// FIXME: correctly type group
|
// FIXME: correctly type group
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Activity
|
alias Mobilizon.Activity
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
alias Mobilizon.Actors.User
|
alias Mobilizon.Actors.{User, Actor}
|
||||||
|
|
||||||
# We limit the max number of events that can be retrieved
|
# We limit the max number of events that can be retrieved
|
||||||
@event_max_limit 100
|
@event_max_limit 100
|
||||||
|
@ -100,8 +100,7 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:ok, %Participant{} = participant} <-
|
{:ok, %Participant{} = participant} <-
|
||||||
Mobilizon.Events.get_participant(event_id, actor_id),
|
Mobilizon.Events.get_participant(event_id, actor_id),
|
||||||
{:only_organizer, false} <-
|
{:only_organizer, false} <-
|
||||||
{:only_organizer,
|
{:only_organizer, check_that_participant_is_not_only_organizer(event_id, actor_id)},
|
||||||
Mobilizon.Events.list_organizers_participants_for_event(event_id) |> length == 1},
|
|
||||||
{:ok, _} <-
|
{:ok, _} <-
|
||||||
Mobilizon.Events.delete_participant(participant) do
|
Mobilizon.Events.delete_participant(participant) do
|
||||||
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
|
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
|
||||||
|
@ -121,6 +120,19 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:error, "You need to be logged-in to leave an event"}
|
{:error, "You need to be logged-in to leave an event"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We check that the actor asking to leave the event is not it's only organizer
|
||||||
|
# We start by fetching the list of organizers and if there's only one of them
|
||||||
|
# and that it's the actor requesting leaving the event we return true
|
||||||
|
@spec check_that_participant_is_not_only_organizer(integer(), integer()) :: boolean()
|
||||||
|
defp check_that_participant_is_not_only_organizer(event_id, actor_id) do
|
||||||
|
with [%Participant{actor: %Actor{id: participant_actor_id}}] <-
|
||||||
|
Mobilizon.Events.list_organizers_participants_for_event(event_id) do
|
||||||
|
participant_actor_id == actor_id
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an event
|
Create an event
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue