mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-21 23:44:30 +00:00
Add draft feature
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
b96f3bc3ad
commit
442a011490
22 changed files with 587 additions and 66 deletions
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
<h2 class="title" ref="title">{{ event.title }}</h2>
|
||||
</div>
|
||||
<span>
|
||||
<span class="organizer-place-wrapper">
|
||||
<span v-if="actorDisplayName && actorDisplayName !== '@'">{{ $t('By {name}', { name: actorDisplayName }) }}</span>
|
||||
<span v-if="event.physicalAddress && (event.physicalAddress.locality || event.physicalAddress.description)">
|
||||
- {{ event.physicalAddress.locality || event.physicalAddress.description }}
|
||||
|
@ -142,6 +142,17 @@ export default class EventCard extends Vue {
|
|||
line-height: 1em;
|
||||
font-size: 1.6em;
|
||||
padding-bottom: 5px;
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
span.organizer-place-wrapper {
|
||||
display: flex;
|
||||
|
||||
span:last-child {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ export const UPDATE_CURRENT_ACTOR_CLIENT = gql`
|
|||
`;
|
||||
|
||||
export const LOGGED_USER_PARTICIPATIONS = gql`
|
||||
query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTime $page: Int, $limit: Int) {
|
||||
query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTime, $page: Int, $limit: Int) {
|
||||
loggedUser {
|
||||
participations(afterDatetime: $afterDateTime, beforeDatetime: $beforeDateTime, page: $page, limit: $limit) {
|
||||
event {
|
||||
|
@ -106,6 +106,40 @@ query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTi
|
|||
}
|
||||
}`;
|
||||
|
||||
export const LOGGED_USER_DRAFTS = gql`
|
||||
query LoggedUserDrafts($page: Int, $limit: Int) {
|
||||
loggedUser {
|
||||
drafts(page: $page, limit: $limit) {
|
||||
id,
|
||||
uuid,
|
||||
title,
|
||||
picture {
|
||||
url,
|
||||
alt
|
||||
},
|
||||
beginsOn,
|
||||
visibility,
|
||||
organizerActor {
|
||||
id,
|
||||
preferredUsername,
|
||||
name,
|
||||
domain,
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved
|
||||
},
|
||||
options {
|
||||
maximumAttendeeCapacity
|
||||
remainingAttendeeCapacity
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export const IDENTITIES = gql`
|
||||
query {
|
||||
identities {
|
||||
|
|
|
@ -69,6 +69,7 @@ export const FETCH_EVENT = gql`
|
|||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
draft,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
|
@ -190,6 +191,7 @@ export const CREATE_EVENT = gql`
|
|||
$status: EventStatus,
|
||||
$visibility: EventVisibility,
|
||||
$joinOptions: EventJoinOptions,
|
||||
$draft: Boolean,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$onlineAddress: String,
|
||||
|
@ -207,6 +209,7 @@ export const CREATE_EVENT = gql`
|
|||
status: $status,
|
||||
visibility: $visibility,
|
||||
joinOptions: $joinOptions,
|
||||
draft: $draft,
|
||||
tags: $tags,
|
||||
picture: $picture,
|
||||
onlineAddress: $onlineAddress,
|
||||
|
@ -224,6 +227,7 @@ export const CREATE_EVENT = gql`
|
|||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
draft,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
|
@ -255,6 +259,7 @@ export const EDIT_EVENT = gql`
|
|||
$status: EventStatus,
|
||||
$visibility: EventVisibility,
|
||||
$joinOptions: EventJoinOptions,
|
||||
$draft: Boolean,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$onlineAddress: String,
|
||||
|
@ -272,6 +277,7 @@ export const EDIT_EVENT = gql`
|
|||
status: $status,
|
||||
visibility: $visibility,
|
||||
joinOptions: $joinOptions,
|
||||
draft: $draft,
|
||||
tags: $tags,
|
||||
picture: $picture,
|
||||
onlineAddress: $onlineAddress,
|
||||
|
@ -289,6 +295,7 @@ export const EDIT_EVENT = gql`
|
|||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
draft,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
|
|
|
@ -13,10 +13,14 @@
|
|||
"Allow all comments": "Allow all comments",
|
||||
"Approve": "Approve",
|
||||
"Are you going to this event?": "Are you going to this event?",
|
||||
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Are you sure you want to cancel the event creation? You'll lose all modifications.",
|
||||
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Are you sure you want to cancel the event edition? You'll lose all modifications.",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Are you sure you want to cancel your participation at event \"{title}\"?",
|
||||
"Are you sure you want to delete this event? This action cannot be reverted.": "Are you sure you want to delete this event? This action cannot be reverted.",
|
||||
"Before you can login, you need to click on the link inside it to validate your account": "Before you can login, you need to click on the link inside it to validate your account",
|
||||
"By {name}": "By {name}",
|
||||
"Cancel creation": "Cancel creation",
|
||||
"Cancel edition": "Cancel edition",
|
||||
"Cancel my participation request…": "Cancel my participation request…",
|
||||
"Cancel my participation…": "Cancel my participation…",
|
||||
"Cancel": "Cancel",
|
||||
|
@ -33,6 +37,7 @@
|
|||
"Comments": "Comments",
|
||||
"Confirm my particpation": "Confirm my particpation",
|
||||
"Confirmed: Will happen": "Confirmed: Will happen",
|
||||
"Continue editing": "Continue editing",
|
||||
"Country": "Country",
|
||||
"Create a new event": "Create a new event",
|
||||
"Create a new group": "Create a new group",
|
||||
|
@ -59,12 +64,15 @@
|
|||
"Display participation price": "Display participation price",
|
||||
"Displayed name": "Displayed name",
|
||||
"Do you want to participate in {title}?": "Do you want to participate in {title}?",
|
||||
"Draft": "Draft",
|
||||
"Drafts": "Drafts",
|
||||
"Edit": "Edit",
|
||||
"Either the account is already validated, either the validation token is incorrect.": "Either the account is already validated, either the validation token is incorrect.",
|
||||
"Email": "Email",
|
||||
"Ends on…": "Ends on…",
|
||||
"Enter some tags": "Enter some tags",
|
||||
"Error while validating account": "Error while validating account",
|
||||
"Event already passed": "Event already passed",
|
||||
"Event list": "Event list",
|
||||
"Event {eventTitle} deleted": "Event {eventTitle} deleted",
|
||||
"Event {eventTitle} reported": "Event {eventTitle} reported",
|
||||
|
@ -165,6 +173,7 @@
|
|||
"Public event": "Public event",
|
||||
"Public feeds": "Public feeds",
|
||||
"Public iCal Feed": "Public iCal Feed",
|
||||
"Publish": "Publish",
|
||||
"Published events": "Published events",
|
||||
"RSS/Atom Feed": "RSS/Atom Feed",
|
||||
"Region": "Region",
|
||||
|
@ -172,10 +181,14 @@
|
|||
"Register": "Register",
|
||||
"Registration is currently closed.": "Registration is currently closed.",
|
||||
"Reject": "Reject",
|
||||
"Rejected participations": "Rejected participations",
|
||||
"Rejected": "Rejected",
|
||||
"Report this event": "Report this event",
|
||||
"Report": "Report",
|
||||
"Requests": "Requests",
|
||||
"Resend confirmation email": "Resend confirmation email",
|
||||
"Reset my password": "Reset my password",
|
||||
"Save draft": "Save draft",
|
||||
"Save": "Save",
|
||||
"Search events, groups, etc.": "Search events, groups, etc.",
|
||||
"Search results: \"{search}\"": "Search results: \"{search}\"",
|
||||
|
@ -210,7 +223,9 @@
|
|||
"To confirm, type your event title \"{eventTitle}\"": "To confirm, type your event title \"{eventTitle}\"",
|
||||
"To confirm, type your identity username \"{preferredUsername}\"": "To confirm, type your identity username \"{preferredUsername}\"",
|
||||
"Transfer to {outsideDomain}": "Transfer to {outsideDomain}",
|
||||
"Unfortunately, your participation request was rejected by the organizers.": "Unfortunately, your participation request was rejected by the organizers.",
|
||||
"Unknown error.": "Unknown error.",
|
||||
"Unsaved changes": "Unsaved changes",
|
||||
"Upcoming": "Upcoming",
|
||||
"Update event {name}": "Update event {name}",
|
||||
"Update my event": "Update my event",
|
||||
|
@ -253,9 +268,5 @@
|
|||
"{approved} / {total} seats": "{approved} / {total} seats",
|
||||
"{count} participants": "{count} participants",
|
||||
"{count} requests waiting": "{count} requests waiting",
|
||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
|
||||
"Requests": "Requests",
|
||||
"Rejected": "Rejected",
|
||||
"Rejected participations": "Rejected participations",
|
||||
"Unfortunately, your participation request was rejected by the organizers.": "Unfortunately, your participation request was rejected by the organizers."
|
||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
|
||||
}
|
|
@ -13,10 +13,14 @@
|
|||
"Allow all comments": "Autoriser tous les commentaires",
|
||||
"Approve": "Approuver",
|
||||
"Are you going to this event?": "Allez-vous à cet événement ?",
|
||||
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Étes-vous certain⋅e de vouloir annuler la création de l'événement ? Vous allez perdre toutes vos modifications.",
|
||||
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Étes-vous certain⋅e de vouloir annuler l'édition de l'événement ? Vous allez perdre toutes vos modifications.",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Êtes-vous certain⋅e de vouloir annuler votre participation à l'événement « {title} » ?",
|
||||
"Are you sure you want to delete this event? This action cannot be reverted.": "Êtes-vous certain⋅e de vouloir supprimer cet événement ? Cette action ne peut être annulée.",
|
||||
"Before you can login, you need to click on the link inside it to validate your account": "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte",
|
||||
"By {name}": "Par {name}",
|
||||
"Cancel creation": "Annuler la création",
|
||||
"Cancel edition": "Annuler l'édition",
|
||||
"Cancel my participation request…": "Cancel my participation request…",
|
||||
"Cancel my participation…": "Annuler ma participation…",
|
||||
"Cancel": "Annuler",
|
||||
|
@ -33,6 +37,7 @@
|
|||
"Comments": "Commentaires",
|
||||
"Confirm my particpation": "Confirmer ma particpation",
|
||||
"Confirmed: Will happen": "Confirmé : aura lieu",
|
||||
"Continue editing": "Continuer l'édition",
|
||||
"Country": "Pays",
|
||||
"Create a new event": "Créer un nouvel événement",
|
||||
"Create a new group": "Créer un nouveau groupe",
|
||||
|
@ -59,12 +64,15 @@
|
|||
"Display participation price": "Afficher un prix de participation",
|
||||
"Displayed name": "Nom affiché",
|
||||
"Do you want to participate in {title}?": "Voulez-vous participer à {title} ?",
|
||||
"Draft": "Brouillon",
|
||||
"Drafts": "Brouillons",
|
||||
"Edit": "Éditer",
|
||||
"Either the account is already validated, either the validation token is incorrect.": "Soit le compte est déjà validé, soit le jeton de validation est incorrect.",
|
||||
"Email": "Email",
|
||||
"Ends on…": "Se termine le…",
|
||||
"Enter some tags": "Écrire des tags",
|
||||
"Error while validating account": "Erreur lors de la validation du compte",
|
||||
"Event already passed": "Événement déjà passé",
|
||||
"Event list": "Liste d'événements",
|
||||
"Event {eventTitle} deleted": "Événement {eventTitle} supprimé",
|
||||
"Event {eventTitle} reported": "Événement {eventTitle} signalé",
|
||||
|
@ -165,6 +173,7 @@
|
|||
"Public event": "Événement public",
|
||||
"Public feeds": "Flux publics",
|
||||
"Public iCal Feed": "Flux iCal public",
|
||||
"Publish": "Publier",
|
||||
"Published events": "Événements publiés",
|
||||
"RSS/Atom Feed": "Flux RSS/Atom",
|
||||
"Region": "Région",
|
||||
|
@ -172,10 +181,14 @@
|
|||
"Register": "S'inscrire",
|
||||
"Registration is currently closed.": "Les inscriptions sont actuellement fermées.",
|
||||
"Reject": "Rejetter",
|
||||
"Rejected participations": "Participations rejetées",
|
||||
"Rejected": "Rejetés",
|
||||
"Report this event": "Signaler cet événement",
|
||||
"Report": "Signaler",
|
||||
"Requests": "Requêtes",
|
||||
"Resend confirmation email": "Envoyer à nouveau l'email de confirmation",
|
||||
"Reset my password": "Réinitialiser mon mot de passe",
|
||||
"Save draft": "Enregistrer le brouillon",
|
||||
"Save": "Enregistrer",
|
||||
"Search events, groups, etc.": "Rechercher des événements, des groupes, etc.",
|
||||
"Search results: \"{search}\"": "Résultats de recherche: « {search} »",
|
||||
|
@ -210,7 +223,9 @@
|
|||
"To confirm, type your event title \"{eventTitle}\"": "Pour confirmer, entrez le titre de l'événement « {eventTitle} »",
|
||||
"To confirm, type your identity username \"{preferredUsername}\"": "Pour confirmer, entrez le nom de l’identité « {preferredUsername} »",
|
||||
"Transfer to {outsideDomain}": "Transférer à {outsideDomain}",
|
||||
"Unfortunately, your participation request was rejected by the organizers.": "Malheureusement, votre demande de participation a été refusée par les organisateur⋅ices.",
|
||||
"Unknown error.": "Erreur inconnue.",
|
||||
"Unsaved changes": "Modifications non enregistrées",
|
||||
"Upcoming": "À venir",
|
||||
"Update event {name}": "Éditer l'événement {name}",
|
||||
"Update my event": "Éditer mon événement",
|
||||
|
@ -253,9 +268,5 @@
|
|||
"{approved} / {total} seats": "{approved} / {total} places",
|
||||
"{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
|
||||
"{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente",
|
||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
|
||||
"Requests": "Requêtes",
|
||||
"Rejected": "Rejetés",
|
||||
"Rejected participations": "Participations rejetées",
|
||||
"Unfortunately, your participation request was rejected by the organizers.": "Malheureusement, votre demande de participation a été refusée par les organisateur⋅ices."
|
||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines"
|
||||
}
|
|
@ -96,15 +96,13 @@ export interface IEvent {
|
|||
slug: string;
|
||||
description: string;
|
||||
category: Category | null;
|
||||
|
||||
beginsOn: Date;
|
||||
endsOn: Date | null;
|
||||
publishAt: Date;
|
||||
|
||||
status: EventStatus;
|
||||
visibility: EventVisibility;
|
||||
|
||||
joinOptions: EventJoinOptions;
|
||||
draft: boolean;
|
||||
|
||||
picture: IPicture | null;
|
||||
|
||||
|
@ -176,6 +174,7 @@ export class EventModel implements IEvent {
|
|||
category: Category | null = Category.MEETING;
|
||||
joinOptions = EventJoinOptions.FREE;
|
||||
status = EventStatus.CONFIRMED;
|
||||
draft = true;
|
||||
|
||||
publishAt = new Date();
|
||||
|
||||
|
@ -210,8 +209,8 @@ export class EventModel implements IEvent {
|
|||
|
||||
this.status = hash.status;
|
||||
this.visibility = hash.visibility;
|
||||
|
||||
this.joinOptions = hash.joinOptions;
|
||||
this.draft = hash.draft;
|
||||
|
||||
this.picture = hash.picture;
|
||||
|
||||
|
@ -240,6 +239,7 @@ export class EventModel implements IEvent {
|
|||
status: this.status,
|
||||
visibility: this.visibility,
|
||||
joinOptions: this.joinOptions,
|
||||
draft: this.draft,
|
||||
tags: this.tags.map(t => t.title),
|
||||
picture: this.picture,
|
||||
onlineAddress: this.onlineAddress,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{{ $t('Change') }}
|
||||
</b-button>
|
||||
<b-modal :active.sync="isComponentModalActive" has-modal-card>
|
||||
<identity-picker :currentIdentity="currentIdentity" @input="relay" />
|
||||
<identity-picker v-model="currentIdentity" @input="relay" />
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</h1>
|
||||
|
||||
<div class="columns is-centered">
|
||||
<form class="column is-two-thirds-desktop">
|
||||
<form class="column is-two-thirds-desktop" ref="form">
|
||||
<h2 class="subtitle">
|
||||
{{ $t('General information') }}
|
||||
</h2>
|
||||
|
@ -170,14 +170,16 @@
|
|||
{{ $t('Cancel') }}
|
||||
</b-button>
|
||||
</span>
|
||||
<span class="navbar-item" v-if="isUpdate === false">
|
||||
<b-button type="is-primary" outlined>
|
||||
<!-- If an event has been published we can't make it draft anymore -->
|
||||
<span class="navbar-item" v-if="event.draft === true">
|
||||
<b-button type="is-primary" outlined @click="createOrUpdateDraft">
|
||||
{{ $t('Save draft') }}
|
||||
</b-button>
|
||||
</span>
|
||||
<span class="navbar-item">
|
||||
<b-button type="is-primary" @click="createOrUpdate" @keyup.enter="createOrUpdate">
|
||||
<b-button type="is-primary" @click="createOrUpdatePublish" @keyup.enter="createOrUpdatePublish">
|
||||
<span v-if="isUpdate === false">{{ $t('Create my event') }}</span>
|
||||
<span v-else-if="event.draft === true"> {{ $t('Publish') }}</span>
|
||||
<span v-else> {{ $t('Update my event') }}</span>
|
||||
</b-button>
|
||||
</span>
|
||||
|
@ -238,7 +240,7 @@ import {
|
|||
EventVisibility, IEvent,
|
||||
} from '@/types/event.model';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { Person } from '@/types/actor';
|
||||
import { IActor, Person } from '@/types/actor';
|
||||
import PictureUpload from '@/components/PictureUpload.vue';
|
||||
import Editor from '@/components/Editor.vue';
|
||||
import DateTimePicker from '@/components/Event/DateTimePicker.vue';
|
||||
|
@ -312,7 +314,6 @@ export default class EditEvent extends Vue {
|
|||
this.observer = new IntersectionObserver((entries, observer) => {
|
||||
for (const entry of entries) {
|
||||
if (entry) {
|
||||
console.log(entry);
|
||||
this.showFixedNavbar = !entry.isIntersecting;
|
||||
}
|
||||
}
|
||||
|
@ -322,12 +323,29 @@ export default class EditEvent extends Vue {
|
|||
this.observer.observe(this.$refs.bottomObserver as Element);
|
||||
}
|
||||
|
||||
createOrUpdate(e: Event) {
|
||||
createOrUpdateDraft(e: Event) {
|
||||
e.preventDefault();
|
||||
if (this.validateForm()) {
|
||||
if (this.eventId) return this.updateEvent();
|
||||
|
||||
if (this.eventId) return this.updateEvent();
|
||||
return this.createEvent();
|
||||
}
|
||||
}
|
||||
|
||||
return this.createEvent();
|
||||
createOrUpdatePublish(e: Event) {
|
||||
if (this.validateForm()) {
|
||||
this.event.draft = false;
|
||||
this.createOrUpdateDraft(e);
|
||||
}
|
||||
}
|
||||
|
||||
private validateForm() {
|
||||
const form = this.$refs.form as HTMLFormElement;
|
||||
if (form.checkValidity()) {
|
||||
return true;
|
||||
}
|
||||
form.reportValidity();
|
||||
return false;
|
||||
}
|
||||
|
||||
async createEvent() {
|
||||
|
@ -412,13 +430,16 @@ export default class EditEvent extends Vue {
|
|||
* Confirm cancel
|
||||
*/
|
||||
confirmGoBack() {
|
||||
if (!this.isEventModified) {
|
||||
return this.$router.go(-1);
|
||||
}
|
||||
const title: string = this.isUpdate ?
|
||||
this.$t('Cancel edition') as string :
|
||||
this.$t('Cancel creation') as string;
|
||||
const message: string = this.isUpdate ?
|
||||
this.$t('Are you sure you want to cancel the event edition? You\'ll lose all modifications.',
|
||||
this.$t("Are you sure you want to cancel the event edition? You'll lose all modifications.",
|
||||
{ title: this.event.title }) as string :
|
||||
this.$t('Are you sure you want to cancel the event creation? You\'ll lose all modifications.',
|
||||
this.$t("Are you sure you want to cancel the event creation? You'll lose all modifications.",
|
||||
{ title: this.event.title }) as string;
|
||||
|
||||
this.$buefy.dialog.confirm({
|
||||
|
|
|
@ -18,15 +18,15 @@
|
|||
</div>
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
</div>
|
||||
<div class="has-text-right">
|
||||
<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>
|
||||
<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"
|
||||
v-if="currentActor.id && !actorIsOrganizer && !event.draft"
|
||||
:participation="participations[0]"
|
||||
:current-actor="currentActor"
|
||||
@joinEvent="joinEvent"
|
||||
|
@ -34,15 +34,25 @@
|
|||
@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>
|
||||
<div class="metadata columns">
|
||||
<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>-->
|
||||
<span class="tag" v-if="event.tags" v-for="tag in event.tags">{{ tag.title }}</span>
|
||||
<span class="visibility">
|
||||
<span v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</span>
|
||||
<span v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</span>
|
||||
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags">{{ tag.title }}</b-tag>
|
||||
<span v-if="event.tags > 0">⋅</span>
|
||||
<span class="visibility" v-if="!event.draft">
|
||||
<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>
|
||||
</p>
|
||||
<div class="date-and-add-to-calendar">
|
||||
|
@ -50,7 +60,7 @@
|
|||
<b-icon icon="calendar-clock" />
|
||||
<event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
|
||||
</div>
|
||||
<a class="add-to-calendar" @click="downloadIcsEvent()">
|
||||
<a class="add-to-calendar" @click="downloadIcsEvent()" v-if="!event.draft">
|
||||
<b-icon icon="calendar-plus" />
|
||||
{{ $t('Add to my calendar') }}
|
||||
</a>
|
||||
|
@ -61,7 +71,7 @@
|
|||
</div>
|
||||
<div class="column sidebar">
|
||||
<div class="field has-addons" v-if="currentActor.id">
|
||||
<p class="control" v-if="actorIsOrganizer">
|
||||
<p class="control" v-if="actorIsOrganizer || event.draft">
|
||||
<router-link
|
||||
class="button"
|
||||
:to="{ name: 'EditEvent', params: {eventId: event.uuid}}"
|
||||
|
@ -69,7 +79,7 @@
|
|||
{{ $t('Edit') }}
|
||||
</router-link>
|
||||
</p>
|
||||
<p class="control" v-if="actorIsOrganizer">
|
||||
<p class="control" v-if="actorIsOrganizer || event.draft">
|
||||
<a class="button is-danger" @click="openDeleteEventModalWrapper">
|
||||
{{ $t('Delete') }}
|
||||
</a>
|
||||
|
@ -133,7 +143,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="share">
|
||||
<section class="share" v-if="!event.draft">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-half has-text-centered">
|
||||
|
@ -433,6 +443,10 @@ export default class Event extends EventMixin {
|
|||
return this.participations.length > 0 && this.participations[0].role === ParticipantRole.CREATOR;
|
||||
}
|
||||
|
||||
get endDate() {
|
||||
return this.event.endsOn !== null && this.event.endsOn > this.event.beginsOn ? this.event.endsOn : this.event.beginsOn;
|
||||
}
|
||||
|
||||
get twitterShareUrl(): string {
|
||||
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.event.url)}&text=${this.event.title}`;
|
||||
}
|
||||
|
@ -597,18 +611,14 @@ export default class Event extends EventMixin {
|
|||
|
||||
p.tags {
|
||||
span {
|
||||
&.tag {
|
||||
&.tag.is-success {
|
||||
&::before {
|
||||
content: '#';
|
||||
}
|
||||
text-transform: uppercase;
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
&.visibility::before {
|
||||
content: "⋅"
|
||||
}
|
||||
|
||||
|
||||
margin: auto 5px;
|
||||
}
|
||||
margin-bottom: 1rem;
|
||||
|
|
|
@ -26,6 +26,19 @@
|
|||
v-if="hasMoreFutureParticipations && (futureParticipations.length === limit)" @click="loadMoreFutureParticipations" size="is-large" type="is-primary">{{ $t('Load more') }}</b-button>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="drafts.length > 0">
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Drafts') }}
|
||||
</h2>
|
||||
<div class="columns is-multiline">
|
||||
<EventCard
|
||||
v-for="draft in drafts"
|
||||
:key="draft.uuid"
|
||||
:event="draft"
|
||||
class="is-one-quarter-desktop column"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="pastParticipations.length > 0">
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Past events') }}
|
||||
|
@ -56,13 +69,15 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { LOGGED_USER_PARTICIPATIONS } from '@/graphql/actor';
|
||||
import { IParticipant, Participant } from '@/types/event.model';
|
||||
import { LOGGED_USER_PARTICIPATIONS, LOGGED_USER_DRAFTS } from '@/graphql/actor';
|
||||
import { EventModel, IEvent, IParticipant, Participant } from '@/types/event.model';
|
||||
import EventListCard from '@/components/Event/EventListCard.vue';
|
||||
import EventCard from '@/components/Event/EventCard.vue';
|
||||
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
EventCard,
|
||||
EventListCard,
|
||||
},
|
||||
apollo: {
|
||||
|
@ -75,6 +90,14 @@ import EventListCard from '@/components/Event/EventListCard.vue';
|
|||
},
|
||||
update: data => data.loggedUser.participations.map(participation => new Participant(participation)),
|
||||
},
|
||||
drafts: {
|
||||
query: LOGGED_USER_DRAFTS,
|
||||
variables: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
update: data => data.loggedUser.drafts.map(event => new EventModel(event)),
|
||||
},
|
||||
pastParticipations: {
|
||||
query: LOGGED_USER_PARTICIPATIONS,
|
||||
variables: {
|
||||
|
@ -97,6 +120,8 @@ export default class MyEvents extends Vue {
|
|||
pastParticipations: IParticipant[] = [];
|
||||
hasMorePastParticipations: boolean = true;
|
||||
|
||||
drafts: IEvent[] = [];
|
||||
|
||||
private monthlyParticipations(participations: IParticipant[]): Map<string, Participant[]> {
|
||||
const res = participations.filter(({ event }) => event.beginsOn != null);
|
||||
res.sort(
|
||||
|
|
|
@ -32,6 +32,7 @@ defmodule Mobilizon.Events.Event do
|
|||
ends_on: DateTime.t(),
|
||||
title: String.t(),
|
||||
status: EventStatus.t(),
|
||||
draft: boolean,
|
||||
visibility: EventVisibility.t(),
|
||||
join_options: JoinOptions.t(),
|
||||
publish_at: DateTime.t(),
|
||||
|
@ -57,6 +58,7 @@ defmodule Mobilizon.Events.Event do
|
|||
:ends_on,
|
||||
:category,
|
||||
:status,
|
||||
:draft,
|
||||
:visibility,
|
||||
:publish_at,
|
||||
:online_address,
|
||||
|
@ -74,6 +76,7 @@ defmodule Mobilizon.Events.Event do
|
|||
:ends_on,
|
||||
:category,
|
||||
:status,
|
||||
:draft,
|
||||
:visibility,
|
||||
:join_options,
|
||||
:publish_at,
|
||||
|
@ -93,6 +96,7 @@ defmodule Mobilizon.Events.Event do
|
|||
field(:ends_on, :utc_datetime)
|
||||
field(:title, :string)
|
||||
field(:status, EventStatus, default: :confirmed)
|
||||
field(:draft, :boolean, default: false)
|
||||
field(:visibility, EventVisibility, default: :public)
|
||||
field(:join_options, JoinOptions, default: :free)
|
||||
field(:publish_at, :utc_datetime)
|
||||
|
|
|
@ -169,6 +169,7 @@ defmodule Mobilizon.Events do
|
|||
url
|
||||
|> event_by_url_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> preload_for_event()
|
||||
|> Repo.one()
|
||||
|
||||
|
@ -190,18 +191,32 @@ defmodule Mobilizon.Events do
|
|||
url
|
||||
|> event_by_url_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> preload_for_event()
|
||||
|> Repo.one!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an event by its UUID, with all associations loaded.
|
||||
Gets a public event by its UUID, with all associations loaded.
|
||||
"""
|
||||
@spec get_public_event_by_uuid_with_preload(String.t()) :: Event.t() | nil
|
||||
def get_public_event_by_uuid_with_preload(uuid) do
|
||||
uuid
|
||||
|> event_by_uuid_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> preload_for_event()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an event by its UUID, with all associations loaded.
|
||||
"""
|
||||
@spec get_own_event_by_uuid_with_preload(String.t(), integer()) :: Event.t() | nil
|
||||
def get_own_event_by_uuid_with_preload(uuid, user_id) do
|
||||
uuid
|
||||
|> event_by_uuid_query()
|
||||
|> user_events_query(user_id)
|
||||
|> preload_for_event()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
@ -215,6 +230,7 @@ defmodule Mobilizon.Events do
|
|||
|> upcoming_public_event_for_actor_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_not_event_uuid(not_event_uuid)
|
||||
|> filter_draft()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
|
@ -223,16 +239,18 @@ defmodule Mobilizon.Events do
|
|||
"""
|
||||
@spec create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_event(attrs \\ %{}) do
|
||||
with {:ok, %Event{} = event} <- do_create_event(attrs),
|
||||
with {:ok, %Event{draft: false} = event} <- do_create_event(attrs),
|
||||
{:ok, %Participant{} = _participant} <-
|
||||
%Participant{}
|
||||
|> Participant.changeset(%{
|
||||
create_participant(%{
|
||||
actor_id: event.organizer_actor_id,
|
||||
role: :creator,
|
||||
event_id: event.id
|
||||
})
|
||||
|> Repo.insert() do
|
||||
}) do
|
||||
{:ok, event}
|
||||
else
|
||||
# We don't create a creator participant if the event is a draft
|
||||
{:ok, %Event{draft: true} = event} -> {:ok, event}
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -262,10 +280,24 @@ defmodule Mobilizon.Events do
|
|||
Updates an event.
|
||||
"""
|
||||
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_event(%Event{} = old_event, attrs) do
|
||||
def update_event(
|
||||
%Event{draft: old_draft_status, id: event_id, organizer_actor_id: organizer_actor_id} =
|
||||
old_event,
|
||||
attrs
|
||||
) do
|
||||
with %Ecto.Changeset{changes: changes} = changeset <-
|
||||
old_event |> Repo.preload(:tags) |> Event.update_changeset(attrs) do
|
||||
with {:ok, %Event{} = new_event} <- Repo.update(changeset) do
|
||||
with {:ok, %Event{draft: new_draft_status} = new_event} <- Repo.update(changeset) do
|
||||
# If the event is no longer a draft
|
||||
if old_draft_status == true && new_draft_status == false do
|
||||
{:ok, %Participant{} = _participant} =
|
||||
create_participant(%{
|
||||
event_id: event_id,
|
||||
role: :creator,
|
||||
actor_id: organizer_actor_id
|
||||
})
|
||||
end
|
||||
|
||||
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
|
||||
old_event,
|
||||
new_event,
|
||||
|
@ -309,6 +341,7 @@ defmodule Mobilizon.Events do
|
|||
|> sort(sort, direction)
|
||||
|> filter_future_events(is_future)
|
||||
|> filter_unlisted(is_unlisted)
|
||||
|> filter_draft()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
|
@ -320,6 +353,7 @@ defmodule Mobilizon.Events do
|
|||
tags
|
||||
|> Enum.map(& &1.id)
|
||||
|> events_by_tags_query(limit)
|
||||
|> filter_draft()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
|
@ -333,6 +367,7 @@ defmodule Mobilizon.Events do
|
|||
actor_id
|
||||
|> event_for_actor_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> preload_for_event()
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
|
@ -345,6 +380,15 @@ defmodule Mobilizon.Events do
|
|||
{:ok, events, events_count}
|
||||
end
|
||||
|
||||
@spec list_drafts_for_user(integer, integer | nil, integer | nil) :: [Event.t()]
|
||||
def list_drafts_for_user(user_id, page \\ nil, limit \\ nil) do
|
||||
Event
|
||||
|> user_events_query(user_id)
|
||||
|> filter_draft(true)
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds close events to coordinates.
|
||||
Radius is in meters and defaults to 50km.
|
||||
|
@ -354,6 +398,7 @@ defmodule Mobilizon.Events do
|
|||
"SRID=#{srid};POINT(#{lon} #{lat})"
|
||||
|> Geo.WKT.decode!()
|
||||
|> close_events_query(radius)
|
||||
|> filter_draft()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
|
@ -364,6 +409,7 @@ defmodule Mobilizon.Events do
|
|||
def count_local_events do
|
||||
count_local_events_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
|
@ -1134,6 +1180,16 @@ defmodule Mobilizon.Events do
|
|||
)
|
||||
end
|
||||
|
||||
@spec user_events_query(Ecto.Query.t(), number()) :: Ecto.Query.t()
|
||||
defp user_events_query(query, user_id) do
|
||||
from(
|
||||
e in query,
|
||||
join: a in Actor,
|
||||
on: a.id == e.organizer_actor_id,
|
||||
where: a.user_id == ^user_id
|
||||
)
|
||||
end
|
||||
|
||||
@spec events_by_name_query(String.t()) :: Ecto.Query.t()
|
||||
defp events_by_name_query(name) do
|
||||
from(
|
||||
|
@ -1372,6 +1428,11 @@ defmodule Mobilizon.Events do
|
|||
from(e in query, where: e.uuid != ^not_event_uuid)
|
||||
end
|
||||
|
||||
@spec filter_draft(Ecto.Query.t(), boolean) :: Ecto.Query.t()
|
||||
defp filter_draft(query, is_draft \\ false) do
|
||||
from(e in query, where: e.draft == ^is_draft)
|
||||
end
|
||||
|
||||
@spec filter_future_events(Ecto.Query.t(), boolean) :: Ecto.Query.t()
|
||||
defp filter_future_events(query, true) do
|
||||
from(q in query, where: q.begins_on > ^DateTime.utc_now())
|
||||
|
|
|
@ -31,7 +31,8 @@ defmodule MobilizonWeb.API.Events do
|
|||
to: args.to,
|
||||
actor: organizer_actor,
|
||||
object: event,
|
||||
local: true
|
||||
# For now we don't federate drafts but it will be needed if we want to edit them as groups
|
||||
local: args.metadata.draft == false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
@ -65,7 +66,7 @@ defmodule MobilizonWeb.API.Events do
|
|||
actor: organizer_actor.url,
|
||||
cc: [],
|
||||
object: event,
|
||||
local: true
|
||||
local: args.metadata.draft == false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
@ -95,7 +96,8 @@ defmodule MobilizonWeb.API.Events do
|
|||
join_options: Map.get(args, :join_options),
|
||||
status: Map.get(args, :status),
|
||||
online_address: Map.get(args, :online_address),
|
||||
phone_address: Map.get(args, :phone_address)
|
||||
phone_address: Map.get(args, :phone_address),
|
||||
draft: Map.get(args, :draft)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -31,6 +31,20 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||
{:error, :events_max_limit_reached}
|
||||
end
|
||||
|
||||
def find_event(
|
||||
_parent,
|
||||
%{uuid: uuid},
|
||||
%{context: %{current_user: %User{id: user_id}}} = _resolution
|
||||
) do
|
||||
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} 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"}
|
||||
end
|
||||
end
|
||||
|
||||
def find_event(_parent, %{uuid: uuid}, _resolution) do
|
||||
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
|
||||
{:has_event, %Event{} = event} ->
|
||||
|
@ -264,6 +278,9 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Organizer actor id is not owned by the user"}
|
||||
|
||||
{:error, %Ecto.Changeset{} = error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -243,6 +243,19 @@ defmodule MobilizonWeb.Resolvers.User do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of draft events for the current user
|
||||
"""
|
||||
def user_drafted_events(%User{id: user_id}, args, %{
|
||||
context: %{current_user: %User{id: logged_user_id}}
|
||||
}) do
|
||||
with {:same_user, true} <- {:same_user, user_id == logged_user_id},
|
||||
events <-
|
||||
Events.list_drafts_for_user(user_id, Map.get(args, :page), Map.get(args, :limit)) do
|
||||
{:ok, events}
|
||||
end
|
||||
end
|
||||
|
||||
def change_password(_parent, %{old_password: old_password, new_password: new_password}, %{
|
||||
context: %{current_user: %User{password_hash: old_password_hash} = user}
|
||||
}) do
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
import MobilizonWeb.Schema.Utils
|
||||
|
||||
alias Mobilizon.{Actors, Addresses}
|
||||
|
||||
|
@ -60,6 +61,8 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
|
||||
field(:category, :string, description: "The event's category")
|
||||
|
||||
field(:draft, :boolean, description: "Whether or not the event is a draft")
|
||||
|
||||
field(:participant_stats, :participant_stats, resolve: &Event.stats_participants_for_event/3)
|
||||
|
||||
field(:participants, list_of(:participant), description: "The event's participants") do
|
||||
|
@ -252,8 +255,9 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
arg(:category, :string, default_value: "meeting")
|
||||
arg(:physical_address, :address_input)
|
||||
arg(:options, :event_options_input)
|
||||
arg(:draft, :boolean, default_value: false)
|
||||
|
||||
resolve(&Event.create_event/3)
|
||||
resolve(handle_errors(&Event.create_event/3))
|
||||
end
|
||||
|
||||
@desc "Update an event"
|
||||
|
@ -280,8 +284,9 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
arg(:category, :string)
|
||||
arg(:physical_address, :address_input)
|
||||
arg(:options, :event_options_input)
|
||||
arg(:draft, :boolean)
|
||||
|
||||
resolve(&Event.update_event/3)
|
||||
resolve(handle_errors(&Event.update_event/3))
|
||||
end
|
||||
|
||||
@desc "Delete an event"
|
||||
|
|
|
@ -49,7 +49,7 @@ defmodule MobilizonWeb.Schema.UserType do
|
|||
field(:locale, :string, description: "The user's locale")
|
||||
|
||||
field(:participations, list_of(:participant),
|
||||
description: "The list of events this user goes to"
|
||||
description: "The list of participations this user has"
|
||||
) do
|
||||
arg(:after_datetime, :datetime)
|
||||
arg(:before_datetime, :datetime)
|
||||
|
@ -57,6 +57,12 @@ defmodule MobilizonWeb.Schema.UserType do
|
|||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&User.user_participations/3)
|
||||
end
|
||||
|
||||
field(:drafts, list_of(:event), description: "The list of draft events this user has created") do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&User.user_drafted_events/3)
|
||||
end
|
||||
end
|
||||
|
||||
enum :user_role do
|
||||
|
|
|
@ -70,6 +70,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
|||
"status" => object["status"],
|
||||
"online_address" => object["onlineAddress"],
|
||||
"phone_address" => object["phoneAddress"],
|
||||
"draft" => object["draft"] || false,
|
||||
"url" => object["id"],
|
||||
"uuid" => object["uuid"],
|
||||
"tags" => tags,
|
||||
|
@ -111,6 +112,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
|||
"joinOptions" => to_string(event.join_options),
|
||||
"endTime" => event.ends_on |> date_to_string(),
|
||||
"tag" => event.tags |> build_tags(),
|
||||
"draft" => event.draft,
|
||||
"id" => event.url,
|
||||
"url" => event.url
|
||||
}
|
||||
|
|
|
@ -319,6 +319,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
|||
"status" => metadata.status,
|
||||
"onlineAddress" => metadata.online_address,
|
||||
"phoneAddress" => metadata.phone_address,
|
||||
"draft" => metadata.draft,
|
||||
"uuid" => uuid,
|
||||
"tag" =>
|
||||
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Mobilizon.Storage.Repo.Migrations.AddDraftFlagToEvents do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:events) do
|
||||
add(:draft, :boolean, default: false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# source: http://localhost:4000/api
|
||||
# timestamp: Mon Sep 30 2019 09:56:05 GMT+0200 (GMT+02:00)
|
||||
# timestamp: Wed Oct 02 2019 16:30:43 GMT+0200 (GMT+02:00)
|
||||
|
||||
schema {
|
||||
query: RootQueryType
|
||||
|
@ -264,6 +264,9 @@ type Event implements ActionLogObject {
|
|||
"""The event's description"""
|
||||
description: String
|
||||
|
||||
"""Whether or not the event is a draft"""
|
||||
draft: Boolean
|
||||
|
||||
"""Datetime for when the event ends"""
|
||||
endsOn: DateTime
|
||||
|
||||
|
@ -897,6 +900,7 @@ type RootMutationType {
|
|||
beginsOn: DateTime!
|
||||
category: String = "meeting"
|
||||
description: String!
|
||||
draft: Boolean = false
|
||||
endsOn: DateTime
|
||||
joinOptions: EventJoinOptions = FREE
|
||||
onlineAddress: String
|
||||
|
@ -973,7 +977,7 @@ type RootMutationType {
|
|||
createReportNote(content: String, moderatorId: ID!, reportId: ID!): ReportNote
|
||||
|
||||
"""Create an user"""
|
||||
createUser(email: String!, password: String!): User
|
||||
createUser(email: String!, locale: String, password: String!): User
|
||||
|
||||
"""Delete an event"""
|
||||
deleteEvent(actorId: ID!, eventId: ID!): DeletedObject
|
||||
|
@ -1030,19 +1034,20 @@ type RootMutationType {
|
|||
): Person
|
||||
|
||||
"""Resend registration confirmation token"""
|
||||
resendConfirmationEmail(email: String!, locale: String = "en"): String
|
||||
resendConfirmationEmail(email: String!, locale: String): String
|
||||
|
||||
"""Reset user password"""
|
||||
resetPassword(locale: String = "en", password: String!, token: String!): Login
|
||||
|
||||
"""Send a link through email to reset user password"""
|
||||
sendResetPassword(email: String!, locale: String = "en"): String
|
||||
sendResetPassword(email: String!, locale: String): String
|
||||
|
||||
"""Update an event"""
|
||||
updateEvent(
|
||||
beginsOn: DateTime
|
||||
category: String
|
||||
description: String
|
||||
draft: Boolean
|
||||
endsOn: DateTime
|
||||
eventId: ID!
|
||||
joinOptions: EventJoinOptions = FREE
|
||||
|
@ -1212,6 +1217,9 @@ type User {
|
|||
"""The user's default actor"""
|
||||
defaultActor: Person
|
||||
|
||||
"""The list of draft events this user has created"""
|
||||
drafts(limit: Int = 10, page: Int = 1): [Event]
|
||||
|
||||
"""The user's email"""
|
||||
email: String!
|
||||
|
||||
|
@ -1221,7 +1229,10 @@ type User {
|
|||
"""The user's ID"""
|
||||
id: ID!
|
||||
|
||||
"""The list of events this user goes to"""
|
||||
"""The user's locale"""
|
||||
locale: String
|
||||
|
||||
"""The list of participations this user has"""
|
||||
participations(afterDatetime: DateTime, beforeDatetime: DateTime, limit: Int = 10, page: Int = 1): [Participant]
|
||||
|
||||
"""The user's list of profiles (identities)"""
|
||||
|
|
|
@ -119,6 +119,97 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
|||
assert json_response(res, 200)["data"]["createEvent"]["title"] == "come to my event"
|
||||
end
|
||||
|
||||
test "create_event/3 creates an event as a draft", %{conn: conn, actor: actor, user: user} do
|
||||
mutation = """
|
||||
mutation {
|
||||
createEvent(
|
||||
title: "come to my event",
|
||||
description: "it will be fine",
|
||||
begins_on: "#{
|
||||
DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
|
||||
}",
|
||||
organizer_actor_id: "#{actor.id}",
|
||||
category: "birthday",
|
||||
draft: true
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
id,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["data"]["createEvent"]["title"] == "come to my event"
|
||||
assert json_response(res, 200)["data"]["createEvent"]["draft"] == true
|
||||
|
||||
event_uuid = json_response(res, 200)["data"]["createEvent"]["uuid"]
|
||||
event_id = json_response(res, 200)["data"]["createEvent"]["id"]
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event_uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] =~ "not found"
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event_uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["event"]["draft"] == true
|
||||
|
||||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor.preferred_username}") {
|
||||
id,
|
||||
participations(eventId: #{event_id}) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == []
|
||||
end
|
||||
|
||||
test "create_event/3 creates an event with options", %{conn: conn, actor: actor, user: user} do
|
||||
begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
|
||||
ends_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
|
||||
|
@ -684,6 +775,157 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
|||
"picture for my event"
|
||||
end
|
||||
|
||||
test "update_event/3 respects the draft status", %{conn: conn, actor: actor, user: user} do
|
||||
event = insert(:event, organizer_actor: actor, draft: true)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: #{event.id},
|
||||
title: "my event updated but still draft"
|
||||
) {
|
||||
draft,
|
||||
title,
|
||||
uuid
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["data"]["updateEvent"]["draft"] == true
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] =~ "not found"
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["event"]["draft"] == true
|
||||
|
||||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor.preferred_username}") {
|
||||
id,
|
||||
participations(eventId: #{event.id}) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == []
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: #{event.id},
|
||||
title: "my event updated and no longer draft",
|
||||
draft: false
|
||||
) {
|
||||
draft,
|
||||
title,
|
||||
uuid
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["data"]["updateEvent"]["draft"] == false
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["event"]["draft"] == false
|
||||
|
||||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor.preferred_username}") {
|
||||
id,
|
||||
participations(eventId: #{event.id}) {
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == [
|
||||
%{
|
||||
"actor" => %{"id" => to_string(actor.id)},
|
||||
"event" => %{"id" => to_string(event.id)},
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "list_events/3 returns events", context do
|
||||
event = insert(:event)
|
||||
|
||||
|
@ -782,6 +1024,24 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
|||
assert json_response(res, 200)["data"]["events"] |> Enum.map(& &1["uuid"]) == []
|
||||
end
|
||||
|
||||
test "list_events/3 doesn't list draft events", context do
|
||||
insert(:event, visibility: :public, draft: true)
|
||||
|
||||
query = """
|
||||
{
|
||||
events {
|
||||
uuid,
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert json_response(res, 200)["data"]["events"] |> Enum.map(& &1["uuid"]) == []
|
||||
end
|
||||
|
||||
test "find_event/3 returns an unlisted event", context do
|
||||
event = insert(:event, visibility: :unlisted)
|
||||
|
||||
|
|
Loading…
Reference in a new issue