-
+
+
+
+
+
@@ -36,6 +47,13 @@ div.eventMetadataBlock {
&.padding-left {
padding: 0 20px;
+
+ a {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
}
}
}
diff --git a/js/src/components/Event/EventMetadataItem.vue b/js/src/components/Event/EventMetadataItem.vue
new file mode 100644
index 000000000..9a950b860
--- /dev/null
+++ b/js/src/components/Event/EventMetadataItem.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
diff --git a/js/src/components/Event/EventMetadataList.vue b/js/src/components/Event/EventMetadataList.vue
new file mode 100644
index 000000000..b5779def6
--- /dev/null
+++ b/js/src/components/Event/EventMetadataList.vue
@@ -0,0 +1,206 @@
+
+
+
+
+ addElement(option)"
+ >
+
+
+
+ {{
+ $t("No results for {search}", { search })
+ }}
+
+
+
+ {{ $t("Add new…") }}
+
+
+
+
+
+
+
+
+
diff --git a/js/src/components/Event/EventMetadataSidebar.vue b/js/src/components/Event/EventMetadataSidebar.vue
new file mode 100644
index 000000000..af567ee76
--- /dev/null
+++ b/js/src/components/Event/EventMetadataSidebar.vue
@@ -0,0 +1,450 @@
+
+
+
+
+
diff --git a/js/src/components/Event/Integrations/PeerTube.vue b/js/src/components/Event/Integrations/PeerTube.vue
new file mode 100644
index 000000000..db4ccaed2
--- /dev/null
+++ b/js/src/components/Event/Integrations/PeerTube.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/js/src/components/Event/Integrations/Twitch.vue b/js/src/components/Event/Integrations/Twitch.vue
new file mode 100644
index 000000000..a89f740c4
--- /dev/null
+++ b/js/src/components/Event/Integrations/Twitch.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/js/src/components/Event/Integrations/YouTube.vue b/js/src/components/Event/Integrations/YouTube.vue
new file mode 100644
index 000000000..2dc188691
--- /dev/null
+++ b/js/src/components/Event/Integrations/YouTube.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts
index f563e8a97..600ea4db2 100644
--- a/js/src/graphql/event.ts
+++ b/js/src/graphql/event.ts
@@ -171,6 +171,12 @@ const FULL_EVENT_FRAGMENT = gql`
options {
...EventOptions
}
+ metadata {
+ key
+ title
+ value
+ type
+ }
}
${ADDRESS_FRAGMENT}
${TAG_FRAGMENT}
@@ -326,6 +332,7 @@ export const EDIT_EVENT = gql`
$physicalAddress: AddressInput
$options: EventOptionsInput
$contacts: [Contact]
+ $metadata: EventMetadataInput
) {
updateEvent(
eventId: $id
@@ -347,6 +354,7 @@ export const EDIT_EVENT = gql`
physicalAddress: $physicalAddress
options: $options
contacts: $contacts
+ metadata: $metadata
) {
...FullEvent
}
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 55450901f..3d054a646 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -1072,5 +1072,55 @@
"+ Create a post": "+ Create a post",
"Edited {relative_time} ago": "Edited {relative_time} ago",
"Members-only post": "Members-only post",
- "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator."
+ "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.",
+ "Find or add an element": "Find or add an element",
+ "e.g. Accessibility, Twitch, PeerTube": "e.g. Accessibility, Twitch, PeerTube",
+ "Add new…": "Add new…",
+ "No results for {search}": "No results for {search}",
+ "Wheelchair accessibility": "Wheelchair accessibility",
+ "Whether the event is accessible with a wheelchair": "Whether the event is accessible with a wheelchair",
+ "Not accessible with a wheelchair": "Not accessible with a wheelchair",
+ "Partially accessible with a wheelchair": "Partially accessible with a wheelchair",
+ "Fully accessible with a wheelchair": "Fully accessible with a wheelchair",
+ "YouTube replay": "YouTube replay",
+ "The URL where the event live can be watched again after it has ended": "The URL where the event live can be watched again after it has ended",
+ "Twitch replay": "Twitch replay",
+ "PeerTube replay": "PeerTube replay",
+ "PeerTube live": "PeerTube live",
+ "The URL where the event can be watched live": "The URL where the event can be watched live",
+ "Twitch live": "Twitch live",
+ "YouTube live": "YouTube live",
+ "Event metadata": "Event metadata",
+ "Framadate poll": "Framadate poll",
+ "The URL of a poll where the choice for the event date is happening": "The URL of a poll where the choice for the event date is happening",
+ "View account on {hostname} (in a new window)": "View account on {hostname} (in a new window)",
+ "Twitter account": "Twitter account",
+ "A twitter account handle to follow for event updates": "A twitter account handle to follow for event updates",
+ "Fediverse account": "Fediverse account",
+ "A fediverse account URL to follow for event updates": "A fediverse account URL to follow for event updates",
+ "Element title": "Element title",
+ "Element value": "Element value",
+ "Subtitles": "Subtitles",
+ "Whether the event live video is subtitled": "Whether the event live video is subtitled",
+ "The event live video contains subtitles": "The event live video contains subtitles",
+ "The event live video does not contain subtitles": "The event live video does not contain subtitles",
+ "Sign Language": "Sign Language",
+ "Whether the event is interpreted in sign language": "Whether the event is interpreted in sign language",
+ "The event has a sign language interpreter": "The event has a sign language interpreter",
+ "The event hasn't got a sign language interpreter": "The event hasn't got a sign language interpreter",
+ "Online ticketing": "Online ticketing",
+ "An URL to an external ticketing platform": "An URL to an external ticketing platform",
+ "Price sheet": "Price sheet",
+ "A link to a page presenting the price options": "A link to a page presenting the price options",
+ "Integrate this event with 3rd-party tools and show metadata for the event.": "Integrate this event with 3rd-party tools and show metadata for the event.",
+ "This URL doesn't seem to be valid": "This URL doesn't seem to be valid",
+ "Schedule": "Schedule",
+ "A link to a page presenting the event schedule": "A link to a page presenting the event schedule",
+ "Accessibility": "Accessibility",
+ "Live": "Live",
+ "Replay": "Replay",
+ "Tools": "Tools",
+ "Social": "Social",
+ "Details": "Details",
+ "Booking": "Booking"
}
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index cc125b8f9..58630776b 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -1163,5 +1163,55 @@
"+ Create a post": "+ Créer un billet",
"Edited {relative_time} ago": "Édité il y a {relative_time}",
"Members-only post": "Billet reservé aux membres",
- "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Ce billet est accessible uniquement aux membres. Vous y avez accès à des fins de modération car vous êtes modérateur⋅ice de l'instance."
+ "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Ce billet est accessible uniquement aux membres. Vous y avez accès à des fins de modération car vous êtes modérateur⋅ice de l'instance.",
+ "Find or add an element": "Trouver ou ajouter un élément",
+ "e.g. Accessibility, Twitch, PeerTube": "par ex. Accessibilité, Framadate, PeerTube",
+ "Add new…": "Ajouter un nouvel élément…",
+ "No results for {search}": "Pas de résultats pour {search}",
+ "Wheelchair accessibility": "Accessibilité aux fauteuils roulants",
+ "Whether the event is accessible with a wheelchair": "Si l'événement est accessible avec un fauteuil roulant",
+ "Not accessible with a wheelchair": "Non accessible avec un fauteuil roulant",
+ "Partially accessible with a wheelchair": "Partiellement accessible avec un fauteuil roulant",
+ "Fully accessible with a wheelchair": "Entièrement accessible avec un fauteuil roulant",
+ "YouTube replay": "Replay sur YouTube",
+ "The URL where the event live can be watched again after it has ended": "L'URL où le direct de l'événement peut être visionné à nouveau une fois terminé",
+ "Twitch replay": "Replay sur Twitch",
+ "PeerTube replay": "Replay sur PeerTube",
+ "PeerTube live": "Direct sur PeerTube",
+ "The URL where the event can be watched live": "L'URL où l'événement peut être visionné en direct",
+ "Twitch live": "Direct sur Twitch",
+ "YouTube live": "Direct sur YouTube",
+ "Event metadata": "Métadonnées de l'événement",
+ "Framadate poll": "Sondage Framadate",
+ "The URL of a poll where the choice for the event date is happening": "L'URL d'un sondage où la date de l'événement doit être choisie",
+ "View account on {hostname} (in a new window)": "Voir le compte sur {hostname} (dans une nouvelle fenêtre)",
+ "Twitter account": "Compte Twitter",
+ "A twitter account handle to follow for event updates": "Un compte sur Twitter à suivre pour les mises à jour de l'événement",
+ "Fediverse account": "Compte fediverse",
+ "A fediverse account URL to follow for event updates": "Un compte sur le fediverse à suivre pour les mises à jour de l'événement",
+ "Element title": "Titre de l'élement",
+ "Element value": "Valeur de l'élement",
+ "Subtitles": "Sous-titres",
+ "Whether the event live video is subtitled": "Si le direct vidéo de l'événement est sous-titré",
+ "The event live video contains subtitles": "Le direct vidéo de l'événement contient des sous-titres",
+ "The event live video does not contain subtitles": "Le direct vidéo de l'événement ne contient pas de sous-titres",
+ "Sign Language": "Langue des signes",
+ "Whether the event is interpreted in sign language": "Si l'événement est interprété en langue des signes",
+ "The event has a sign language interpreter": "L'événement a un interprète en langue des signes",
+ "The event hasn't got a sign language interpreter": "L'événement n'a pas d'interprète en langue des signes",
+ "Online ticketing": "Billetterie en ligne",
+ "An URL to an external ticketing platform": "Une URL vers une plateforme de billetterie externe",
+ "Price sheet": "Feuille des prix",
+ "A link to a page presenting the price options": "Un lien vers une page présentant la tarification",
+ "Integrate this event with 3rd-party tools and show metadata for the event.": "Intégrer cet événement avec des outils tiers et afficher des métadonnées pour l'événement.",
+ "This URL doesn't seem to be valid": "Cette URL ne semble pas être valide",
+ "Schedule": "Programme",
+ "A link to a page presenting the event schedule": "Un lien vers une page présentant le programme de l'événement",
+ "Accessibility": "Accessibilité",
+ "Live": "Direct",
+ "Replay": "Rattrapage",
+ "Tools": "Outils",
+ "Social": "Social",
+ "Details": "Détails",
+ "Booking": "Réservations"
}
diff --git a/js/src/mixins/event.ts b/js/src/mixins/event.ts
index 213db15e3..5f0b28822 100644
--- a/js/src/mixins/event.ts
+++ b/js/src/mixins/event.ts
@@ -193,13 +193,4 @@ export default class EventMixin extends mixins(Vue) {
console.error(error);
}
}
-
- // eslint-disable-next-line class-methods-use-this
- urlToHostname(url: string): string | null {
- try {
- return new URL(url).hostname;
- } catch (e) {
- return null;
- }
- }
}
diff --git a/js/src/services/EventMetadata.ts b/js/src/services/EventMetadata.ts
new file mode 100644
index 000000000..a6b899d85
--- /dev/null
+++ b/js/src/services/EventMetadata.ts
@@ -0,0 +1,212 @@
+import {
+ EventMetadataType,
+ EventMetadataKeyType,
+ EventMetadataCategories,
+} from "@/types/enums";
+import { IEventMetadataDescription } from "@/types/event-metadata";
+import { i18n } from "@/utils/i18n";
+
+export const eventMetaDataList: IEventMetadataDescription[] = [
+ {
+ icon: "wheelchair-accessibility",
+ key: "mz:accessibility:wheelchairAccessible",
+ label: i18n.t("Wheelchair accessibility") as string,
+ description: i18n.t(
+ "Whether the event is accessible with a wheelchair"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.CHOICE,
+ choices: {
+ no: i18n.t("Not accessible with a wheelchair") as string,
+ partially: i18n.t("Partially accessible with a wheelchair") as string,
+ fully: i18n.t("Fully accessible with a wheelchair") as string,
+ },
+ category: EventMetadataCategories.ACCESSIBILITY,
+ },
+ {
+ icon: "subtitles",
+ key: "mz:accessibility:live:subtitle",
+ label: i18n.t("Subtitles") as string,
+ description: i18n.t("Whether the event live video is subtitled") as string,
+ value: "",
+ type: EventMetadataType.BOOLEAN,
+ keyType: EventMetadataKeyType.PLAIN,
+ choices: {
+ true: i18n.t("The event live video contains subtitles") as string,
+ false: i18n.t(
+ "The event live video does not contain subtitles"
+ ) as string,
+ },
+ category: EventMetadataCategories.ACCESSIBILITY,
+ },
+ {
+ icon: "mz:icon:sign_language",
+ key: "mz:accessibility:live:sign_language",
+ label: i18n.t("Sign Language") as string,
+ description: i18n.t(
+ "Whether the event is interpreted in sign language"
+ ) as string,
+ value: "",
+ type: EventMetadataType.BOOLEAN,
+ keyType: EventMetadataKeyType.PLAIN,
+ choices: {
+ true: i18n.t("The event has a sign language interpreter") as string,
+ false: i18n.t(
+ "The event hasn't got a sign language interpreter"
+ ) as string,
+ },
+ category: EventMetadataCategories.ACCESSIBILITY,
+ },
+ {
+ icon: "youtube",
+ key: "mz:replay:youtube:url",
+ label: i18n.t("YouTube replay") as string,
+ description: i18n.t(
+ "The URL where the event live can be watched again after it has ended"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ pattern:
+ /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-_]*)(&(amp;)?[\w?=]*)?/,
+ category: EventMetadataCategories.REPLAY,
+ },
+ // {
+ // icon: "twitch",
+ // key: "mz:replay:twitch:url",
+ // label: i18n.t("Twitch replay") as string,
+ // description: i18n.t(
+ // "The URL where the event live can be watched again after it has ended"
+ // ) as string,
+ // value: "",
+ // type: EventMetadataType.STRING,
+ // },
+ {
+ icon: "mz:icon:peertube",
+ key: "mz:replay:peertube:url",
+ label: i18n.t("PeerTube replay") as string,
+ description: i18n.t(
+ "The URL where the event live can be watched again after it has ended"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ pattern: /^https?:\/\/([^/]+)\/(?:videos\/(?:watch|embed)|w)\/([^/]+)$/,
+ category: EventMetadataCategories.REPLAY,
+ },
+ {
+ icon: "mz:icon:peertube",
+ key: "mz:live:peertube:url",
+ label: i18n.t("PeerTube live") as string,
+ description: i18n.t(
+ "The URL where the event can be watched live"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ pattern: /^https?:\/\/([^/]+)\/(?:videos\/(?:watch|embed)|w)\/([^/]+)$/,
+ category: EventMetadataCategories.LIVE,
+ },
+ {
+ icon: "twitch",
+ key: "mz:live:twitch:url",
+ label: i18n.t("Twitch live") as string,
+ description: i18n.t(
+ "The URL where the event can be watched live"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ placeholder: "https://www.twitch.tv/",
+ pattern: /^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/([a-z0-9_]+)($|\?)/,
+ category: EventMetadataCategories.LIVE,
+ },
+ {
+ icon: "youtube",
+ key: "mz:live:youtube:url",
+ label: i18n.t("YouTube live") as string,
+ description: i18n.t(
+ "The URL where the event can be watched live"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ pattern:
+ /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-_]*)(&(amp;)?[\w?=]*)?/,
+ category: EventMetadataCategories.LIVE,
+ },
+ {
+ icon: "calendar-check",
+ key: "mz:poll:framadate:url",
+ label: i18n.t("Framadate poll") as string,
+ description: i18n.t(
+ "The URL of a poll where the choice for the event date is happening"
+ ) as string,
+ value: "",
+ placeholder: "https://framadate.org/",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ category: EventMetadataCategories.TOOLS,
+ },
+ {
+ icon: "twitter",
+ key: "mz:social:twitter:account",
+ label: i18n.t("Twitter account") as string,
+ description: i18n.t(
+ "A twitter account handle to follow for event updates"
+ ) as string,
+ value: "",
+ placeholder: "@JoinMobilizon",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.HANDLE,
+ category: EventMetadataCategories.SOCIAL,
+ },
+ {
+ icon: "mz:icon:fediverse",
+ key: "mz:social:fediverse:account_url",
+ label: i18n.t("Fediverse account") as string,
+ description: i18n.t(
+ "A fediverse account URL to follow for event updates"
+ ) as string,
+ value: "",
+ placeholder: "https://framapiaf.org/@mobilizon",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ category: EventMetadataCategories.SOCIAL,
+ },
+ {
+ icon: "ticket-confirmation",
+ key: "mz:ticket:external_url",
+ label: i18n.t("Online ticketing") as string,
+ description: i18n.t("An URL to an external ticketing platform") as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ category: EventMetadataCategories.BOOKING,
+ },
+ {
+ icon: "cash",
+ key: "mz:ticket:price_url",
+ label: i18n.t("Price sheet") as string,
+ description: i18n.t(
+ "A link to a page presenting the price options"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ category: EventMetadataCategories.DETAILS,
+ },
+ {
+ icon: "calendar-text",
+ key: "mz:schedule_url",
+ label: i18n.t("Schedule") as string,
+ description: i18n.t(
+ "A link to a page presenting the event schedule"
+ ) as string,
+ value: "",
+ type: EventMetadataType.STRING,
+ keyType: EventMetadataKeyType.URL,
+ category: EventMetadataCategories.DETAILS,
+ },
+];
diff --git a/js/src/types/enums.ts b/js/src/types/enums.ts
index f05e5262c..b5c738b7c 100644
--- a/js/src/types/enums.ts
+++ b/js/src/types/enums.ts
@@ -251,3 +251,27 @@ export enum SortDirection {
ASC = "ASC",
DESC = "DESC",
}
+
+export enum EventMetadataType {
+ STRING = "STRING",
+ INTEGER = "INTEGER",
+ FLOAT = "FLOAT",
+ BOOLEAN = "BOOLEAN",
+}
+
+export enum EventMetadataKeyType {
+ PLAIN = "PLAIN",
+ URL = "URL",
+ CHOICE = "CHOICE",
+ HANDLE = "HANDLE",
+}
+
+export enum EventMetadataCategories {
+ ACCESSIBILITY = "ACCESSIBILITY",
+ LIVE = "LIVE",
+ REPLAY = "REPLAY",
+ SOCIAL = "SOCIAL",
+ TOOLS = "TOOLS",
+ DETAILS = "DETAILS",
+ BOOKING = "BOOKING",
+}
diff --git a/js/src/types/event-metadata.ts b/js/src/types/event-metadata.ts
new file mode 100644
index 000000000..776ed7fd7
--- /dev/null
+++ b/js/src/types/event-metadata.ts
@@ -0,0 +1,23 @@
+import {
+ EventMetadataCategories,
+ EventMetadataKeyType,
+ EventMetadataType,
+} from "./enums";
+
+export interface IEventMetadata {
+ key: string;
+ title?: string;
+ value: string;
+ type: EventMetadataType;
+}
+
+export interface IEventMetadataDescription extends IEventMetadata {
+ icon?: string;
+ placeholder?: string;
+ description: string;
+ choices?: Record
;
+ keyType: EventMetadataKeyType;
+ pattern?: RegExp;
+ label: string;
+ category: EventMetadataCategories;
+}
diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts
index 3dcf496e0..eb3c02469 100644
--- a/js/src/types/event.model.ts
+++ b/js/src/types/event.model.ts
@@ -10,6 +10,7 @@ import type { IParticipant } from "./participant.model";
import { EventOptions } from "./event-options.model";
import type { IEventOptions } from "./event-options.model";
import { EventJoinOptions, EventStatus, EventVisibility } from "./enums";
+import { IEventMetadata } from "./event-metadata";
export interface IEventCardOptions {
hideDate: boolean;
@@ -49,6 +50,7 @@ interface IEventEditJSON {
tags: string[];
options: IEventOptions;
contacts: { id?: string }[];
+ metadata: IEventMetadata[];
}
export interface IEvent {
@@ -84,6 +86,7 @@ export interface IEvent {
tags: ITag[];
options: IEventOptions;
+ metadata: IEventMetadata[];
contacts: IActor[];
toEditJSON(): IEventEditJSON;
@@ -153,6 +156,8 @@ export class EventModel implements IEvent {
options: IEventOptions = new EventOptions();
+ metadata: IEventMetadata[] = [];
+
constructor(hash?: IEvent) {
if (!hash) return;
@@ -193,6 +198,7 @@ export class EventModel implements IEvent {
this.contacts = hash.contacts;
this.tags = hash.tags;
+ this.metadata = hash.metadata;
if (hash.options) this.options = hash.options;
}
@@ -212,6 +218,12 @@ export class EventModel implements IEvent {
phoneAddress: this.phoneAddress,
physicalAddress: this.removeTypeName(this.physicalAddress),
options: this.removeTypeName(this.options),
+ metadata: this.metadata.map(({ key, value, type, title }) => ({
+ key,
+ value,
+ type,
+ title,
+ })),
attributedToId:
this.attributedTo && this.attributedTo.id ? this.attributedTo.id : null,
contacts: this.contacts.map(({ id }) => ({
diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue
index 9144809a9..eaacac57b 100644
--- a/js/src/views/Event/Edit.vue
+++ b/js/src/views/Event/Edit.vue
@@ -122,6 +122,15 @@
+ {{
+ $t(
+ "Integrate this event with 3rd-party tools and show metadata for the event."
+ )
+ }}
+