Add global search

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-08-26 16:08:58 +02:00
parent bfc936f57c
commit 48935e2168
216 changed files with 3646 additions and 2806 deletions

View File

@@ -1,23 +1,23 @@
<template>
<div class="container mx-auto" v-if="hasCurrentActorPermissionsToEdit">
<h1 class="" v-if="isUpdate === true">
{{ $t("Update event {name}", { name: event.title }) }}
{{ t("Update event {name}", { name: event.title }) }}
</h1>
<h1 class="" v-else>
{{ $t("Create a new event") }}
{{ t("Create a new event") }}
</h1>
<form ref="form">
<h2>{{ $t("General information") }}</h2>
<h2>{{ t("General information") }}</h2>
<picture-upload
v-if="pictureFile"
v-model:pictureFile="pictureFile"
:textFallback="$t('Headline picture')"
v-model:modelValue="pictureFile"
:textFallback="t('Headline picture')"
:defaultImage="event.picture"
/>
<o-field
:label="$t('Title')"
:label="t('Title')"
label-for="title"
:type="checkTitleLength[0]"
:message="checkTitleLength[1]"
@@ -35,12 +35,12 @@
<div class="flex flex-wrap gap-4">
<o-field
v-if="eventCategories"
:label="$t('Category')"
:label="t('Category')"
label-for="category"
class="w-full md:max-w-fit"
>
<o-select
:placeholder="$t('Select a category')"
:placeholder="t('Select a category')"
v-model="event.category"
expanded
>
@@ -62,13 +62,13 @@
<o-field
horizontal
:label="$t('Starts on…')"
:label="t('Starts on…')"
class="begins-on-field"
label-for="begins-on-field"
>
<o-datetimepicker
class="datepicker starts-on"
:placeholder="$t('Type or select a date…')"
:placeholder="t('Type or select a date…')"
icon="calendar-today"
:locale="$i18n.locale"
v-model="beginsOn"
@@ -78,17 +78,17 @@
:first-day-of-week="firstDayOfWeek"
:datepicker="{
id: 'begins-on-field',
'aria-next-label': $t('Next month'),
'aria-previous-label': $t('Previous month'),
'aria-next-label': t('Next month'),
'aria-previous-label': t('Previous month'),
}"
>
</o-datetimepicker>
</o-field>
<o-field horizontal :label="$t('Ends on…')" label-for="ends-on-field">
<o-field horizontal :label="t('Ends on…')" label-for="ends-on-field">
<o-datetimepicker
class="datepicker ends-on"
:placeholder="$t('Type or select a date…')"
:placeholder="t('Type or select a date…')"
icon="calendar-today"
:locale="$i18n.locale"
v-model="endsOn"
@@ -99,41 +99,40 @@
:first-day-of-week="firstDayOfWeek"
:datepicker="{
id: 'ends-on-field',
'aria-next-label': $t('Next month'),
'aria-previous-label': $t('Previous month'),
'aria-next-label': t('Next month'),
'aria-previous-label': t('Previous month'),
}"
>
</o-datetimepicker>
</o-field>
<o-button type="is-text" @click="dateSettingsIsOpen = true">
{{ $t("Date parameters") }}
<o-button class="block" variant="text" @click="dateSettingsIsOpen = true">
{{ t("Date parameters") }}
</o-button>
<div class="address">
<div class="my-6">
<full-address-auto-complete
v-model="eventPhysicalAddress"
:user-timezone="userActualTimezone"
:disabled="isOnline"
:disabled="event.options.isOnline"
:hideSelected="true"
/>
<o-switch class="is-online" v-model="isOnline">{{
$t("The event is fully online")
<o-switch class="my-4" v-model="isOnline">{{
t("The event is fully online")
}}</o-switch>
</div>
<div class="o-field field">
<label class="o-field__label field-label">{{
$t("Description")
}}</label>
<label class="o-field__label field-label">{{ t("Description") }}</label>
<editor-component
v-if="currentActor"
:current-actor="(currentActor as IPerson)"
v-model="event.description"
:aria-label="$t('Event description body')"
:aria-label="t('Event description body')"
/>
</div>
<o-field :label="$t('Website / URL')" label-for="website-url">
<o-field :label="t('Website / URL')" label-for="website-url">
<o-input
icon="link"
type="url"
@@ -144,7 +143,7 @@
</o-field>
<section class="my-4">
<h2>{{ $t("Organizers") }}</h2>
<h2>{{ t("Organizers") }}</h2>
<div v-if="features?.groups && organizerActor?.id">
<o-field>
@@ -155,21 +154,21 @@
</o-field>
<p v-if="!attributedToAGroup && organizerActorEqualToCurrentActor">
{{
$t("The event will show as attributed to your personal profile.")
t("The event will show as attributed to your personal profile.")
}}
</p>
<p v-else-if="!attributedToAGroup">
{{ $t("The event will show as attributed to this profile.") }}
{{ t("The event will show as attributed to this profile.") }}
</p>
<p v-else>
<span>{{
$t("The event will show as attributed to this group.")
t("The event will show as attributed to this group.")
}}</span>
<span
v-if="event.contacts && event.contacts.length"
v-html="
' ' +
$t(
t(
'<b>{contact}</b> will be displayed as contact.',
{
@@ -184,16 +183,16 @@
"
/>
<span v-else>
{{ $t("You may show some members as contacts.") }}
{{ t("You may show some members as contacts.") }}
</span>
</p>
</div>
</section>
<section class="my-4">
<h2>{{ $t("Event metadata") }}</h2>
<h2>{{ t("Event metadata") }}</h2>
<p>
{{
$t(
t(
"Integrate this event with 3rd-party tools and show metadata for the event."
)
}}
@@ -202,12 +201,12 @@
</section>
<section class="my-4">
<h2>
{{ $t("Who can view this event and participate") }}
{{ t("Who can view this event and participate") }}
</h2>
<fieldset>
<legend>
{{
$t(
t(
"When the event is private, you'll need to share the link around."
)
}}
@@ -217,7 +216,7 @@
v-model="event.visibility"
name="eventVisibility"
:native-value="EventVisibility.PUBLIC"
>{{ $t("Visible everywhere on the web (public)") }}</o-radio
>{{ t("Visible everywhere on the web (public)") }}</o-radio
>
</div>
<div class="field">
@@ -225,7 +224,7 @@
v-model="event.visibility"
name="eventVisibility"
:native-value="EventVisibility.UNLISTED"
>{{ $t("Only accessible through link (private)") }}</o-radio
>{{ t("Only accessible through link (private)") }}</o-radio
>
</div>
</fieldset>
@@ -233,7 +232,7 @@
<o-radio v-model="event.visibility"
name="eventVisibility"
:native-value="EventVisibility.PRIVATE">
{{ $t('Page limited to my group (asks for auth)') }}
{{ t('Page limited to my group (asks for auth)') }}
</o-radio>
</div>-->
@@ -242,9 +241,7 @@
:label="t('Anonymous participations')"
>
<o-switch v-model="eventOptions.anonymousParticipation">
{{
$t("I want to allow people to participate without an account.")
}}
{{ t("I want to allow people to participate without an account.") }}
<small
v-if="
anonymousParticipationConfig?.validation.email
@@ -253,7 +250,7 @@
>
<br />
{{
$t(
t(
"Anonymous participants will be asked to confirm their participation through e-mail."
)
}}
@@ -263,23 +260,23 @@
<o-field :label="t('Participation approval')">
<o-switch v-model="needsApproval">{{
$t("I want to approve every participation request")
t("I want to approve every participation request")
}}</o-switch>
</o-field>
<o-field :label="t('Number of places')">
<o-switch v-model="limitedPlaces">{{
$t("Limited number of places")
t("Limited number of places")
}}</o-switch>
</o-field>
<div class="" v-if="limitedPlaces">
<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
type="number"
controls-position="compact"
:aria-minus-label="$t('Decrease')"
:aria-plus-label="$t('Increase')"
:aria-minus-label="t('Decrease')"
:aria-plus-label="t('Increase')"
min="1"
v-model="eventOptions.maximumAttendeeCapacity"
id="number-of-places"
@@ -288,28 +285,28 @@
<!--
<o-field>
<o-switch v-model="eventOptions.showRemainingAttendeeCapacity">
{{ $t('Show remaining number of places') }}
{{ t('Show remaining number of places') }}
</o-switch>
</o-field>
<o-field>
<o-switch v-model="eventOptions.showParticipationPrice">
{{ $t('Display participation price') }}
{{ t('Display participation price') }}
</o-switch>
</o-field>-->
</div>
</section>
<section class="my-4">
<h2>{{ $t("Public comment moderation") }}</h2>
<h2>{{ t("Public comment moderation") }}</h2>
<fieldset>
<legend>{{ $t("Who can post a comment?") }}</legend>
<legend>{{ t("Who can post a comment?") }}</legend>
<o-field>
<o-radio
v-model="eventOptions.commentModeration"
name="commentModeration"
:native-value="CommentModeration.ALLOW_ALL"
>{{ $t("Allow all comments from users with accounts") }}</o-radio
>{{ t("Allow all comments from users with accounts") }}</o-radio
>
</o-field>
@@ -317,7 +314,7 @@
<!-- <o-radio v-model="eventOptions.commentModeration"-->
<!-- name="commentModeration"-->
<!-- :native-value="CommentModeration.MODERATED">-->
<!-- {{ $t('Moderated comments (shown after approval)') }}-->
<!-- {{ t('Moderated comments (shown after approval)') }}-->
<!-- </o-radio>-->
<!-- </div>-->
@@ -326,18 +323,18 @@
v-model="eventOptions.commentModeration"
name="commentModeration"
:native-value="CommentModeration.CLOSED"
>{{ $t("Close comments for all (except for admins)") }}</o-radio
>{{ t("Close comments for all (except for admins)") }}</o-radio
>
</o-field>
</fieldset>
</section>
<section class="my-4">
<h2>{{ $t("Status") }}</h2>
<h2>{{ t("Status") }}</h2>
<fieldset>
<legend>
{{
$t(
t(
"Does the event needs to be confirmed later or is it cancelled?"
)
}}
@@ -350,7 +347,7 @@
:native-value="EventStatus.TENTATIVE"
>
<o-icon icon="calendar-question" />
{{ $t("Tentative: Will be confirmed later") }}
{{ t("Tentative: Will be confirmed later") }}
</o-radio>
<o-radio
v-model="event.status"
@@ -359,7 +356,7 @@
:native-value="EventStatus.CONFIRMED"
>
<o-icon icon="calendar-check" />
{{ $t("Confirmed: Will happen") }}
{{ t("Confirmed: Will happen") }}
</o-radio>
<o-radio
v-model="event.status"
@@ -368,7 +365,7 @@
:native-value="EventStatus.CANCELLED"
>
<o-icon icon="calendar-remove" />
{{ $t("Cancelled: Won't happen") }}
{{ t("Cancelled: Won't happen") }}
</o-radio>
</o-field>
</fieldset>
@@ -377,30 +374,30 @@
</div>
<div class="container mx-auto" v-else>
<o-notification variant="danger">
{{ $t("Only group moderators can create, edit and delete events.") }}
{{ t("Only group moderators can create, edit and delete events.") }}
</o-notification>
</div>
<o-modal
v-model:active="dateSettingsIsOpen"
has-modal-card
trap-focus
:close-button-aria-label="$t('Close')"
:close-button-aria-label="t('Close')"
>
<form class="p-3">
<header class="">
<h2 class="">{{ $t("Date and time settings") }}</h2>
<h2 class="">{{ t("Date and time settings") }}</h2>
</header>
<section class="">
<p>
{{
$t(
t(
"Event timezone will default to the timezone of the event's address if there is one, or to your own timezone setting."
)
}}
</p>
<o-field :label="$t('Timezone')" label-for="timezone" expanded>
<o-field :label="t('Timezone')" label-for="timezone" expanded>
<o-select
:placeholder="$t('Select a timezone')"
:placeholder="t('Select a timezone')"
:loading="timezoneLoading"
v-model="timezone"
id="timezone"
@@ -424,23 +421,23 @@
@click="timezone = null"
class="reset-area"
icon-left="close"
:title="$t('Clear timezone field')"
:title="t('Clear timezone field')"
/>
</o-field>
<o-field :label="$t('Event page settings')">
<o-field :label="t('Event page settings')">
<o-switch v-model="eventOptions.showStartTime">{{
$t("Show the time when the event begins")
t("Show the time when the event begins")
}}</o-switch>
</o-field>
<o-field>
<o-switch v-model="eventOptions.showEndTime">{{
$t("Show the time when the event ends")
t("Show the time when the event ends")
}}</o-switch>
</o-field>
</section>
<footer class="mt-2">
<o-button @click="dateSettingsIsOpen = false">
{{ $t("OK") }}
{{ t("OK") }}
</o-button>
</footer>
</form>
@@ -449,23 +446,22 @@
<nav
role="navigation"
aria-label="main navigation"
class="bg-secondary/70"
class="bg-mbz-yellow-alt-200 py-3"
:class="{ 'is-fixed-bottom': showFixedNavbar }"
v-if="hasCurrentActorPermissionsToEdit"
>
<div class="container mx-auto">
<div class="flex justify-between">
<div class="">
<span class="dark:text-gray-900" v-if="isEventModified">{{
$t("Unsaved changes")
}}</span>
</div>
<div class="flex justify-between items-center">
<span class="dark:text-gray-900" v-if="isEventModified">
{{ t("Unsaved changes") }}
</span>
<div class="flex flex-wrap gap-3 items-center">
<span class="">
<o-button type="is-text" @click="confirmGoBack">{{
$t("Cancel")
}}</o-button>
</span>
<o-button
variant="text"
@click="confirmGoBack"
class="dark:!text-black"
>{{ t("Cancel") }}</o-button
>
<!-- If an event has been published we can't make it draft anymore -->
<span class="" v-if="event.draft === true">
<o-button
@@ -473,7 +469,7 @@
outlined
@click="createOrUpdateDraft"
:disabled="saving"
>{{ $t("Save draft") }}</o-button
>{{ t("Save draft") }}</o-button
>
</span>
<span class="">
@@ -483,9 +479,9 @@
@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>
<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>
</o-button>
</span>
</div>
@@ -596,7 +592,7 @@
<script lang="ts" setup>
import { getTimezoneOffset } from "date-fns-tz";
import PictureUpload from "@/components/PictureUpload.vue";
import EditorComponent from "@/components/Editor.vue";
import EditorComponent from "@/components/TextEditor.vue";
import TagInput from "@/components/Event/TagInput.vue";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
import EventMetadataList from "@/components/Event/EventMetadataList.vue";
@@ -617,27 +613,33 @@ import {
MemberRole,
ParticipantRole,
} from "@/types/enums";
import OrganizerPickerWrapper from "../../components/Event/OrganizerPickerWrapper.vue";
import OrganizerPickerWrapper from "@/components/Event/OrganizerPickerWrapper.vue";
import {
CREATE_EVENT,
EDIT_EVENT,
EVENT_PERSON_PARTICIPATION,
} from "../../graphql/event";
} from "@/graphql/event";
import {
EventModel,
IEditableEvent,
IEvent,
removeTypeName,
toEditJSON,
} from "../../types/event.model";
import { LOGGED_USER_DRAFTS } from "../../graphql/actor";
import { IActor, IGroup, IPerson, usernameWithDomain } from "../../types/actor";
} from "@/types/event.model";
import { LOGGED_USER_DRAFTS } from "@/graphql/actor";
import {
IActor,
IGroup,
IPerson,
usernameWithDomain,
displayNameAndUsername,
} from "@/types/actor";
import {
buildFileFromIMedia,
buildFileVariable,
readFileAsync,
} from "../../utils/image";
import RouteName from "../../router/name";
} from "@/utils/image";
import RouteName from "@/router/name";
import "intersection-observer";
import {
ApolloCache,
@@ -759,7 +761,7 @@ const unmodifiedEvent = ref<IEditableEvent>(new EventModel());
const pictureFile = ref<File | null>(null);
const canPromote = ref(true);
// const canPromote = ref(true);
const limitedPlaces = ref(false);
const showFixedNavbar = ref(true);
@@ -905,7 +907,7 @@ onCreateEventMutationDone(async ({ data }) => {
message: (event.value.draft
? t("The event has been created as a draft")
: t("The event has been published")) as string,
variant: "is-success",
variant: "success",
position: "bottom-right",
duration: 5000,
});
@@ -964,6 +966,7 @@ onEditEventMutationError((err) => {
const updateEvent = async (): Promise<void> => {
saving.value = true;
const variables = await buildVariables();
console.debug("update event", variables);
editEventMutation(variables);
};
@@ -1016,7 +1019,6 @@ const handleError = (err: any) => {
*/
const postCreateOrUpdate = (store: any, updatedEvent: IEvent) => {
const resultEvent: IEvent = { ...updatedEvent };
console.debug("resultEvent", resultEvent);
if (!updatedEvent.draft) {
store.writeQuery({
query: EVENT_PERSON_PARTICIPATION,
@@ -1056,7 +1058,6 @@ const postCreateOrUpdate = (store: any, updatedEvent: IEvent) => {
/**
* Refresh drafts or participation cache depending if the event is still draft or not
*/
// eslint-disable-next-line class-methods-use-this
const postRefetchQueries = (
updatedEvent: IEvent
): InternalRefetchQueriesInclude => {
@@ -1093,11 +1094,11 @@ const buildVariables = async () => {
options: eventOptions.value,
};
// const organizerActor = event.value?.organizerActor?.id
// ? event.value.organizerActor
// : organizerActor.value;
const localOrganizerActor = event.value?.organizerActor?.id
? event.value.organizerActor
: organizerActor.value;
if (organizerActor.value) {
res = { ...res, organizerActorId: organizerActor.value?.id };
res = { ...res, organizerActorId: localOrganizerActor?.id };
}
const attributedToId = event.value?.attributedTo?.id
? event.value?.attributedTo.id
@@ -1155,7 +1156,7 @@ const needsApproval = computed({
const checkTitleLength = computed((): Array<string | undefined> => {
return event.value.title.length > 80
? ["is-info", t("The event title will be ellipsed.") as string]
? ["info", t("The event title will be ellipsed.")]
: [undefined, undefined];
});
@@ -1189,7 +1190,7 @@ const confirmGoElsewhere = (): Promise<boolean> => {
message,
confirmText: t("Abandon editing") as string,
cancelText: t("Continue editing") as string,
type: "is-warning",
variant: "warning",
hasIcon: true,
onConfirm: () => resolve(true),
onCancel: () => resolve(false),
@@ -1356,6 +1357,13 @@ const isOnline = computed({
};
},
});
watch(isOnline, (newIsOnline) => {
if (newIsOnline === true) {
eventPhysicalAddress.value = null;
}
});
const dateFnsLocale = inject<Locale>("dateFnsLocale");
const firstDayOfWeek = computed((): number => {
@@ -1366,6 +1374,15 @@ const { event: fetchedEvent, onResult: onFetchEventResult } = useFetchEvent(
eventId.value
);
watch(
fetchedEvent,
() => {
if (!fetchedEvent.value) return;
event.value = { ...fetchedEvent.value };
},
{ immediate: true }
);
onFetchEventResult((result) => {
if (!result.loading && result.data?.event) {
event.value = { ...result.data?.event };

View File

@@ -65,36 +65,35 @@
</popover-actor-card>
</span>
</div>
<p class="flex gap-1 items-center" dir="auto">
<tag v-if="eventCategory" class="category" capitalize>{{
eventCategory
}}</tag>
<router-link
v-for="tag in event?.tags ?? []"
:key="tag.title"
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
>
<tag>{{ tag.title }}</tag>
</router-link>
</p>
<tag variant="warning" size="is-medium" v-if="event?.draft"
>{{ t("Draft") }}
</tag>
<span
class="event-status"
v-if="event?.status !== EventStatus.CONFIRMED"
>
<tag
variant="warning"
v-if="event?.status === EventStatus.TENTATIVE"
>{{ t("Event to be confirmed") }}</tag
>
<tag
variant="danger"
v-if="event?.status === EventStatus.CANCELLED"
>{{ t("Event cancelled") }}</tag
>
</span>
<div class="inline-flex items-center gap-1">
<p v-if="event?.status !== EventStatus.CONFIRMED">
<tag
variant="warning"
v-if="event?.status === EventStatus.TENTATIVE"
>{{ t("Event to be confirmed") }}</tag
>
<tag
variant="danger"
v-if="event?.status === EventStatus.CANCELLED"
>{{ t("Event cancelled") }}</tag
>
</p>
<p class="flex gap-1 items-center" dir="auto">
<tag v-if="eventCategory" class="category" capitalize>{{
eventCategory
}}</tag>
<router-link
v-for="tag in event?.tags ?? []"
:key="tag.title"
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
>
<tag>{{ tag.title }}</tag>
</router-link>
</p>
<tag variant="warning" size="medium" v-if="event?.draft"
>{{ t("Draft") }}
</tag>
</div>
</div>
<div class="">
@@ -375,7 +374,7 @@
ref="reportModal"
:close-button-aria-label="t('Close')"
>
<report-modal
<ReportModal
:on-confirm="reportEvent"
:title="t('Report this event')"
:outside-domain="organizerDomain"
@@ -456,7 +455,7 @@
<o-field :label="t('Message')">
<o-input
type="textarea"
size="is-medium"
size="medium"
v-model="messageForConfirmation"
minlength="10"
></o-input>
@@ -522,35 +521,28 @@ import {
FETCH_EVENT,
JOIN_EVENT,
} from "@/graphql/event";
import { CURRENT_ACTOR_CLIENT, PERSON_STATUS_GROUP } from "@/graphql/actor";
import { EventModel, IEvent } from "@/types/event.model";
import { IEvent } from "@/types/event.model";
import {
displayName,
IActor,
IPerson,
Person,
usernameWithDomain,
} from "@/types/actor";
import { GRAPHQL_API_ENDPOINT } from "@/api/_entrypoint";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import MultiCard from "@/components/Event/MultiCard.vue";
import ReportModal from "@/components/Report/ReportModal.vue";
import { IReport } from "@/types/report.model";
import { CREATE_REPORT } from "@/graphql/report";
import EventMixin from "@/mixins/event";
import IdentityPicker from "../Account/IdentityPicker.vue";
import IdentityPicker from "@/views/Account/IdentityPicker.vue";
import ParticipationSection from "@/components/Participation/ParticipationSection.vue";
import RouteName from "@/router/name";
import CommentTree from "@/components/Comment/CommentTree.vue";
import "intersection-observer";
import { CONFIG } from "@/graphql/config";
import {
AnonymousParticipationNotFoundError,
getLeaveTokenForParticipation,
isParticipatingInThisEvent,
removeAnonymousParticipation,
} from "@/services/AnonymousParticipationStorage";
import { IConfig } from "@/types/config.model";
import Tag from "@/components/Tag.vue";
import EventMetadataSidebar from "@/components/Event/EventMetadataSidebar.vue";
import EventBanner from "@/components/Event/EventBanner.vue";
@@ -560,12 +552,9 @@ import { IParticipant } from "@/types/participant.model";
import { ApolloCache, FetchResult } from "@apollo/client/core";
import { IEventMetadataDescription } from "@/types/event-metadata";
import { eventMetaDataList } from "@/services/EventMetadata";
import { USER_SETTINGS } from "@/graphql/user";
import { IUser } from "@/types/current-user.model";
import { useDeleteEvent, useFetchEvent } from "@/composition/apollo/event";
import {
computed,
handleError,
onMounted,
ref,
watch,
@@ -601,24 +590,26 @@ import { useI18n } from "vue-i18n";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import { Dialog } from "@/plugins/dialog";
import { Notifier } from "@/plugins/notifier";
import { AbsintheGraphQLErrors } from "@/types/errors.model";
import { useHead } from "@vueuse/head";
const ShareEventModal = defineAsyncComponent(
() => import("@/components/Event/ShareEventModal.vue")
);
const IntegrationTwitch = defineAsyncComponent(
() => import("@/components/Event/Integrations/Twitch.vue")
() => import("@/components/Event/Integrations/TwitchIntegration.vue")
);
const IntegrationPeertube = defineAsyncComponent(
() => import("@/components/Event/Integrations/PeerTube.vue")
() => import("@/components/Event/Integrations/PeerTubeIntegration.vue")
);
const IntegrationYoutube = defineAsyncComponent(
() => import("@/components/Event/Integrations/YouTube.vue")
() => import("@/components/Event/Integrations/YouTubeIntegration.vue")
);
const IntegrationJitsiMeet = defineAsyncComponent(
() => import("@/components/Event/Integrations/JitsiMeet.vue")
() => import("@/components/Event/Integrations/JitsiMeetIntegration.vue")
);
const IntegrationEtherpad = defineAsyncComponent(
() => import("@/components/Event/Integrations/Etherpad.vue")
() => import("@/components/Event/Integrations/EtherpadIntegration.vue")
);
const props = defineProps<{
@@ -1057,8 +1048,8 @@ const triggerShare = (): void => {
title: event.value?.title,
url: event.value?.url,
})
.then(() => console.log("Successful share"))
.catch((error: any) => console.log("Error sharing", error));
.then(() => console.debug("Successful share"))
.catch((error: any) => console.debug("Error sharing", error));
} else {
isShareModalActive.value = true;
// send popup
@@ -1067,7 +1058,7 @@ const triggerShare = (): void => {
// @ts-ignore-end
};
const handleErrors = (errors: any[]): void => {
const handleErrors = (errors: AbsintheGraphQLErrors): void => {
if (
errors.some((error) => error.status_code === 404) ||
errors.some(({ message }) => message.includes("has invalid value $uuid"))
@@ -1076,7 +1067,9 @@ const handleErrors = (errors: any[]): void => {
}
};
onFetchEventError(({ graphQlErrors }) => handleErrors(graphQLErrors));
onFetchEventError(({ graphQLErrors }) =>
handleErrors(graphQLErrors as AbsintheGraphQLErrors)
);
const actorIsParticipant = computed((): boolean => {
if (actorIsOrganizer.value) return true;
@@ -1108,7 +1101,7 @@ const canManageEvent = computed((): boolean => {
return actorIsOrganizer.value || hasGroupPrivileges.value;
});
const endDate = computed((): Date | undefined => {
const endDate = computed((): string | undefined => {
return event.value?.endsOn && event.value.endsOn > event.value.beginsOn
? event.value.endsOn
: event.value?.beginsOn;
@@ -1211,6 +1204,11 @@ const eventCategory = computed((): string | undefined => {
return eventCategory.id === event.value?.category;
})?.label as string;
});
useHead({
title: computed(() => eventTitle.value ?? ""),
meta: [{ name: "description", content: eventDescription.value }],
});
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@@ -1380,10 +1378,6 @@ a.participations-link {
text-decoration: none;
}
.event-status .tag {
font-size: 1rem;
}
.no-border {
border: 0;
cursor: auto;

View File

@@ -70,7 +70,7 @@
)
}}
</p>
<o-button type="is-text" tag="a" :href="group.url">
<o-button variant="text" tag="a" :href="group.url">
{{ $t("View the group profile on the original instance") }}
</o-button>
</div>

View File

@@ -20,7 +20,9 @@
</div>
<!-- <o-loading v-model:active="$apollo.loading"></o-loading> -->
<div class="wrapper flex flex-wrap gap-4 items-start">
<div class="event-filter text-violet-1 flex-auto md:flex-none">
<div
class="event-filter rounded p-3 flex-auto md:flex-none bg-zinc-300 dark:bg-zinc-700"
>
<o-field>
<o-switch v-model="showUpcoming">{{
showUpcoming ? t("Upcoming events") : t("Past events")
@@ -56,10 +58,12 @@
? t('Showing events starting on')
: t('Showing events before')
"
labelFor="events-start-datepicker"
>
<o-datepicker
v-model="dateFilter"
:first-day-of-week="firstDayOfWeek"
id="events-start-datepicker"
/>
<o-button
@click="dateFilter = new Date()"
@@ -469,9 +473,6 @@ section {
.event-filter {
grid-area: filter;
background: lightgray;
border-radius: 5px;
padding: 0.75rem 1.25rem 0.25rem;
// @include desktop {
// padding: 2rem 1.25rem;

View File

@@ -55,16 +55,22 @@
:key="format"
aria-role="listitem"
@click="
exportParticipants({
eventId: event?.id,
format,
})
exportParticipants(
{
eventId: event?.id,
format,
},
{ context: { type: format } }
)
"
@keyup.enter="
exportParticipants({
eventId: event.value?.id,
format,
})
exportParticipants(
{
eventId: event?.id,
format,
},
{ context: { type: format } }
)
"
>
<button class="dropdown-button">
@@ -81,9 +87,9 @@
ref="queueTable"
detailed
detail-key="id"
:checked-rows.sync="checkedRows"
v-model:checked-rows="checkedRows"
checkable
:is-row-checkable="(row) => row.role !== ParticipantRole.CREATOR"
:is-row-checkable="(row: IParticipant) => row.role !== ParticipantRole.CREATOR"
checkbox-position="left"
:show-detail-icon="false"
:loading="participantsLoading"
@@ -100,37 +106,38 @@
backend-sorting
:default-sort-direction="'desc'"
:default-sort="['insertedAt', 'desc']"
@page-change="(newPage) => (page = newPage)"
@sort="(field, order) => emit('sort', field, order)"
@page-change="(newPage: number) => (page = newPage)"
@sort="(field: string, order: string) => emit('sort', field, order)"
>
<o-table-column
field="actor.preferredUsername"
:label="t('Participant')"
v-slot="props"
>
<article class="media">
<figure
class="media-left image is-48x48"
v-if="props.row.actor.avatar"
>
<img class="is-rounded" :src="props.row.actor.avatar.url" alt="" />
<article>
<figure v-if="props.row.actor.avatar">
<img
class="rounded"
:src="props.row.actor.avatar.url"
alt=""
height="48"
width="48"
/>
</figure>
<Incognito
v-else-if="props.row.actor.preferredUsername === 'anonymous'"
:size="48"
/>
<AccountCircle v-else :size="48" />
<div class="media-content">
<div>
<div class="prose dark:prose-invert">
<span v-if="props.row.actor.preferredUsername !== 'anonymous'">
<span v-if="props.row.actor.name">{{
props.row.actor.name
}}</span
><br />
<span class="is-size-7 has-text-grey-dark"
>@{{ usernameWithDomain(props.row.actor) }}</span
>
</span>
>@{{ usernameWithDomain(props.row.actor) }}</span
>
<span v-else>
{{ t("Anonymous participant") }}
</span>
@@ -139,30 +146,30 @@
</article>
</o-table-column>
<o-table-column field="role" :label="t('Role')" v-slot="props">
<b-tag
<tag
variant="primary"
v-if="props.row.role === ParticipantRole.CREATOR"
>
{{ t("Organizer") }}
</b-tag>
<b-tag v-else-if="props.row.role === ParticipantRole.PARTICIPANT">
</tag>
<tag v-else-if="props.row.role === ParticipantRole.PARTICIPANT">
{{ t("Participant") }}
</b-tag>
<b-tag v-else-if="props.row.role === ParticipantRole.NOT_CONFIRMED">
</tag>
<tag v-else-if="props.row.role === ParticipantRole.NOT_CONFIRMED">
{{ t("Not confirmed") }}
</b-tag>
<b-tag
</tag>
<tag
variant="warning"
v-else-if="props.row.role === ParticipantRole.NOT_APPROVED"
>
{{ t("Not approved") }}
</b-tag>
<b-tag
</tag>
<tag
variant="danger"
v-else-if="props.row.role === ParticipantRole.REJECTED"
>
{{ t("Rejected") }}
</b-tag>
</tag>
</o-table-column>
<o-table-column
field="metadata.message"
@@ -250,17 +257,17 @@
<script lang="ts" setup>
import { ParticipantRole } from "@/types/enums";
import { IParticipant } from "../../types/participant.model";
import { IEvent, IEventParticipantStats } from "../../types/event.model";
import { IParticipant } from "@/types/participant.model";
import { IEvent } from "@/types/event.model";
import {
EXPORT_EVENT_PARTICIPATIONS,
PARTICIPANTS,
UPDATE_PARTICIPANT,
} from "../../graphql/event";
import { usernameWithDomain } from "../../types/actor";
import { nl2br } from "../../utils/html";
import { asyncForEach } from "../../utils/asyncForEach";
import RouteName from "../../router/name";
} from "@/graphql/event";
import { usernameWithDomain } from "@/types/actor";
import { nl2br } from "@/utils/html";
import { asyncForEach } from "@/utils/asyncForEach";
import RouteName from "@/router/name";
import { useCurrentActorClient } from "@/composition/apollo/actor";
import { useParticipantsExportFormats } from "@/composition/config";
import { useMutation, useQuery } from "@vue/apollo-composable";
@@ -276,6 +283,7 @@ import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Incognito from "vue-material-design-icons/Incognito.vue";
import EmptyContent from "@/components/Utils/EmptyContent.vue";
import { Notifier } from "@/plugins/notifier";
import Tag from "@/components/Tag.vue";
const PARTICIPANTS_PER_PAGE = 10;
const MESSAGE_ELLIPSIS_LENGTH = 130;
@@ -286,6 +294,8 @@ const props = defineProps<{
eventId: string;
}>();
const emit = defineEmits(["sort"]);
const { t } = useI18n({ useScope: "global" });
const { currentActor } = useCurrentActorClient();
@@ -307,11 +317,9 @@ const role = useRouteQuery(
enumTransformer(ParticipantRole)
);
const limit = ref(PARTICIPANTS_PER_PAGE);
const checkedRows = ref<IParticipant[]>([]);
// const queueTable = ref(null);
const queueTable = ref();
const { result: participantsResult, loading: participantsLoading } = useQuery<{
event: IEvent;
@@ -333,10 +341,10 @@ const { result: participantsResult, loading: participantsLoading } = useQuery<{
const event = computed(() => participantsResult.value?.event);
const participantStats = computed((): IEventParticipantStats | null => {
if (!event.value) return null;
return event.value.participantStats;
});
// const participantStats = computed((): IEventParticipantStats | null => {
// if (!event.value) return null;
// return event.value.participantStats;
// });
const { mutate: updateParticipant, onError: onUpdateParticipantError } =
useMutation(UPDATE_PARTICIPANT);
@@ -373,14 +381,14 @@ const {
onError: onExportParticipantsMutationError,
} = useMutation(EXPORT_EVENT_PARTICIPATIONS);
onExportParticipantsMutationDone(({ data }) => {
onExportParticipantsMutationDone(({ data, context }) => {
const link =
window.origin +
"/exports/" +
type.toLowerCase() +
context?.type.toLowerCase() +
"/" +
exportEventParticipants;
console.log(link);
data?.exportEventParticipants;
console.debug(link);
const a = document.createElement("a");
a.style.display = "none";
document.body.appendChild(a);
@@ -449,7 +457,7 @@ const toggleQueueDetails = (row: IParticipant): void => {
}
};
const openDetailedRows = <Record<string, boolean>>{};
const openDetailedRows = ref<Record<string, boolean>>({});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
@@ -485,21 +493,4 @@ nav.breadcrumb {
text-decoration: none;
}
}
button.dropdown-button {
&:hover {
background-color: #f5f5f5;
color: #0a0a0a;
}
width: 100%;
display: flex;
flex: 1;
background: white;
border: none;
cursor: pointer;
color: #4a4a4a;
font-size: 0.875rem;
line-height: 1.5;
padding: 0.375rem 1rem;
}
</style>