Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
25836b5971
|
@ -132,7 +132,9 @@ exunit:
|
||||||
variables:
|
variables:
|
||||||
MIX_ENV: test
|
MIX_ENV: test
|
||||||
before_script:
|
before_script:
|
||||||
- mix deps.get && mix tz_world.update
|
- mix deps.get
|
||||||
|
- mix compile
|
||||||
|
- mix tz_world.update
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
script:
|
script:
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 3.2.0-beta.2 (2023-09-01)
|
||||||
|
|
||||||
|
Fixes a CI issue that prevented 3.2.0-beta.2 being released.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **front:** fix behavior of local toggle for profiles & groups view depending on domain value ([84f62cd](https://framagit.org/framasoft/mobilizon/commit/84f62cd043d5cf5d186fea6f24a1a9dff5fc64ce))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 3.2.0-beta.1 (2023-09-01)
|
## 3.2.0-beta.1 (2023-09-01)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -41,7 +41,10 @@ config :mobilizon, :instance,
|
||||||
email_reply_to: "noreply@localhost"
|
email_reply_to: "noreply@localhost"
|
||||||
|
|
||||||
config :mobilizon, :groups, enabled: true
|
config :mobilizon, :groups, enabled: true
|
||||||
config :mobilizon, :events, creation: true
|
|
||||||
|
config :mobilizon, :events,
|
||||||
|
creation: true,
|
||||||
|
external: true
|
||||||
|
|
||||||
config :mobilizon, :restrictions, only_admin_can_create_groups: false
|
config :mobilizon, :restrictions, only_admin_can_create_groups: false
|
||||||
config :mobilizon, :restrictions, only_groups_can_create_events: false
|
config :mobilizon, :restrictions, only_groups_can_create_events: false
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mobilizon",
|
"name": "mobilizon",
|
||||||
"version": "3.2.0-beta.1",
|
"version": "3.2.0-beta.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="">
|
||||||
|
<external-participation-button
|
||||||
|
v-if="event && event.joinOptions === EventJoinOptions.EXTERNAL"
|
||||||
|
:event="event"
|
||||||
|
:current-actor="currentActor"
|
||||||
|
/>
|
||||||
|
|
||||||
<participation-section
|
<participation-section
|
||||||
v-if="event && anonymousParticipationConfig"
|
v-else-if="event && anonymousParticipationConfig"
|
||||||
:participation="participations[0]"
|
:participation="participations[0]"
|
||||||
:event="event"
|
:event="event"
|
||||||
:anonymousParticipation="anonymousParticipation"
|
:anonymousParticipation="anonymousParticipation"
|
||||||
|
@ -15,7 +21,10 @@
|
||||||
@cancel-anonymous-participation="cancelAnonymousParticipation"
|
@cancel-anonymous-participation="cancelAnonymousParticipation"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col gap-1 mt-1">
|
<div class="flex flex-col gap-1 mt-1">
|
||||||
<p class="inline-flex gap-2 ml-auto">
|
<p
|
||||||
|
class="inline-flex gap-2 ml-auto"
|
||||||
|
v-if="event.joinOptions !== EventJoinOptions.EXTERNAL"
|
||||||
|
>
|
||||||
<TicketConfirmationOutline />
|
<TicketConfirmationOutline />
|
||||||
<router-link
|
<router-link
|
||||||
class="participations-link"
|
class="participations-link"
|
||||||
|
@ -349,6 +358,7 @@ import { useMutation } from "@vue/apollo-composable";
|
||||||
import { useCreateReport } from "@/composition/apollo/report";
|
import { useCreateReport } from "@/composition/apollo/report";
|
||||||
import { useDeleteEvent } from "@/composition/apollo/event";
|
import { useDeleteEvent } from "@/composition/apollo/event";
|
||||||
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
||||||
|
import ExternalParticipationButton from "./ExternalParticipationButton.vue";
|
||||||
|
|
||||||
const ShareEventModal = defineAsyncComponent(
|
const ShareEventModal = defineAsyncComponent(
|
||||||
() => import("@/components/Event/ShareEventModal.vue")
|
() => import("@/components/Event/ShareEventModal.vue")
|
||||||
|
|
30
js/src/components/Event/ExternalParticipationButton.vue
Normal file
30
js/src/components/Event/ExternalParticipationButton.vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<o-button
|
||||||
|
tag="a"
|
||||||
|
:href="
|
||||||
|
event.externalParticipationUrl
|
||||||
|
? encodeURI(`${event.externalParticipationUrl}?uuid=${event.uuid}`)
|
||||||
|
: '#'
|
||||||
|
"
|
||||||
|
rel="noopener ugc"
|
||||||
|
target="_blank"
|
||||||
|
:disabled="!event.externalParticipationUrl"
|
||||||
|
icon-right="OpenInNew"
|
||||||
|
>
|
||||||
|
{{ t("Go to booking") }}
|
||||||
|
</o-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { IEvent } from "../../types/event.model";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
event: IEvent;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const event = computed(() => props.event);
|
||||||
|
</script>
|
|
@ -72,6 +72,7 @@ export const CONFIG = gql`
|
||||||
features {
|
features {
|
||||||
groups
|
groups
|
||||||
eventCreation
|
eventCreation
|
||||||
|
eventExternal
|
||||||
antispam
|
antispam
|
||||||
}
|
}
|
||||||
restrictions {
|
restrictions {
|
||||||
|
@ -370,6 +371,7 @@ export const FEATURES = gql`
|
||||||
features {
|
features {
|
||||||
groups
|
groups
|
||||||
eventCreation
|
eventCreation
|
||||||
|
eventExternal
|
||||||
antispam
|
antispam
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ const FULL_EVENT_FRAGMENT = gql`
|
||||||
status
|
status
|
||||||
visibility
|
visibility
|
||||||
joinOptions
|
joinOptions
|
||||||
|
externalParticipationUrl
|
||||||
draft
|
draft
|
||||||
language
|
language
|
||||||
category
|
category
|
||||||
|
@ -121,6 +122,7 @@ export const FETCH_EVENT_BASIC = gql`
|
||||||
id
|
id
|
||||||
uuid
|
uuid
|
||||||
joinOptions
|
joinOptions
|
||||||
|
externalParticipationUrl
|
||||||
participantStats {
|
participantStats {
|
||||||
going
|
going
|
||||||
notApproved
|
notApproved
|
||||||
|
@ -199,6 +201,7 @@ export const CREATE_EVENT = gql`
|
||||||
$status: EventStatus
|
$status: EventStatus
|
||||||
$visibility: EventVisibility
|
$visibility: EventVisibility
|
||||||
$joinOptions: EventJoinOptions
|
$joinOptions: EventJoinOptions
|
||||||
|
$externalParticipationUrl: String
|
||||||
$draft: Boolean
|
$draft: Boolean
|
||||||
$tags: [String]
|
$tags: [String]
|
||||||
$picture: MediaInput
|
$picture: MediaInput
|
||||||
|
@ -220,6 +223,7 @@ export const CREATE_EVENT = gql`
|
||||||
status: $status
|
status: $status
|
||||||
visibility: $visibility
|
visibility: $visibility
|
||||||
joinOptions: $joinOptions
|
joinOptions: $joinOptions
|
||||||
|
externalParticipationUrl: $externalParticipationUrl
|
||||||
draft: $draft
|
draft: $draft
|
||||||
tags: $tags
|
tags: $tags
|
||||||
picture: $picture
|
picture: $picture
|
||||||
|
@ -247,6 +251,7 @@ export const EDIT_EVENT = gql`
|
||||||
$status: EventStatus
|
$status: EventStatus
|
||||||
$visibility: EventVisibility
|
$visibility: EventVisibility
|
||||||
$joinOptions: EventJoinOptions
|
$joinOptions: EventJoinOptions
|
||||||
|
$externalParticipationUrl: String
|
||||||
$draft: Boolean
|
$draft: Boolean
|
||||||
$tags: [String]
|
$tags: [String]
|
||||||
$picture: MediaInput
|
$picture: MediaInput
|
||||||
|
@ -269,6 +274,7 @@ export const EDIT_EVENT = gql`
|
||||||
status: $status
|
status: $status
|
||||||
visibility: $visibility
|
visibility: $visibility
|
||||||
joinOptions: $joinOptions
|
joinOptions: $joinOptions
|
||||||
|
externalParticipationUrl: $externalParticipationUrl
|
||||||
draft: $draft
|
draft: $draft
|
||||||
tags: $tags
|
tags: $tags
|
||||||
picture: $picture
|
picture: $picture
|
||||||
|
|
|
@ -295,6 +295,10 @@ export const CREATE_GROUP = gql`
|
||||||
$summary: String
|
$summary: String
|
||||||
$avatar: MediaInput
|
$avatar: MediaInput
|
||||||
$banner: MediaInput
|
$banner: MediaInput
|
||||||
|
$physicalAddress: AddressInput
|
||||||
|
$visibility: GroupVisibility
|
||||||
|
$openness: Openness
|
||||||
|
$manuallyApprovesFollowers: Boolean
|
||||||
) {
|
) {
|
||||||
createGroup(
|
createGroup(
|
||||||
preferredUsername: $preferredUsername
|
preferredUsername: $preferredUsername
|
||||||
|
@ -302,6 +306,10 @@ export const CREATE_GROUP = gql`
|
||||||
summary: $summary
|
summary: $summary
|
||||||
banner: $banner
|
banner: $banner
|
||||||
avatar: $avatar
|
avatar: $avatar
|
||||||
|
physicalAddress: $physicalAddress
|
||||||
|
visibility: $visibility
|
||||||
|
openness: $openness
|
||||||
|
manuallyApprovesFollowers: $manuallyApprovesFollowers
|
||||||
) {
|
) {
|
||||||
...ActorFragment
|
...ActorFragment
|
||||||
banner {
|
banner {
|
||||||
|
|
|
@ -1605,5 +1605,10 @@
|
||||||
"Reported by an unknown actor": "Reported by an unknown actor",
|
"Reported by an unknown actor": "Reported by an unknown actor",
|
||||||
"Reported at": "Reported at",
|
"Reported at": "Reported at",
|
||||||
"Updated at": "Updated at",
|
"Updated at": "Updated at",
|
||||||
"Suspend the profile?": "Suspend the profile?"
|
"Suspend the profile?": "Suspend the profile?",
|
||||||
|
"Go to booking": "Go to booking",
|
||||||
|
"External registration": "External registration",
|
||||||
|
"I want to manage the registration with an external provider": "I want to manage the registration with an external provider",
|
||||||
|
"External provider URL": "External provider URL",
|
||||||
|
"Members will also access private sections like discussions, resources and restricted posts.": "Members will also access private sections like discussions, resources and restricted posts."
|
||||||
}
|
}
|
|
@ -1601,5 +1601,10 @@
|
||||||
"{username} was invited to {group}": "{username} a été invité à {group}",
|
"{username} was invited to {group}": "{username} a été invité à {group}",
|
||||||
"{user}'s follow request was accepted": "La demande de suivi de {user} a été acceptée",
|
"{user}'s follow request was accepted": "La demande de suivi de {user} a été acceptée",
|
||||||
"{user}'s follow request was rejected": "La demande de suivi de {user} a été rejetée",
|
"{user}'s follow request was rejected": "La demande de suivi de {user} a été rejetée",
|
||||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
|
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
|
||||||
|
"Go to booking": "Aller à la réservation",
|
||||||
|
"External registration": "Inscription externe",
|
||||||
|
"I want to manage the registration with an external provider": "Je souhaite gérer l'enregistrement auprès d'un fournisseur externe",
|
||||||
|
"External provider URL": "URL du fournisseur externe",
|
||||||
|
"Members will also access private sections like discussions, resources and restricted posts.": "Les membres auront également accès aux section privées comme les discussions, les ressources et les billets restreints."
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ import type { IResource } from "../resource";
|
||||||
import type { IEvent } from "../event.model";
|
import type { IEvent } from "../event.model";
|
||||||
import type { IDiscussion } from "../discussions";
|
import type { IDiscussion } from "../discussions";
|
||||||
import type { IPost } from "../post.model";
|
import type { IPost } from "../post.model";
|
||||||
import type { IAddress } from "../address.model";
|
import { Address, type IAddress } from "../address.model";
|
||||||
import { Address } from "../address.model";
|
|
||||||
import { ActorType, GroupVisibility, Openness } from "../enums";
|
import { ActorType, GroupVisibility, Openness } from "../enums";
|
||||||
import type { IMember } from "./member.model";
|
import type { IMember } from "./member.model";
|
||||||
import type { ITodoList } from "../todolist";
|
import type { ITodoList } from "../todolist";
|
||||||
|
@ -53,11 +52,11 @@ export class Group extends Actor implements IGroup {
|
||||||
visibility: GroupVisibility = GroupVisibility.PUBLIC;
|
visibility: GroupVisibility = GroupVisibility.PUBLIC;
|
||||||
activity: Paginate<IActivity> = { elements: [], total: 0 };
|
activity: Paginate<IActivity> = { elements: [], total: 0 };
|
||||||
|
|
||||||
openness: Openness = Openness.INVITE_ONLY;
|
openness: Openness = Openness.MODERATED;
|
||||||
|
|
||||||
physicalAddress: IAddress = new Address();
|
physicalAddress: IAddress = new Address();
|
||||||
|
|
||||||
manuallyApprovesFollowers = true;
|
manuallyApprovesFollowers = false;
|
||||||
|
|
||||||
patch(hash: IGroup | Record<string, unknown>): void {
|
patch(hash: IGroup | Record<string, unknown>): void {
|
||||||
Object.assign(this, hash);
|
Object.assign(this, hash);
|
||||||
|
|
|
@ -96,6 +96,7 @@ export interface IConfig {
|
||||||
timezones: string[];
|
timezones: string[];
|
||||||
features: {
|
features: {
|
||||||
eventCreation: boolean;
|
eventCreation: boolean;
|
||||||
|
eventExternal: boolean;
|
||||||
groups: boolean;
|
groups: boolean;
|
||||||
antispam: boolean;
|
antispam: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,6 +64,7 @@ export enum EventJoinOptions {
|
||||||
FREE = "FREE",
|
FREE = "FREE",
|
||||||
RESTRICTED = "RESTRICTED",
|
RESTRICTED = "RESTRICTED",
|
||||||
INVITE = "INVITE",
|
INVITE = "INVITE",
|
||||||
|
EXTERNAL = "EXTERNAL",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EventVisibilityJoinOptions {
|
export enum EventVisibilityJoinOptions {
|
||||||
|
|
|
@ -43,6 +43,7 @@ interface IEventEditJSON {
|
||||||
status: EventStatus;
|
status: EventStatus;
|
||||||
visibility: EventVisibility;
|
visibility: EventVisibility;
|
||||||
joinOptions: EventJoinOptions;
|
joinOptions: EventJoinOptions;
|
||||||
|
externalParticipationUrl: string | null;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
picture?: IMedia | { mediaId: string } | null;
|
picture?: IMedia | { mediaId: string } | null;
|
||||||
attributedToId: string | null;
|
attributedToId: string | null;
|
||||||
|
@ -72,6 +73,7 @@ export interface IEvent {
|
||||||
status: EventStatus;
|
status: EventStatus;
|
||||||
visibility: EventVisibility;
|
visibility: EventVisibility;
|
||||||
joinOptions: EventJoinOptions;
|
joinOptions: EventJoinOptions;
|
||||||
|
externalParticipationUrl: string | null;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
|
|
||||||
picture: IMedia | null;
|
picture: IMedia | null;
|
||||||
|
@ -132,6 +134,8 @@ export class EventModel implements IEvent {
|
||||||
|
|
||||||
joinOptions = EventJoinOptions.FREE;
|
joinOptions = EventJoinOptions.FREE;
|
||||||
|
|
||||||
|
externalParticipationUrl: string | null = null;
|
||||||
|
|
||||||
status = EventStatus.CONFIRMED;
|
status = EventStatus.CONFIRMED;
|
||||||
|
|
||||||
draft = true;
|
draft = true;
|
||||||
|
@ -197,6 +201,7 @@ export class EventModel implements IEvent {
|
||||||
this.status = hash.status;
|
this.status = hash.status;
|
||||||
this.visibility = hash.visibility;
|
this.visibility = hash.visibility;
|
||||||
this.joinOptions = hash.joinOptions;
|
this.joinOptions = hash.joinOptions;
|
||||||
|
this.externalParticipationUrl = hash.externalParticipationUrl;
|
||||||
this.draft = hash.draft;
|
this.draft = hash.draft;
|
||||||
|
|
||||||
this.picture = hash.picture;
|
this.picture = hash.picture;
|
||||||
|
@ -248,6 +253,7 @@ export function toEditJSON(event: IEditableEvent): IEventEditJSON {
|
||||||
category: event.category,
|
category: event.category,
|
||||||
visibility: event.visibility,
|
visibility: event.visibility,
|
||||||
joinOptions: event.joinOptions,
|
joinOptions: event.joinOptions,
|
||||||
|
externalParticipationUrl: event.externalParticipationUrl,
|
||||||
draft: event.draft,
|
draft: event.draft,
|
||||||
tags: event.tags.map((t) => t.title),
|
tags: event.tags.map((t) => t.title),
|
||||||
onlineAddress: event.onlineAddress,
|
onlineAddress: event.onlineAddress,
|
||||||
|
|
|
@ -120,7 +120,7 @@ import {
|
||||||
} from "vue-use-route-query";
|
} from "vue-use-route-query";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@vueuse/head";
|
import { useHead } from "@vueuse/head";
|
||||||
import { computed, ref } from "vue";
|
import { computed } from "vue";
|
||||||
import { Paginate } from "@/types/paginate";
|
import { Paginate } from "@/types/paginate";
|
||||||
import { IGroup } from "@/types/actor";
|
import { IGroup } from "@/types/actor";
|
||||||
import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
||||||
|
@ -129,11 +129,11 @@ const PROFILES_PER_PAGE = 10;
|
||||||
|
|
||||||
const { restrictions } = useRestrictions();
|
const { restrictions } = useRestrictions();
|
||||||
|
|
||||||
const preferredUsername = ref("");
|
const preferredUsername = useRouteQuery("preferredUsername", "");
|
||||||
const name = ref("");
|
const name = useRouteQuery("name", "");
|
||||||
const domain = ref("");
|
const domain = useRouteQuery("domain", "");
|
||||||
|
|
||||||
const local = useRouteQuery("local", true, booleanTransformer);
|
const local = useRouteQuery("local", domain.value === "", booleanTransformer);
|
||||||
const suspended = useRouteQuery("suspended", false, booleanTransformer);
|
const suspended = useRouteQuery("suspended", false, booleanTransformer);
|
||||||
const page = useRouteQuery("page", 1, integerTransformer);
|
const page = useRouteQuery("page", 1, integerTransformer);
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ const preferredUsername = useRouteQuery("preferredUsername", "");
|
||||||
const name = useRouteQuery("name", "");
|
const name = useRouteQuery("name", "");
|
||||||
const domain = useRouteQuery("domain", "");
|
const domain = useRouteQuery("domain", "");
|
||||||
|
|
||||||
const local = useRouteQuery("local", true, booleanTransformer);
|
const local = useRouteQuery("local", domain.value === "", booleanTransformer);
|
||||||
const suspended = useRouteQuery("suspended", false, booleanTransformer);
|
const suspended = useRouteQuery("suspended", false, booleanTransformer);
|
||||||
const page = useRouteQuery("page", 1, integerTransformer);
|
const page = useRouteQuery("page", 1, integerTransformer);
|
||||||
|
|
||||||
|
|
|
@ -247,7 +247,28 @@
|
||||||
</div>-->
|
</div>-->
|
||||||
|
|
||||||
<o-field
|
<o-field
|
||||||
v-if="anonymousParticipationConfig?.allowed"
|
:label="t('External registration')"
|
||||||
|
v-if="features?.eventExternal"
|
||||||
|
>
|
||||||
|
<o-switch v-model="externalParticipation">
|
||||||
|
{{
|
||||||
|
t("I want to manage the registration with an external provider")
|
||||||
|
}}
|
||||||
|
</o-switch>
|
||||||
|
</o-field>
|
||||||
|
|
||||||
|
<o-field v-if="externalParticipation" :label="t('URL')">
|
||||||
|
<o-input
|
||||||
|
icon="link"
|
||||||
|
type="url"
|
||||||
|
v-model="event.externalParticipationUrl"
|
||||||
|
:placeholder="t('External provider URL')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</o-field>
|
||||||
|
|
||||||
|
<o-field
|
||||||
|
v-if="anonymousParticipationConfig?.allowed && !externalParticipation"
|
||||||
:label="t('Anonymous participations')"
|
:label="t('Anonymous participations')"
|
||||||
>
|
>
|
||||||
<o-switch v-model="eventOptions.anonymousParticipation">
|
<o-switch v-model="eventOptions.anonymousParticipation">
|
||||||
|
@ -268,19 +289,22 @@
|
||||||
</o-switch>
|
</o-switch>
|
||||||
</o-field>
|
</o-field>
|
||||||
|
|
||||||
<o-field :label="t('Participation approval')">
|
<o-field
|
||||||
|
:label="t('Participation approval')"
|
||||||
|
v-show="!externalParticipation"
|
||||||
|
>
|
||||||
<o-switch v-model="needsApproval">{{
|
<o-switch v-model="needsApproval">{{
|
||||||
t("I want to approve every participation request")
|
t("I want to approve every participation request")
|
||||||
}}</o-switch>
|
}}</o-switch>
|
||||||
</o-field>
|
</o-field>
|
||||||
|
|
||||||
<o-field :label="t('Number of places')">
|
<o-field :label="t('Number of places')" v-show="!externalParticipation">
|
||||||
<o-switch v-model="limitedPlaces">{{
|
<o-switch v-model="limitedPlaces">{{
|
||||||
t("Limited number of places")
|
t("Limited number of places")
|
||||||
}}</o-switch>
|
}}</o-switch>
|
||||||
</o-field>
|
</o-field>
|
||||||
|
|
||||||
<div class="" v-if="limitedPlaces">
|
<div class="" v-if="limitedPlaces && !externalParticipation">
|
||||||
<o-field :label="t('Number of places')" label-for="number-of-places">
|
<o-field :label="t('Number of places')" label-for="number-of-places">
|
||||||
<o-input
|
<o-input
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -1308,6 +1332,19 @@ const orderedCategories = computed(() => {
|
||||||
if (!eventCategories.value) return undefined;
|
if (!eventCategories.value) return undefined;
|
||||||
return sortBy(eventCategories.value, ["label"]);
|
return sortBy(eventCategories.value, ["label"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const externalParticipation = computed({
|
||||||
|
get() {
|
||||||
|
return event.value?.joinOptions === EventJoinOptions.EXTERNAL;
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
if (newValue === true) {
|
||||||
|
event.value.joinOptions = EventJoinOptions.EXTERNAL;
|
||||||
|
} else {
|
||||||
|
event.value.joinOptions = EventJoinOptions.FREE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -69,11 +69,25 @@
|
||||||
:message="summaryErrors[0]"
|
:message="summaryErrors[0]"
|
||||||
:type="summaryErrors[1]"
|
:type="summaryErrors[1]"
|
||||||
>
|
>
|
||||||
<o-input v-model="group.summary" type="textarea" id="group-summary" />
|
<editor
|
||||||
|
v-if="currentActor"
|
||||||
|
id="group-summary"
|
||||||
|
mode="basic"
|
||||||
|
class="mb-3"
|
||||||
|
v-model="group.summary"
|
||||||
|
:maxSize="500"
|
||||||
|
:aria-label="$t('Group description body')"
|
||||||
|
:current-actor="currentActor"
|
||||||
|
/>
|
||||||
</o-field>
|
</o-field>
|
||||||
|
|
||||||
<div>
|
<full-address-auto-complete
|
||||||
<b>{{ t("Avatar") }}</b>
|
:label="$t('Group address')"
|
||||||
|
v-model="group.physicalAddress"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<b class="field-label">{{ t("Avatar") }}</b>
|
||||||
<picture-upload
|
<picture-upload
|
||||||
:textFallback="t('Avatar')"
|
:textFallback="t('Avatar')"
|
||||||
v-model="avatarFile"
|
v-model="avatarFile"
|
||||||
|
@ -81,8 +95,8 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="field">
|
||||||
<b>{{ t("Banner") }}</b>
|
<b class="field-label">{{ t("Banner") }}</b>
|
||||||
<picture-upload
|
<picture-upload
|
||||||
:textFallback="t('Banner')"
|
:textFallback="t('Banner')"
|
||||||
v-model="bannerFile"
|
v-model="bannerFile"
|
||||||
|
@ -90,7 +104,101 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<o-button variant="primary" native-type="submit">
|
<fieldset>
|
||||||
|
<legend class="field-label !mb-0 mt-2">
|
||||||
|
{{ t("Group visibility") }}
|
||||||
|
</legend>
|
||||||
|
<o-radio
|
||||||
|
v-model="group.visibility"
|
||||||
|
name="groupVisibility"
|
||||||
|
:native-value="GroupVisibility.PUBLIC"
|
||||||
|
>
|
||||||
|
{{ $t("Visible everywhere on the web") }}<br />
|
||||||
|
<small>{{
|
||||||
|
$t(
|
||||||
|
"The group will be publicly listed in search results and may be suggested in the explore section. Only public informations will be shown on it's page."
|
||||||
|
)
|
||||||
|
}}</small>
|
||||||
|
</o-radio>
|
||||||
|
<o-radio
|
||||||
|
v-model="group.visibility"
|
||||||
|
name="groupVisibility"
|
||||||
|
:native-value="GroupVisibility.UNLISTED"
|
||||||
|
>{{ $t("Only accessible through link") }}<br />
|
||||||
|
<small>{{
|
||||||
|
$t(
|
||||||
|
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines."
|
||||||
|
)
|
||||||
|
}}</small>
|
||||||
|
</o-radio>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend class="mt-2">
|
||||||
|
<span class="field-label !mb-0">{{ t("New members") }} </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"Members will also access private sections like discussions, resources and restricted posts."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
<o-field>
|
||||||
|
<o-radio
|
||||||
|
v-model="group.openness"
|
||||||
|
name="groupOpenness"
|
||||||
|
:native-value="Openness.OPEN"
|
||||||
|
>
|
||||||
|
{{ $t("Anyone can join freely") }}<br />
|
||||||
|
<small>{{
|
||||||
|
$t(
|
||||||
|
"Anyone wanting to be a member from your group will be able to from your group page."
|
||||||
|
)
|
||||||
|
}}</small>
|
||||||
|
</o-radio>
|
||||||
|
</o-field>
|
||||||
|
<o-field>
|
||||||
|
<o-radio
|
||||||
|
v-model="group.openness"
|
||||||
|
name="groupOpenness"
|
||||||
|
:native-value="Openness.MODERATED"
|
||||||
|
>{{ $t("Moderate new members") }}<br />
|
||||||
|
<small>{{
|
||||||
|
$t(
|
||||||
|
"Anyone can request being a member, but an administrator needs to approve the membership."
|
||||||
|
)
|
||||||
|
}}</small>
|
||||||
|
</o-radio>
|
||||||
|
</o-field>
|
||||||
|
<o-field>
|
||||||
|
<o-radio
|
||||||
|
v-model="group.openness"
|
||||||
|
name="groupOpenness"
|
||||||
|
:native-value="Openness.INVITE_ONLY"
|
||||||
|
>{{ $t("Manually invite new members") }}<br />
|
||||||
|
<small>{{
|
||||||
|
$t(
|
||||||
|
"The only way for your group to get new members is if an admininistrator invites them."
|
||||||
|
)
|
||||||
|
}}</small>
|
||||||
|
</o-radio>
|
||||||
|
</o-field>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend class="mt-2">
|
||||||
|
<span class="field-label !mb-0">
|
||||||
|
{{ t("Followers") }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ t("Followers will receive new public events and posts.") }}
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
<o-checkbox v-model="group.manuallyApprovesFollowers">
|
||||||
|
{{ t("Manually approve new followers") }}
|
||||||
|
</o-checkbox>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<o-button variant="primary" native-type="submit" class="mt-3">
|
||||||
{{ t("Create my group") }}
|
{{ t("Create my group") }}
|
||||||
</o-button>
|
</o-button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -105,7 +213,14 @@ import PictureUpload from "../../components/PictureUpload.vue";
|
||||||
import { ErrorResponse } from "@/types/errors.model";
|
import { ErrorResponse } from "@/types/errors.model";
|
||||||
import { ServerParseError } from "@apollo/client/link/http";
|
import { ServerParseError } from "@apollo/client/link/http";
|
||||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||||
import { computed, inject, reactive, ref, watch } from "vue";
|
import {
|
||||||
|
computed,
|
||||||
|
defineAsyncComponent,
|
||||||
|
inject,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useCreateGroup } from "@/composition/apollo/group";
|
import { useCreateGroup } from "@/composition/apollo/group";
|
||||||
|
@ -116,6 +231,12 @@ import {
|
||||||
} from "@/composition/config";
|
} from "@/composition/config";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { useHead } from "@vueuse/head";
|
import { useHead } from "@vueuse/head";
|
||||||
|
import { Openness, GroupVisibility } from "@/types/enums";
|
||||||
|
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
||||||
|
|
||||||
|
const Editor = defineAsyncComponent(
|
||||||
|
() => import("@/components/TextEditor.vue")
|
||||||
|
);
|
||||||
|
|
||||||
const { currentActor } = useCurrentActorClient();
|
const { currentActor } = useCurrentActorClient();
|
||||||
|
|
||||||
|
@ -156,10 +277,20 @@ const buildVariables = computed(() => {
|
||||||
let avatarObj = {};
|
let avatarObj = {};
|
||||||
let bannerObj = {};
|
let bannerObj = {};
|
||||||
|
|
||||||
|
const cloneGroup = group.value;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
delete cloneGroup.physicalAddress.__typename;
|
||||||
|
delete cloneGroup.physicalAddress.pictureInfo;
|
||||||
|
|
||||||
const groupBasic = {
|
const groupBasic = {
|
||||||
preferredUsername: group.value.preferredUsername,
|
preferredUsername: group.value.preferredUsername,
|
||||||
name: group.value.name,
|
name: group.value.name,
|
||||||
summary: group.value.summary,
|
summary: group.value.summary,
|
||||||
|
physicalAddress: cloneGroup.physicalAddress,
|
||||||
|
visibility: group.value.visibility,
|
||||||
|
openness: group.value.openness,
|
||||||
|
manuallyApprovesFollowers: group.value.manuallyApprovesFollowers,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (avatarFile.value) {
|
if (avatarFile.value) {
|
||||||
|
|
|
@ -78,6 +78,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
join_options: Map.get(object, "joinMode", "free"),
|
join_options: Map.get(object, "joinMode", "free"),
|
||||||
local: is_local?(object["id"]),
|
local: is_local?(object["id"]),
|
||||||
|
external_participation_url: object["externalParticipationUrl"],
|
||||||
options: options,
|
options: options,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(),
|
status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(),
|
||||||
|
@ -129,6 +130,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
"mediaType" => "text/html",
|
"mediaType" => "text/html",
|
||||||
"startTime" => event.begins_on |> shift_tz(event.options.timezone) |> date_to_string(),
|
"startTime" => event.begins_on |> shift_tz(event.options.timezone) |> date_to_string(),
|
||||||
"joinMode" => to_string(event.join_options),
|
"joinMode" => to_string(event.join_options),
|
||||||
|
"externalParticipationUrl" => event.external_participation_url,
|
||||||
"endTime" => event.ends_on |> shift_tz(event.options.timezone) |> date_to_string(),
|
"endTime" => event.ends_on |> shift_tz(event.options.timezone) |> date_to_string(),
|
||||||
"tag" => event.tags |> build_tags(),
|
"tag" => event.tags |> build_tags(),
|
||||||
"maximumAttendeeCapacity" => event.options.maximum_attendee_capacity,
|
"maximumAttendeeCapacity" => event.options.maximum_attendee_capacity,
|
||||||
|
|
|
@ -145,6 +145,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||||
features: %{
|
features: %{
|
||||||
groups: Config.instance_group_feature_enabled?(),
|
groups: Config.instance_group_feature_enabled?(),
|
||||||
event_creation: Config.instance_event_creation_enabled?(),
|
event_creation: Config.instance_event_creation_enabled?(),
|
||||||
|
event_external: Config.instance_event_external_enabled?(),
|
||||||
antispam: AntiSpam.service().ready?()
|
antispam: AntiSpam.service().ready?()
|
||||||
},
|
},
|
||||||
restrictions: %{
|
restrictions: %{
|
||||||
|
|
|
@ -254,6 +254,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
|
|
||||||
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
||||||
{:can_create_event, true} <- can_create_event(args),
|
{:can_create_event, true} <- can_create_event(args),
|
||||||
|
{:event_external, true} <- edit_event_external_checker(args),
|
||||||
{:organizer_group_member, true} <-
|
{:organizer_group_member, true} <-
|
||||||
{:organizer_group_member, is_organizer_group_member?(args)},
|
{:organizer_group_member, is_organizer_group_member?(args)},
|
||||||
args_with_organizer <-
|
args_with_organizer <-
|
||||||
|
@ -281,6 +282,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
"Only groups can create events"
|
"Only groups can create events"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{:event_external, false} ->
|
||||||
|
{:error,
|
||||||
|
dgettext(
|
||||||
|
"errors",
|
||||||
|
"Providing external registration is not allowed"
|
||||||
|
)}
|
||||||
|
|
||||||
{:organizer_group_member, false} ->
|
{:organizer_group_member, false} ->
|
||||||
{:error,
|
{:error,
|
||||||
dgettext(
|
dgettext(
|
||||||
|
@ -322,6 +330,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec edit_event_external_checker(map()) :: {:event_external, boolean()}
|
||||||
|
defp edit_event_external_checker(args) do
|
||||||
|
if Config.instance_event_external_enabled?() do
|
||||||
|
{:event_external, true}
|
||||||
|
else
|
||||||
|
{:event_external,
|
||||||
|
Map.get(args, :join_options) != :external and
|
||||||
|
is_nil(Map.get(args, :external_participation_url))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Update an event
|
Update an event
|
||||||
"""
|
"""
|
||||||
|
@ -340,6 +359,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
args <- extract_timezone(args, user.id),
|
args <- extract_timezone(args, user.id),
|
||||||
{:event_can_be_managed, true} <-
|
{:event_can_be_managed, true} <-
|
||||||
{:event_can_be_managed, can_event_be_updated_by?(event, actor)},
|
{:event_can_be_managed, can_event_be_updated_by?(event, actor)},
|
||||||
|
{:event_external, true} <- edit_event_external_checker(args),
|
||||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||||
API.Events.update_event(args, event) do
|
API.Events.update_event(args, event) do
|
||||||
{:ok, event}
|
{:ok, event}
|
||||||
|
@ -351,6 +371,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
"This profile doesn't have permission to update an event on behalf of this group"
|
"This profile doesn't have permission to update an event on behalf of this group"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{:event_external, false} ->
|
||||||
|
{:error,
|
||||||
|
dgettext(
|
||||||
|
"errors",
|
||||||
|
"Providing external registration is not allowed"
|
||||||
|
)}
|
||||||
|
|
||||||
{:error, :event_not_found} ->
|
{:error, :event_not_found} ->
|
||||||
{:error, dgettext("errors", "Event not found")}
|
{:error, dgettext("errors", "Event not found")}
|
||||||
|
|
||||||
|
|
|
@ -305,6 +305,10 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
||||||
description: "Whether the group can be join freely, with approval or is invite-only."
|
description: "Whether the group can be join freely, with approval or is invite-only."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
arg(:manually_approves_followers, :boolean,
|
||||||
|
description: "Whether this group approves new followers manually"
|
||||||
|
)
|
||||||
|
|
||||||
arg(:avatar, :media_input,
|
arg(:avatar, :media_input,
|
||||||
description:
|
description:
|
||||||
"The avatar for the group, either as an object or directly the ID of an existing media"
|
"The avatar for the group, either as an object or directly the ID of an existing media"
|
||||||
|
|
|
@ -314,6 +314,10 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||||
description: "Whether event creation is allowed on this instance"
|
description: "Whether event creation is allowed on this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
field(:event_external, :boolean,
|
||||||
|
description: "Whether redirecting to external providers is authorized in event edition"
|
||||||
|
)
|
||||||
|
|
||||||
field(:antispam, :boolean, description: "Whether anti-spam is activated on this instance")
|
field(:antispam, :boolean, description: "Whether anti-spam is activated on this instance")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||||
field(:status, :event_status, description: "Status of the event")
|
field(:status, :event_status, description: "Status of the event")
|
||||||
field(:visibility, :event_visibility, description: "The event's visibility")
|
field(:visibility, :event_visibility, description: "The event's visibility")
|
||||||
field(:join_options, :event_join_options, description: "The event's visibility")
|
field(:join_options, :event_join_options, description: "The event's visibility")
|
||||||
|
field(:external_participation_url, :string, description: "External URL for participation")
|
||||||
|
|
||||||
field(:picture, :media,
|
field(:picture, :media,
|
||||||
description: "The event's picture",
|
description: "The event's picture",
|
||||||
|
@ -130,6 +131,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||||
value(:free, description: "Anyone can join and is automatically accepted")
|
value(:free, description: "Anyone can join and is automatically accepted")
|
||||||
value(:restricted, description: "Manual acceptation")
|
value(:restricted, description: "Manual acceptation")
|
||||||
value(:invite, description: "Participants must be invited")
|
value(:invite, description: "Participants must be invited")
|
||||||
|
value(:external, description: "External registration")
|
||||||
end
|
end
|
||||||
|
|
||||||
@desc "The list of possible options for the event's status"
|
@desc "The list of possible options for the event's status"
|
||||||
|
@ -398,6 +400,8 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||||
description: "The event's options to join"
|
description: "The event's options to join"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
arg(:external_participation_url, :string, description: "External URL for participation")
|
||||||
|
|
||||||
arg(:tags, list_of(:string),
|
arg(:tags, list_of(:string),
|
||||||
default_value: [],
|
default_value: [],
|
||||||
description: "The list of tags associated to the event"
|
description: "The list of tags associated to the event"
|
||||||
|
@ -469,6 +473,8 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||||
description: "The event's options to join"
|
description: "The event's options to join"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
arg(:external_participation_url, :string, description: "External URL for participation")
|
||||||
|
|
||||||
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
||||||
|
|
||||||
arg(:picture, :media_input,
|
arg(:picture, :media_input,
|
||||||
|
|
|
@ -146,7 +146,8 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
:domain,
|
:domain,
|
||||||
:summary,
|
:summary,
|
||||||
:visibility,
|
:visibility,
|
||||||
:openness
|
:openness,
|
||||||
|
:manually_approves_followers
|
||||||
]
|
]
|
||||||
@group_creation_attrs @group_creation_required_attrs ++ @group_creation_optional_attrs
|
@group_creation_attrs @group_creation_required_attrs ++ @group_creation_optional_attrs
|
||||||
|
|
||||||
|
|
|
@ -356,6 +356,10 @@ defmodule Mobilizon.Config do
|
||||||
def instance_event_creation_enabled?,
|
def instance_event_creation_enabled?,
|
||||||
do: :mobilizon |> Application.get_env(:events) |> Keyword.get(:creation)
|
do: :mobilizon |> Application.get_env(:events) |> Keyword.get(:creation)
|
||||||
|
|
||||||
|
@spec instance_event_external_enabled? :: boolean
|
||||||
|
def instance_event_external_enabled?,
|
||||||
|
do: :mobilizon |> Application.get_env(:events) |> Keyword.get(:external)
|
||||||
|
|
||||||
@spec instance_export_formats :: %{event_participants: list(String.t())}
|
@spec instance_export_formats :: %{event_participants: list(String.t())}
|
||||||
def instance_export_formats do
|
def instance_export_formats do
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -47,6 +47,7 @@ defmodule Mobilizon.Events.Event do
|
||||||
draft: boolean,
|
draft: boolean,
|
||||||
visibility: atom(),
|
visibility: atom(),
|
||||||
join_options: atom(),
|
join_options: atom(),
|
||||||
|
external_participation_url: String.t(),
|
||||||
publish_at: DateTime.t() | nil,
|
publish_at: DateTime.t() | nil,
|
||||||
uuid: Ecto.UUID.t(),
|
uuid: Ecto.UUID.t(),
|
||||||
online_address: String.t() | nil,
|
online_address: String.t() | nil,
|
||||||
|
@ -81,6 +82,7 @@ defmodule Mobilizon.Events.Event do
|
||||||
:local,
|
:local,
|
||||||
:visibility,
|
:visibility,
|
||||||
:join_options,
|
:join_options,
|
||||||
|
:external_participation_url,
|
||||||
:publish_at,
|
:publish_at,
|
||||||
:online_address,
|
:online_address,
|
||||||
:phone_address,
|
:phone_address,
|
||||||
|
@ -105,6 +107,7 @@ defmodule Mobilizon.Events.Event do
|
||||||
field(:draft, :boolean, default: false)
|
field(:draft, :boolean, default: false)
|
||||||
field(:visibility, EventVisibility, default: :public)
|
field(:visibility, EventVisibility, default: :public)
|
||||||
field(:join_options, JoinOptions, default: :free)
|
field(:join_options, JoinOptions, default: :free)
|
||||||
|
field(:external_participation_url, :string)
|
||||||
field(:publish_at, :utc_datetime)
|
field(:publish_at, :utc_datetime)
|
||||||
field(:uuid, Ecto.UUID, default: Ecto.UUID.generate())
|
field(:uuid, Ecto.UUID, default: Ecto.UUID.generate())
|
||||||
field(:online_address, :string)
|
field(:online_address, :string)
|
||||||
|
|
|
@ -46,7 +46,8 @@ defmodule Mobilizon.Events do
|
||||||
defenum(JoinOptions, :join_options, [
|
defenum(JoinOptions, :join_options, [
|
||||||
:free,
|
:free,
|
||||||
:restricted,
|
:restricted,
|
||||||
:invite
|
:invite,
|
||||||
|
:external
|
||||||
])
|
])
|
||||||
|
|
||||||
defenum(EventStatus, :event_status, [
|
defenum(EventStatus, :event_status, [
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -1,7 +1,7 @@
|
||||||
defmodule Mobilizon.Mixfile do
|
defmodule Mobilizon.Mixfile do
|
||||||
use Mix.Project
|
use Mix.Project
|
||||||
|
|
||||||
@version "3.2.0-beta.1"
|
@version "3.2.0-beta.2"
|
||||||
|
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.AddExternalUrlForEvents do
|
||||||
|
use Ecto.Migration
|
||||||
|
alias Mobilizon.Events.JoinOptions
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:events) do
|
||||||
|
add(:external_participation_url, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
reset_join_options_enum()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:events) do
|
||||||
|
remove(:external_participation_url, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
reset_join_options_enum()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reset_join_options_enum do
|
||||||
|
execute("ALTER TABLE events ALTER COLUMN join_options TYPE VARCHAR USING join_options::text")
|
||||||
|
execute("ALTER TABLE events ALTER COLUMN join_options DROP DEFAULT")
|
||||||
|
JoinOptions.drop_type()
|
||||||
|
JoinOptions.create_type()
|
||||||
|
|
||||||
|
execute(
|
||||||
|
"ALTER TABLE events ALTER COLUMN join_options TYPE join_options USING join_options::join_options"
|
||||||
|
)
|
||||||
|
|
||||||
|
execute("ALTER TABLE events ALTER COLUMN join_options SET DEFAULT 'free'::join_options")
|
||||||
|
end
|
||||||
|
end
|
|
@ -1551,6 +1551,9 @@ type RootMutationType {
|
||||||
"The event's options to join"
|
"The event's options to join"
|
||||||
joinOptions: EventJoinOptions
|
joinOptions: EventJoinOptions
|
||||||
|
|
||||||
|
"External URL for participation"
|
||||||
|
externalParticipationUrl: String
|
||||||
|
|
||||||
"The list of tags associated to the event"
|
"The list of tags associated to the event"
|
||||||
tags: [String]
|
tags: [String]
|
||||||
|
|
||||||
|
@ -1620,6 +1623,9 @@ type RootMutationType {
|
||||||
"The event's options to join"
|
"The event's options to join"
|
||||||
joinOptions: EventJoinOptions
|
joinOptions: EventJoinOptions
|
||||||
|
|
||||||
|
"External URL for participation"
|
||||||
|
externalParticipationUrl: String
|
||||||
|
|
||||||
"The list of tags associated to the event"
|
"The list of tags associated to the event"
|
||||||
tags: [String]
|
tags: [String]
|
||||||
|
|
||||||
|
@ -2956,6 +2962,9 @@ type Event implements ActivityObject & Interactable & ActionLogObject {
|
||||||
"The event's visibility"
|
"The event's visibility"
|
||||||
joinOptions: EventJoinOptions
|
joinOptions: EventJoinOptions
|
||||||
|
|
||||||
|
"External URL for participation"
|
||||||
|
externalParticipationUrl: String
|
||||||
|
|
||||||
"The event's picture"
|
"The event's picture"
|
||||||
picture: Media
|
picture: Media
|
||||||
|
|
||||||
|
@ -3144,6 +3153,9 @@ enum EventJoinOptions {
|
||||||
|
|
||||||
"Participants must be invited"
|
"Participants must be invited"
|
||||||
INVITE
|
INVITE
|
||||||
|
|
||||||
|
"External registration"
|
||||||
|
EXTERNAL
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceFeeds {
|
type InstanceFeeds {
|
||||||
|
|
Loading…
Reference in a new issue