Improve post & events cards, homepage and my events page
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -606,7 +606,6 @@ import {
|
||||
CURRENT_ACTOR_CLIENT,
|
||||
IDENTITIES,
|
||||
LOGGED_USER_DRAFTS,
|
||||
LOGGED_USER_PARTICIPATIONS,
|
||||
PERSON_STATUS_GROUP,
|
||||
} from "../../graphql/actor";
|
||||
import {
|
||||
@@ -635,6 +634,7 @@ import { IEventOptions } from "@/types/event-options.model";
|
||||
import { USER_SETTINGS } from "@/graphql/user";
|
||||
import { IUser } from "@/types/current-user.model";
|
||||
import { IAddress } from "@/types/address.model";
|
||||
import { LOGGED_USER_PARTICIPATIONS } from "@/graphql/participant";
|
||||
|
||||
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
||||
|
||||
|
||||
@@ -52,14 +52,10 @@
|
||||
{{ showPassedEvents ? $t("Past events") : $t("Upcoming events") }}
|
||||
</subtitle>
|
||||
<b-switch v-model="showPassedEvents">{{ $t("Past events") }}</b-switch>
|
||||
<transition-group name="list" tag="div" class="event-list">
|
||||
<EventListViewCard
|
||||
v-for="event in group.organizedEvents.elements"
|
||||
:key="event.id"
|
||||
:event="event"
|
||||
:options="{ memberofGroup: isCurrentActorMember }"
|
||||
/>
|
||||
</transition-group>
|
||||
<grouped-multi-event-minimalist-card
|
||||
:events="group.organizedEvents.elements"
|
||||
:isCurrentActorMember="isCurrentActorMember"
|
||||
/>
|
||||
<b-message
|
||||
v-if="
|
||||
group.organizedEvents.elements.length === 0 &&
|
||||
@@ -88,7 +84,7 @@ import { Component } from "vue-property-decorator";
|
||||
import { mixins } from "vue-class-component";
|
||||
import RouteName from "@/router/name";
|
||||
import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||
import EventListViewCard from "@/components/Event/EventListViewCard.vue";
|
||||
import GroupedMultiEventMinimalistCard from "@/components/Event/GroupedMultiEventMinimalistCard.vue";
|
||||
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
||||
import GroupMixin from "@/mixins/group";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
@@ -120,14 +116,14 @@ const EVENTS_PAGE_LIMIT = 10;
|
||||
beforeDateTime: this.showPassedEvents ? new Date() : null,
|
||||
afterDateTime: this.showPassedEvents ? null : new Date(),
|
||||
organisedEventsPage: this.eventsPage,
|
||||
organisedEventslimit: EVENTS_PAGE_LIMIT,
|
||||
organisedEventsLimit: EVENTS_PAGE_LIMIT,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Subtitle,
|
||||
EventListViewCard,
|
||||
GroupedMultiEventMinimalistCard,
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
@@ -18,110 +18,175 @@
|
||||
>
|
||||
</div>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<section v-if="futureParticipations.length > 0">
|
||||
<subtitle>
|
||||
{{ $t("Upcoming") }}
|
||||
</subtitle>
|
||||
<transition-group name="list" tag="p">
|
||||
<div v-for="month in monthlyFutureParticipations" :key="month[0]">
|
||||
<span class="upcoming-month">{{ month[0] }}</span>
|
||||
<EventListCard
|
||||
v-for="participation in month[1]"
|
||||
:key="participation.id"
|
||||
:participation="participation"
|
||||
:options="{ hideDate: false }"
|
||||
@event-deleted="eventDeleted"
|
||||
class="participation"
|
||||
/>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div class="columns is-centered">
|
||||
<b-button
|
||||
class="column is-narrow"
|
||||
v-if="
|
||||
hasMoreFutureParticipations && futureParticipations.length === limit
|
||||
"
|
||||
@click="loadMoreFutureParticipations"
|
||||
size="is-large"
|
||||
type="is-primary"
|
||||
>{{ $t("Load more") }}</b-button
|
||||
<div class="wrapper">
|
||||
<div class="event-filter">
|
||||
<b-field grouped group-multiline>
|
||||
<b-field>
|
||||
<b-switch v-model="showUpcoming">{{
|
||||
showUpcoming ? $t("Upcoming events") : $t("Past events")
|
||||
}}</b-switch>
|
||||
</b-field>
|
||||
<b-field v-if="showUpcoming">
|
||||
<b-checkbox v-model="showDrafts">{{ $t("Drafts") }}</b-checkbox>
|
||||
</b-field>
|
||||
<b-field v-if="showUpcoming">
|
||||
<b-checkbox v-model="showAttending">{{
|
||||
$t("Attending")
|
||||
}}</b-checkbox>
|
||||
</b-field>
|
||||
<b-field v-if="showUpcoming">
|
||||
<b-checkbox v-model="showMyGroups">{{
|
||||
$t("From my groups")
|
||||
}}</b-checkbox>
|
||||
</b-field>
|
||||
<p v-if="!showUpcoming">
|
||||
{{
|
||||
$tc(
|
||||
"You have attended {count} events in the past.",
|
||||
pastParticipations.total,
|
||||
{
|
||||
count: pastParticipations.total,
|
||||
}
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<b-field
|
||||
class="date-filter"
|
||||
expanded
|
||||
:label="
|
||||
showUpcoming
|
||||
? $t('Showing events starting on')
|
||||
: $t('Showing events before')
|
||||
"
|
||||
>
|
||||
<b-datepicker v-model="dateFilter" />
|
||||
<b-button
|
||||
@click="dateFilter = new Date()"
|
||||
class="reset-area"
|
||||
icon-left="close"
|
||||
:title="$t('Clear date filter field')"
|
||||
/>
|
||||
</b-field>
|
||||
</b-field>
|
||||
</div>
|
||||
<div class="my-events">
|
||||
<section
|
||||
class="py-4"
|
||||
v-if="showUpcoming && showDrafts && drafts.length > 0"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="drafts.length > 0">
|
||||
<subtitle>
|
||||
{{ $t("Drafts") }}
|
||||
</subtitle>
|
||||
<div class="columns is-multiline">
|
||||
<EventCard
|
||||
v-for="draft in drafts"
|
||||
:key="draft.uuid"
|
||||
:event="draft"
|
||||
class="is-one-quarter-desktop column"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="pastParticipations.length > 0">
|
||||
<subtitle>
|
||||
{{ $t("Past events") }}
|
||||
</subtitle>
|
||||
<transition-group name="list" tag="p">
|
||||
<div v-for="month in monthlyPastParticipations" :key="month[0]">
|
||||
<span class="past-month">{{ month[0] }}</span>
|
||||
<EventListCard
|
||||
v-for="participation in month[1]"
|
||||
:key="participation.id"
|
||||
:participation="participation"
|
||||
:options="{ hideDate: false }"
|
||||
@event-deleted="eventDeleted"
|
||||
class="participation"
|
||||
/>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div class="columns is-centered">
|
||||
<b-button
|
||||
class="column is-narrow"
|
||||
<multi-event-minimalist-card :events="drafts" :showOrganizer="true" />
|
||||
</section>
|
||||
<section
|
||||
class="py-4"
|
||||
v-if="
|
||||
hasMorePastParticipations && pastParticipations.length === limit
|
||||
showUpcoming && monthlyFutureEvents && monthlyFutureEvents.size > 0
|
||||
"
|
||||
>
|
||||
<transition-group name="list" tag="p">
|
||||
<div
|
||||
class="mb-5"
|
||||
v-for="month in monthlyFutureEvents"
|
||||
:key="month[0]"
|
||||
>
|
||||
<span class="upcoming-month">{{ month[0] }}</span>
|
||||
<div v-for="element in month[1]" :key="element.id">
|
||||
<event-participation-card
|
||||
v-if="'role' in element"
|
||||
:participation="element"
|
||||
:options="{ hideDate: false }"
|
||||
@event-deleted="eventDeleted"
|
||||
class="participation"
|
||||
/>
|
||||
<event-minimalist-card
|
||||
v-else-if="
|
||||
!monthParticipationsIds(month[1]).includes(element.id)
|
||||
"
|
||||
:event="element"
|
||||
class="participation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div class="columns is-centered">
|
||||
<b-button
|
||||
class="column is-narrow"
|
||||
v-if="
|
||||
hasMoreFutureParticipations &&
|
||||
futureParticipations &&
|
||||
futureParticipations.length === limit
|
||||
"
|
||||
@click="loadMoreFutureParticipations"
|
||||
size="is-large"
|
||||
type="is-primary"
|
||||
>{{ $t("Load more") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
class="has-text-centered not-found"
|
||||
v-if="
|
||||
showUpcoming &&
|
||||
monthlyFutureEvents &&
|
||||
monthlyFutureEvents.size === 0 &&
|
||||
!$apollo.loading
|
||||
"
|
||||
@click="loadMorePastParticipations"
|
||||
size="is-large"
|
||||
type="is-primary"
|
||||
>{{ $t("Load more") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
class="has-text-centered not-found"
|
||||
v-if="
|
||||
futureParticipations.length === 0 &&
|
||||
pastParticipations.length === 0 &&
|
||||
!$apollo.loading
|
||||
"
|
||||
>
|
||||
<div class="columns is-vertical is-centered">
|
||||
<div class="column is-three-quarters">
|
||||
<div class="img-container" :class="{ webp: supportsWebPFormat }" />
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
{{ $t("You didn't create or join any event yet.") }}
|
||||
<i18n path="Do you wish to {create_event} or {explore_events}?">
|
||||
<router-link
|
||||
:to="{ name: RouteName.CREATE_EVENT }"
|
||||
slot="create_event"
|
||||
>{{ $t("create an event") }}</router-link
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: RouteName.SEARCH }"
|
||||
slot="explore_events"
|
||||
>{{ $t("explore the events") }}</router-link
|
||||
>
|
||||
</i18n>
|
||||
{{
|
||||
$t(
|
||||
"You don't have any upcoming events. Maybe try another filter?"
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<i18n
|
||||
path="Do you wish to {create_event} or {explore_events}?"
|
||||
tag="p"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: RouteName.CREATE_EVENT }"
|
||||
slot="create_event"
|
||||
>{{ $t("create an event") }}</router-link
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: RouteName.SEARCH }"
|
||||
slot="explore_events"
|
||||
>{{ $t("explore the events") }}</router-link
|
||||
>
|
||||
</i18n>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="!showUpcoming && pastParticipations.elements.length > 0">
|
||||
<transition-group name="list" tag="p">
|
||||
<div v-for="month in monthlyPastParticipations" :key="month[0]">
|
||||
<span class="past-month">{{ month[0] }}</span>
|
||||
<event-participation-card
|
||||
v-for="participation in month[1]"
|
||||
:key="participation.id"
|
||||
:participation="participation"
|
||||
:options="{ hideDate: false }"
|
||||
@event-deleted="eventDeleted"
|
||||
class="participation"
|
||||
/>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div class="columns is-centered">
|
||||
<b-button
|
||||
class="column is-narrow"
|
||||
v-if="
|
||||
hasMorePastParticipations &&
|
||||
pastParticipations.elements.length === limit
|
||||
"
|
||||
@click="loadMorePastParticipations"
|
||||
size="is-large"
|
||||
type="is-primary"
|
||||
>{{ $t("Load more") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -133,35 +198,47 @@ import { ParticipantRole } from "@/types/enums";
|
||||
import RouteName from "@/router/name";
|
||||
import { supportsWebPFormat } from "@/utils/support";
|
||||
import { IParticipant, Participant } from "../../types/participant.model";
|
||||
import { LOGGED_USER_DRAFTS } from "../../graphql/actor";
|
||||
import { EventModel, IEvent } from "../../types/event.model";
|
||||
import EventParticipationCard from "../../components/Event/EventParticipationCard.vue";
|
||||
import MultiEventMinimalistCard from "../../components/Event/MultiEventMinimalistCard.vue";
|
||||
import EventMinimalistCard from "../../components/Event/EventMinimalistCard.vue";
|
||||
import Subtitle from "../../components/Utils/Subtitle.vue";
|
||||
import {
|
||||
LOGGED_USER_PARTICIPATIONS,
|
||||
LOGGED_USER_DRAFTS,
|
||||
} from "../../graphql/actor";
|
||||
import { EventModel, IEvent } from "../../types/event.model";
|
||||
import EventListCard from "../../components/Event/EventListCard.vue";
|
||||
import EventCard from "../../components/Event/EventCard.vue";
|
||||
import Subtitle from "../../components/Utils/Subtitle.vue";
|
||||
LOGGED_USER_UPCOMING_EVENTS,
|
||||
} from "@/graphql/participant";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
|
||||
type Eventable = IParticipant | IEvent;
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
Subtitle,
|
||||
EventCard,
|
||||
EventListCard,
|
||||
MultiEventMinimalistCard,
|
||||
EventParticipationCard,
|
||||
EventMinimalistCard,
|
||||
},
|
||||
apollo: {
|
||||
config: CONFIG,
|
||||
futureParticipations: {
|
||||
query: LOGGED_USER_PARTICIPATIONS,
|
||||
userUpcomingEvents: {
|
||||
query: LOGGED_USER_UPCOMING_EVENTS,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
afterDateTime: new Date().toISOString(),
|
||||
variables() {
|
||||
return {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
afterDateTime: this.dateFilter,
|
||||
};
|
||||
},
|
||||
update: (data) =>
|
||||
data.loggedUser.participations.elements.map(
|
||||
update(data) {
|
||||
this.futureParticipations = data.loggedUser.participations.elements.map(
|
||||
(participation: IParticipant) => new Participant(participation)
|
||||
),
|
||||
);
|
||||
this.groupEvents = data.loggedUser.followedGroupEvents.elements.map(
|
||||
({ event }: { event: IEvent }) => event
|
||||
);
|
||||
},
|
||||
},
|
||||
drafts: {
|
||||
query: LOGGED_USER_DRAFTS,
|
||||
@@ -176,15 +253,14 @@ import Subtitle from "../../components/Utils/Subtitle.vue";
|
||||
pastParticipations: {
|
||||
query: LOGGED_USER_PARTICIPATIONS,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
beforeDateTime: new Date().toISOString(),
|
||||
variables() {
|
||||
return {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
beforeDateTime: this.dateFilter,
|
||||
};
|
||||
},
|
||||
update: (data) =>
|
||||
data.loggedUser.participations.elements.map(
|
||||
(participation: IParticipant) => new Participant(participation)
|
||||
),
|
||||
update: (data) => data.loggedUser.participations,
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
@@ -200,13 +276,89 @@ export default class MyEvents extends Vue {
|
||||
|
||||
limit = 10;
|
||||
|
||||
get showUpcoming(): boolean {
|
||||
return ((this.$route.query.showUpcoming as string) || "true") === "true";
|
||||
}
|
||||
|
||||
set showUpcoming(showUpcoming: boolean) {
|
||||
this.$router.push({
|
||||
name: RouteName.MY_EVENTS,
|
||||
query: { ...this.$route.query, showUpcoming: showUpcoming.toString() },
|
||||
});
|
||||
}
|
||||
|
||||
get showDrafts(): boolean {
|
||||
return ((this.$route.query.showDrafts as string) || "true") === "true";
|
||||
}
|
||||
|
||||
set showDrafts(showDrafts: boolean) {
|
||||
this.$router.push({
|
||||
name: RouteName.MY_EVENTS,
|
||||
query: { ...this.$route.query, showDrafts: showDrafts.toString() },
|
||||
});
|
||||
}
|
||||
|
||||
get showAttending(): boolean {
|
||||
return ((this.$route.query.showAttending as string) || "true") === "true";
|
||||
}
|
||||
|
||||
set showAttending(showAttending: boolean) {
|
||||
this.$router.push({
|
||||
name: RouteName.MY_EVENTS,
|
||||
query: { ...this.$route.query, showAttending: showAttending.toString() },
|
||||
});
|
||||
}
|
||||
|
||||
get showMyGroups(): boolean {
|
||||
return ((this.$route.query.showMyGroups as string) || "false") === "true";
|
||||
}
|
||||
|
||||
set showMyGroups(showMyGroups: boolean) {
|
||||
this.$router.push({
|
||||
name: RouteName.MY_EVENTS,
|
||||
query: { ...this.$route.query, showMyGroups: showMyGroups.toString() },
|
||||
});
|
||||
}
|
||||
|
||||
get dateFilter(): Date {
|
||||
const query = this.$route.query.dateFilter as string;
|
||||
if (query && /(\d{4}-\d{2}-\d{2})/.test(query)) {
|
||||
return new Date(`${query}T00:00:00Z`);
|
||||
}
|
||||
return new Date();
|
||||
}
|
||||
|
||||
set dateFilter(date: Date) {
|
||||
const pad = (number: number) => {
|
||||
if (number < 10) {
|
||||
return "0" + number;
|
||||
}
|
||||
return number;
|
||||
};
|
||||
const stringifiedDate = `${date.getFullYear()}-${pad(
|
||||
date.getMonth() + 1
|
||||
)}-${pad(date.getDate())}`;
|
||||
|
||||
if (this.$route.query.dateFilter !== stringifiedDate) {
|
||||
this.$router.push({
|
||||
name: RouteName.MY_EVENTS,
|
||||
query: {
|
||||
...this.$route.query,
|
||||
dateFilter: stringifiedDate,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
futureParticipations: IParticipant[] = [];
|
||||
|
||||
groupEvents: IEvent[] = [];
|
||||
|
||||
hasMoreFutureParticipations = true;
|
||||
|
||||
pastParticipations: IParticipant[] = [];
|
||||
pastParticipations: Paginate<IParticipant> = { elements: [], total: 0 };
|
||||
|
||||
hasMorePastParticipations = true;
|
||||
|
||||
@@ -216,49 +368,68 @@ export default class MyEvents extends Vue {
|
||||
|
||||
supportsWebPFormat = supportsWebPFormat;
|
||||
|
||||
static monthlyParticipations(
|
||||
participations: IParticipant[],
|
||||
static monthlyEvents(
|
||||
elements: Eventable[],
|
||||
revertSort = false
|
||||
): Map<string, Participant[]> {
|
||||
const res = participations.filter(
|
||||
({ event, role }) =>
|
||||
event.beginsOn != null && role !== ParticipantRole.REJECTED
|
||||
);
|
||||
if (revertSort) {
|
||||
res.sort(
|
||||
(a: IParticipant, b: IParticipant) =>
|
||||
b.event.beginsOn.getTime() - a.event.beginsOn.getTime()
|
||||
);
|
||||
} else {
|
||||
res.sort(
|
||||
(a: IParticipant, b: IParticipant) =>
|
||||
a.event.beginsOn.getTime() - b.event.beginsOn.getTime()
|
||||
);
|
||||
}
|
||||
return res.reduce(
|
||||
(acc: Map<string, IParticipant[]>, participation: IParticipant) => {
|
||||
const month = new Date(participation.event.beginsOn).toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
}
|
||||
): Map<string, Eventable[]> {
|
||||
const res = elements.filter((element: Eventable) => {
|
||||
if ("role" in element) {
|
||||
return (
|
||||
element.event.beginsOn != null &&
|
||||
element.role !== ParticipantRole.REJECTED
|
||||
);
|
||||
const filteredParticipations: IParticipant[] = acc.get(month) || [];
|
||||
filteredParticipations.push(participation);
|
||||
acc.set(month, filteredParticipations);
|
||||
return acc;
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
}
|
||||
return element.beginsOn != null;
|
||||
});
|
||||
if (revertSort) {
|
||||
res.sort((a: Eventable, b: Eventable) => {
|
||||
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
|
||||
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
|
||||
return new Date(bTime).getTime() - new Date(aTime).getTime();
|
||||
});
|
||||
} else {
|
||||
res.sort((a: Eventable, b: Eventable) => {
|
||||
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
|
||||
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
|
||||
return new Date(aTime).getTime() - new Date(bTime).getTime();
|
||||
});
|
||||
}
|
||||
return res.reduce((acc: Map<string, Eventable[]>, element: Eventable) => {
|
||||
const month = new Date(
|
||||
"role" in element ? element.event.beginsOn : element.beginsOn
|
||||
).toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
});
|
||||
const filteredElements: Eventable[] = acc.get(month) || [];
|
||||
filteredElements.push(element);
|
||||
acc.set(month, filteredElements);
|
||||
return acc;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
get monthlyFutureParticipations(): Map<string, Participant[]> {
|
||||
return MyEvents.monthlyParticipations(this.futureParticipations);
|
||||
get monthlyFutureEvents(): Map<string, Eventable[]> {
|
||||
let eventable = [] as Eventable[];
|
||||
if (this.showAttending) {
|
||||
eventable = [...eventable, ...this.futureParticipations];
|
||||
}
|
||||
if (this.showMyGroups) {
|
||||
eventable = [...eventable, ...this.groupEvents];
|
||||
}
|
||||
return MyEvents.monthlyEvents(eventable);
|
||||
}
|
||||
|
||||
get monthlyPastParticipations(): Map<string, Participant[]> {
|
||||
return MyEvents.monthlyParticipations(this.pastParticipations, true);
|
||||
get monthlyPastParticipations(): Map<string, Eventable[]> {
|
||||
return MyEvents.monthlyEvents(this.pastParticipations.elements, true);
|
||||
}
|
||||
|
||||
monthParticipationsIds(elements: Eventable[]): string[] {
|
||||
let res = elements.filter((element: Eventable) => {
|
||||
return "role" in element;
|
||||
}) as IParticipant[];
|
||||
return res.map(({ event }: { event: IEvent }) => {
|
||||
return event.id as string;
|
||||
});
|
||||
}
|
||||
|
||||
loadMoreFutureParticipations(): void {
|
||||
@@ -287,9 +458,12 @@ export default class MyEvents extends Vue {
|
||||
this.futureParticipations = this.futureParticipations.filter(
|
||||
(participation) => participation.event.id !== eventid
|
||||
);
|
||||
this.pastParticipations = this.pastParticipations.filter(
|
||||
(participation) => participation.event.id !== eventid
|
||||
);
|
||||
this.pastParticipations = {
|
||||
elements: this.pastParticipations.elements.filter(
|
||||
(participation) => participation.event.id !== eventid
|
||||
),
|
||||
total: this.pastParticipations.total - 1,
|
||||
};
|
||||
}
|
||||
|
||||
get hideCreateEventButton(): boolean {
|
||||
@@ -300,6 +474,8 @@ export default class MyEvents extends Vue {
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss" scoped>
|
||||
@import "~bulma/sass/utilities/mixins.sass";
|
||||
|
||||
main > .container {
|
||||
background: $white;
|
||||
|
||||
@@ -335,6 +511,7 @@ section {
|
||||
}
|
||||
|
||||
.not-found {
|
||||
margin-top: 2rem;
|
||||
.img-container {
|
||||
background-image: url("../../../public/img/pics/event_creation-480w.jpg");
|
||||
@media (min-resolution: 2dppx) {
|
||||
@@ -359,4 +536,41 @@ section {
|
||||
margin: auto auto 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-areas: "filter" "events";
|
||||
align-items: start;
|
||||
|
||||
@include desktop {
|
||||
gap: 2rem;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-template-areas: "filter events";
|
||||
}
|
||||
|
||||
.event-filter {
|
||||
grid-area: filter;
|
||||
background: lightgray;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem 1.25rem 0.25rem;
|
||||
|
||||
@include desktop {
|
||||
padding: 2rem 1.25rem;
|
||||
::v-deep .field.is-grouped {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .field > .field {
|
||||
margin: 0 auto 1.25rem !important;
|
||||
}
|
||||
|
||||
.date-filter ::v-deep .field-body {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.my-events {
|
||||
grid-area: events;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
currentActor.id
|
||||
"
|
||||
@click="joinGroup"
|
||||
@keyup.enter="joinGroup"
|
||||
type="is-primary"
|
||||
:disabled="previewPublic"
|
||||
>{{ $t("Join group") }}</b-button
|
||||
@@ -172,6 +173,7 @@
|
||||
currentActor.id
|
||||
"
|
||||
@click="followGroup"
|
||||
@keyup.enter="followGroup"
|
||||
type="is-primary"
|
||||
:disabled="isCurrentActorPendingFollow"
|
||||
>{{ $t("Follow") }}</b-button
|
||||
@@ -195,6 +197,7 @@
|
||||
outlined
|
||||
v-if="isCurrentActorPendingFollow && currentActor.id"
|
||||
@click="unFollowGroup"
|
||||
@keyup.enter="unFollowGroup"
|
||||
type="is-primary"
|
||||
>{{ $t("Cancel follow request") }}</b-button
|
||||
><b-button
|
||||
@@ -208,6 +211,7 @@
|
||||
<b-button
|
||||
v-if="isCurrentActorFollowing"
|
||||
@click="toggleFollowNotify"
|
||||
@keyup.enter="toggleFollowNotify"
|
||||
:icon-left="
|
||||
isCurrentActorFollowingNotify
|
||||
? 'bell-outline'
|
||||
@@ -218,6 +222,7 @@
|
||||
outlined
|
||||
icon-left="share"
|
||||
@click="triggerShare()"
|
||||
@keyup.enter="triggerShare()"
|
||||
v-if="!isCurrentActorAGroupMember || previewPublic"
|
||||
>
|
||||
{{ $t("Share") }}
|
||||
@@ -246,6 +251,7 @@
|
||||
v-if="!previewPublic && isCurrentActorAGroupMember"
|
||||
aria-role="menuitem"
|
||||
@click="triggerShare()"
|
||||
@keyup.enter="triggerShare()"
|
||||
>
|
||||
<span>
|
||||
<b-icon icon="share" />
|
||||
@@ -280,6 +286,7 @@
|
||||
v-if="ableToReport"
|
||||
aria-role="menuitem"
|
||||
@click="isReportModalActive = true"
|
||||
@keyup.enter="isReportModalActive = true"
|
||||
>
|
||||
<span>
|
||||
<b-icon icon="flag" />
|
||||
@@ -289,7 +296,8 @@
|
||||
<b-dropdown-item
|
||||
aria-role="menuitem"
|
||||
v-if="isCurrentActorAGroupMember && !previewPublic"
|
||||
@click="leaveGroup"
|
||||
@click="openLeaveGroupModal"
|
||||
@keyup.enter="openLeaveGroupModal"
|
||||
>
|
||||
<span>
|
||||
<b-icon icon="exit-to-app" />
|
||||
@@ -401,8 +409,8 @@
|
||||
class="organized-events-wrapper"
|
||||
v-if="group && group.organizedEvents.total > 0"
|
||||
>
|
||||
<EventMinimalistCard
|
||||
v-for="event in group.organizedEvents.elements"
|
||||
<event-minimalist-card
|
||||
v-for="event in group.organizedEvents.elements.slice(0, 3)"
|
||||
:event="event"
|
||||
:key="event.uuid"
|
||||
class="organized-event"
|
||||
@@ -436,13 +444,11 @@
|
||||
}"
|
||||
>
|
||||
<template v-slot:default>
|
||||
<div v-if="group.posts.total > 0" class="posts-wrapper">
|
||||
<post-list-item
|
||||
v-for="post in group.posts.elements"
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
/>
|
||||
</div>
|
||||
<multi-post-list-item
|
||||
v-if="group.posts.total > 0"
|
||||
:posts="group.posts.elements.slice(0, 3)"
|
||||
:isCurrentActorMember="isCurrentActorAGroupMember"
|
||||
/>
|
||||
<empty-content v-else-if="group" icon="bullhorn" :inline="true">
|
||||
{{ $t("No posts yet") }}
|
||||
</empty-content>
|
||||
@@ -502,6 +508,7 @@
|
||||
<span
|
||||
class="map-show-button"
|
||||
@click="showMap = !showMap"
|
||||
@keyup.enter="showMap = !showMap"
|
||||
v-if="physicalAddress.geom"
|
||||
>{{ $t("Show map") }}</span
|
||||
>
|
||||
@@ -527,8 +534,8 @@
|
||||
class="organized-events-wrapper"
|
||||
v-if="group && organizedEvents.elements.length > 0"
|
||||
>
|
||||
<EventMinimalistCard
|
||||
v-for="event in organizedEvents.elements"
|
||||
<event-minimalist-card
|
||||
v-for="event in organizedEvents.elements.slice(0, 3)"
|
||||
:event="event"
|
||||
:key="event.uuid"
|
||||
class="organized-event"
|
||||
@@ -562,13 +569,21 @@
|
||||
</section>
|
||||
<section>
|
||||
<subtitle>{{ $t("Latest posts") }}</subtitle>
|
||||
<div v-if="posts.elements.length > 0" class="posts-wrapper">
|
||||
<post-list-item
|
||||
v-for="post in posts.elements"
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<multi-post-list-item
|
||||
v-if="
|
||||
posts.elements.filter(
|
||||
(post) =>
|
||||
!post.draft && post.visibility === PostVisibility.PUBLIC
|
||||
).length > 0
|
||||
"
|
||||
:posts="
|
||||
posts.elements.filter(
|
||||
(post) =>
|
||||
!post.draft && post.visibility === PostVisibility.PUBLIC
|
||||
)
|
||||
"
|
||||
/>
|
||||
<empty-content v-else-if="group" icon="bullhorn" :inline="true">
|
||||
{{ $t("No posts yet") }}
|
||||
</empty-content>
|
||||
@@ -630,7 +645,7 @@ import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||
import CompactTodo from "@/components/Todo/CompactTodo.vue";
|
||||
import EventMinimalistCard from "@/components/Event/EventMinimalistCard.vue";
|
||||
import DiscussionListItem from "@/components/Discussion/DiscussionListItem.vue";
|
||||
import PostListItem from "@/components/Post/PostListItem.vue";
|
||||
import MultiPostListItem from "@/components/Post/MultiPostListItem.vue";
|
||||
import ResourceItem from "@/components/Resource/ResourceItem.vue";
|
||||
import FolderItem from "@/components/Resource/FolderItem.vue";
|
||||
import { Address } from "@/types/address.model";
|
||||
@@ -668,7 +683,7 @@ import {
|
||||
},
|
||||
components: {
|
||||
DiscussionListItem,
|
||||
PostListItem,
|
||||
MultiPostListItem,
|
||||
EventMinimalistCard,
|
||||
CompactTodo,
|
||||
Subtitle,
|
||||
@@ -712,6 +727,8 @@ export default class Group extends mixins(GroupMixin) {
|
||||
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
|
||||
PostVisibility = PostVisibility;
|
||||
|
||||
Openness = Openness;
|
||||
|
||||
showMap = false;
|
||||
@@ -751,6 +768,20 @@ export default class Group extends mixins(GroupMixin) {
|
||||
});
|
||||
}
|
||||
|
||||
protected async openLeaveGroupModal(): Promise<void> {
|
||||
this.$buefy.dialog.confirm({
|
||||
type: "is-danger",
|
||||
title: this.$t("Leave group") as string,
|
||||
message: this.$t(
|
||||
"Are you sure you want to leave the group {groupName}? You'll loose access to this group's private content. This action cannot be undone.",
|
||||
{ groupName: `<b>${displayName(this.group)}</b>` }
|
||||
) as string,
|
||||
onConfirm: () => this.leaveGroup(),
|
||||
confirmText: this.$t("Leave group") as string,
|
||||
cancelText: this.$t("Cancel") as string,
|
||||
});
|
||||
}
|
||||
|
||||
async leaveGroup(): Promise<void> {
|
||||
try {
|
||||
const [group, currentActorId] = [
|
||||
@@ -1016,8 +1047,8 @@ export default class Group extends mixins(GroupMixin) {
|
||||
return {
|
||||
total: this.group.posts.total,
|
||||
elements: this.group.posts.elements.filter((post: IPost) => {
|
||||
if (this.previewPublic) {
|
||||
return !(post.draft || post.visibility == PostVisibility.PRIVATE);
|
||||
if (this.previewPublic || !this.isCurrentActorAGroupMember) {
|
||||
return !post.draft && post.visibility == PostVisibility.PUBLIC;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
@@ -1144,19 +1175,6 @@ div.container {
|
||||
section {
|
||||
background: $white;
|
||||
|
||||
.posts-wrapper {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.organized-events-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.organized-event {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.presentation {
|
||||
.media-left {
|
||||
span.icon.is-large {
|
||||
@@ -1306,10 +1324,6 @@ div.container {
|
||||
|
||||
section {
|
||||
margin-top: 0;
|
||||
|
||||
.posts-wrapper {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1319,5 +1333,12 @@ div.container {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.organized-events-wrapper,
|
||||
.posts-wrapper {
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
grid-template: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<b-loading :active="$apollo.loading" />
|
||||
<section
|
||||
class="container section"
|
||||
v-if="group && isCurrentActorAGroupAdmin && followers"
|
||||
@@ -125,7 +126,7 @@
|
||||
</template>
|
||||
</b-table>
|
||||
</section>
|
||||
<b-message v-else-if="group">
|
||||
<b-message v-else-if="!$apollo.loading && group">
|
||||
{{ $t("You are not an administrator for this group.") }}
|
||||
</b-message>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<b-loading :active="$apollo.loading" />
|
||||
<section
|
||||
class="container section"
|
||||
v-if="group && isCurrentActorAGroupAdmin"
|
||||
@@ -230,7 +231,7 @@
|
||||
</template>
|
||||
</b-table>
|
||||
</section>
|
||||
<b-message v-else-if="group">
|
||||
<b-message v-else-if="!$apollo.loading && group">
|
||||
{{ $t("You are not an administrator for this group.") }}
|
||||
</b-message>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<b-loading :active="$apollo.loading" />
|
||||
<section
|
||||
class="container section"
|
||||
v-if="group && isCurrentActorAGroupAdmin"
|
||||
@@ -169,7 +170,7 @@
|
||||
{{ value }}
|
||||
</b-message>
|
||||
</section>
|
||||
<b-message v-else>
|
||||
<b-message v-else-if="!$apollo.loading">
|
||||
{{ $t("You are not an administrator for this group.") }}
|
||||
</b-message>
|
||||
</div>
|
||||
@@ -178,12 +179,11 @@
|
||||
<script lang="ts">
|
||||
import { Component, Watch } from "vue-property-decorator";
|
||||
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
||||
import { Route } from "vue-router";
|
||||
import PictureUpload from "@/components/PictureUpload.vue";
|
||||
import { mixins } from "vue-class-component";
|
||||
import GroupMixin from "@/mixins/group";
|
||||
import { GroupVisibility, Openness } from "@/types/enums";
|
||||
import { UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
|
||||
import { UPDATE_GROUP } from "../../graphql/group";
|
||||
import { IGroup, usernameWithDomain } from "../../types/actor";
|
||||
import { Address, IAddress } from "../../types/address.model";
|
||||
import { CONFIG } from "@/graphql/config";
|
||||
@@ -246,31 +246,6 @@ export default class GroupSettings extends mixins(GroupMixin) {
|
||||
this.handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
confirmDeleteGroup(): void {
|
||||
this.$buefy.dialog.confirm({
|
||||
title: this.$t("Delete group") as string,
|
||||
message: this.$t(
|
||||
"Are you sure you want to <b>completely delete</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>."
|
||||
) as string,
|
||||
confirmText: this.$t("Delete group") as string,
|
||||
cancelText: this.$t("Cancel") as string,
|
||||
type: "is-danger",
|
||||
hasIcon: true,
|
||||
onConfirm: () => this.deleteGroup(),
|
||||
});
|
||||
}
|
||||
|
||||
async deleteGroup(): Promise<Route> {
|
||||
await this.$apollo.mutate<{ deleteGroup: IGroup }>({
|
||||
mutation: DELETE_GROUP,
|
||||
variables: {
|
||||
groupId: this.group.id,
|
||||
},
|
||||
});
|
||||
return this.$router.push({ name: RouteName.MY_GROUPS });
|
||||
}
|
||||
|
||||
async copyURL(): Promise<void> {
|
||||
await window.navigator.clipboard.writeText(this.group.url);
|
||||
this.showCopiedTooltip = true;
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
</span>
|
||||
</p>
|
||||
<div>
|
||||
<EventListCard
|
||||
<event-participation-card
|
||||
v-for="participation in thisWeek(row)"
|
||||
@event-deleted="eventDeleted"
|
||||
:key="participation[1].id"
|
||||
@@ -230,24 +230,34 @@
|
||||
<hr
|
||||
role="presentation"
|
||||
class="home-separator"
|
||||
v-if="canShowMyUpcomingEvents && canShowFollowActivity"
|
||||
v-if="canShowMyUpcomingEvents && canShowFollowedGroupEvents"
|
||||
/>
|
||||
<!-- Events from your followed groups -->
|
||||
<section class="followActivity" v-if="canShowFollowActivity">
|
||||
<section class="followActivity" v-if="canShowFollowedGroupEvents">
|
||||
<h2 class="title">
|
||||
{{ $t("Recent events from your groups") }}
|
||||
{{ $t("Upcoming events from your groups") }}
|
||||
</h2>
|
||||
<p>{{ $t("That you follow or of which you are a member") }}</p>
|
||||
<multi-card
|
||||
:events="
|
||||
followedGroupEvents.elements.map(({ event }) => event).slice(0, 3)
|
||||
"
|
||||
/>
|
||||
<multi-card :events="filteredFollowedGroupsEvents" />
|
||||
<span class="view-all">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.MY_EVENTS,
|
||||
query: {
|
||||
showUpcoming: 'true',
|
||||
showDrafts: 'false',
|
||||
showAttending: 'false',
|
||||
showMyGroups: 'true',
|
||||
},
|
||||
}"
|
||||
>{{ $t("View everything") }} >></router-link
|
||||
>
|
||||
</span>
|
||||
</section>
|
||||
<hr
|
||||
role="presentation"
|
||||
class="home-separator"
|
||||
v-if="canShowFollowActivity && canShowCloseEvents"
|
||||
v-if="canShowFollowedGroupEvents && canShowCloseEvents"
|
||||
/>
|
||||
|
||||
<!-- Events close to you -->
|
||||
@@ -319,7 +329,7 @@ import { Paginate } from "@/types/paginate";
|
||||
import { supportsWebPFormat } from "@/utils/support";
|
||||
import { IParticipant, Participant } from "../types/participant.model";
|
||||
import { CLOSE_EVENTS, FETCH_EVENTS } from "../graphql/event";
|
||||
import EventListCard from "../components/Event/EventListCard.vue";
|
||||
import EventParticipationCard from "../components/Event/EventParticipationCard.vue";
|
||||
import MultiCard from "../components/Event/MultiCard.vue";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../graphql/actor";
|
||||
import { IPerson, Person } from "../types/actor";
|
||||
@@ -392,7 +402,7 @@ import Subtitle from "../components/Utils/Subtitle.vue";
|
||||
components: {
|
||||
Subtitle,
|
||||
DateComponent,
|
||||
EventListCard,
|
||||
EventParticipationCard,
|
||||
MultiCard,
|
||||
"settings-onboard": () => import("./User/SettingsOnboard.vue"),
|
||||
},
|
||||
@@ -580,8 +590,20 @@ export default class Home extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
get canShowFollowActivity(): boolean {
|
||||
return this.followedGroupEvents.total > 0;
|
||||
get canShowFollowedGroupEvents(): boolean {
|
||||
return this.filteredFollowedGroupsEvents.length > 0;
|
||||
}
|
||||
|
||||
get filteredFollowedGroupsEvents(): IEvent[] {
|
||||
return this.followedGroupEvents.elements
|
||||
.map(({ event }: { event: IEvent }) => event)
|
||||
.filter(
|
||||
({ id }) =>
|
||||
!this.thisWeekGoingToEvents
|
||||
.map(({ event: { id: event_id } }) => event_id)
|
||||
.includes(id)
|
||||
)
|
||||
.slice(0, 3);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -119,9 +119,11 @@
|
||||
}}</b-button>
|
||||
</span>
|
||||
<span class="navbar-item" v-if="this.isUpdate">
|
||||
<b-button type="is-danger is-outlined" @click="deletePost">{{
|
||||
$t("Delete post")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
type="is-danger is-outlined"
|
||||
@click="openDeletePostModal"
|
||||
>{{ $t("Delete post") }}</b-button
|
||||
>
|
||||
</span>
|
||||
<!-- If an post has been published we can't make it draft anymore -->
|
||||
<span class="navbar-item" v-if="post.draft === true">
|
||||
@@ -167,12 +169,7 @@ import {
|
||||
import GroupMixin from "@/mixins/group";
|
||||
import { PostVisibility } from "@/types/enums";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
import {
|
||||
FETCH_POST,
|
||||
CREATE_POST,
|
||||
UPDATE_POST,
|
||||
DELETE_POST,
|
||||
} from "../../graphql/post";
|
||||
import { CREATE_POST, UPDATE_POST } from "../../graphql/post";
|
||||
|
||||
import { IPost } from "../../types/post.model";
|
||||
import Editor from "../../components/Editor.vue";
|
||||
@@ -183,6 +180,7 @@ import Subtitle from "../../components/Utils/Subtitle.vue";
|
||||
import PictureUpload from "../../components/PictureUpload.vue";
|
||||
import { PERSON_STATUS_GROUP } from "@/graphql/actor";
|
||||
import { FETCH_GROUP } from "@/graphql/group";
|
||||
import PostMixin from "../../mixins/post";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@@ -198,18 +196,6 @@ import { FETCH_GROUP } from "@/graphql/group";
|
||||
return !this.preferredUsername;
|
||||
},
|
||||
},
|
||||
post: {
|
||||
query: FETCH_POST,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables() {
|
||||
return {
|
||||
slug: this.slug,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.slug;
|
||||
},
|
||||
},
|
||||
person: {
|
||||
query: PERSON_STATUS_GROUP,
|
||||
fetchPolicy: "cache-and-network",
|
||||
@@ -242,7 +228,7 @@ import { FETCH_GROUP } from "@/graphql/group";
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class EditPost extends mixins(GroupMixin) {
|
||||
export default class EditPost extends mixins(GroupMixin, PostMixin) {
|
||||
@Prop({ required: false, type: String }) slug: undefined | string;
|
||||
|
||||
@Prop({ required: false, type: String }) preferredUsername!: string;
|
||||
@@ -338,23 +324,6 @@ export default class EditPost extends mixins(GroupMixin) {
|
||||
}
|
||||
}
|
||||
|
||||
async deletePost(): Promise<void> {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: DELETE_POST,
|
||||
variables: {
|
||||
id: this.post.id,
|
||||
},
|
||||
});
|
||||
if (data && this.post.attributedTo) {
|
||||
this.$router.push({
|
||||
name: RouteName.POSTS,
|
||||
params: {
|
||||
preferredUsername: usernameWithDomain(this.post.attributedTo),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static transformMessage(message: string[] | string): string | undefined {
|
||||
if (Array.isArray(message) && message.length > 0) {
|
||||
return message[0];
|
||||
|
||||
@@ -49,10 +49,8 @@
|
||||
>
|
||||
</div>
|
||||
<div class="post-list">
|
||||
<post-element-item
|
||||
v-for="post in group.posts.elements"
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
<multi-post-list-item
|
||||
:posts="group.posts.elements"
|
||||
:isCurrentActorMember="isCurrentActorMember"
|
||||
/>
|
||||
</div>
|
||||
@@ -88,7 +86,7 @@ import { Paginate } from "../../types/paginate";
|
||||
import { IPost } from "../../types/post.model";
|
||||
import { usernameWithDomain } from "../../types/actor";
|
||||
import RouteName from "../../router/name";
|
||||
import PostElementItem from "../../components/Post/PostElementItem.vue";
|
||||
import MultiPostListItem from "../../components/Post/MultiPostListItem.vue";
|
||||
|
||||
const POSTS_PAGE_LIMIT = 10;
|
||||
|
||||
@@ -124,7 +122,7 @@ const POSTS_PAGE_LIMIT = 10;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
PostElementItem,
|
||||
MultiPostListItem,
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -132,7 +130,7 @@ const POSTS_PAGE_LIMIT = 10;
|
||||
const { group } = this;
|
||||
return {
|
||||
title: this.$t("{group} posts", {
|
||||
group: group.name || usernameWithDomain(group),
|
||||
group: group?.name || usernameWithDomain(group),
|
||||
}) as string,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -7,7 +7,16 @@
|
||||
<div class="heading-section">
|
||||
<div class="heading-wrapper">
|
||||
<div class="title-metadata">
|
||||
<h1 class="title">{{ post.title }}</h1>
|
||||
<div class="title-wrapper">
|
||||
<b-tag
|
||||
class="mr-2"
|
||||
type="is-warning"
|
||||
size="is-medium"
|
||||
v-if="post.draft"
|
||||
>{{ $t("Draft") }}</b-tag
|
||||
>
|
||||
<h1 class="title">{{ post.title }}</h1>
|
||||
</div>
|
||||
<p class="metadata">
|
||||
<router-link
|
||||
slot="author"
|
||||
@@ -49,7 +58,14 @@
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="post.visibility === PostVisibility.PRIVATE"
|
||||
v-if="post.visibility === PostVisibility.UNLISTED"
|
||||
class="has-text-grey-dark"
|
||||
>
|
||||
<b-icon icon="link" size="is-small" />
|
||||
{{ $t("Accessible only by link") }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="post.visibility === PostVisibility.PRIVATE"
|
||||
class="has-text-grey-dark"
|
||||
>
|
||||
<b-icon icon="lock" size="is-small" />
|
||||
@@ -61,21 +77,72 @@
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="buttons" v-if="isCurrentActorMember">
|
||||
<b-tag type="is-warning" size="is-medium" v-if="post.draft">{{
|
||||
$t("Draft")
|
||||
}}</b-tag>
|
||||
<router-link
|
||||
<b-dropdown position="is-bottom-left" aria-role="list">
|
||||
<b-button slot="trigger" role="button" icon-right="dots-horizontal">
|
||||
{{ $t("Actions") }}
|
||||
</b-button>
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
has-link
|
||||
v-if="
|
||||
currentActor.id === post.author.id ||
|
||||
isCurrentActorAGroupModerator
|
||||
"
|
||||
:to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }"
|
||||
tag="button"
|
||||
class="button is-text"
|
||||
>{{ $t("Edit") }}</router-link
|
||||
>
|
||||
</p>
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.POST_EDIT,
|
||||
params: { slug: post.slug },
|
||||
}"
|
||||
>{{ $t("Edit") }} <b-icon icon="pencil"
|
||||
/></router-link>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
v-if="
|
||||
currentActor.id === post.author.id ||
|
||||
isCurrentActorAGroupModerator
|
||||
"
|
||||
@click="openDeletePostModal"
|
||||
@keyup.enter="openDeletePostModal"
|
||||
>
|
||||
{{ $t("Delete") }}
|
||||
<b-icon icon="delete" />
|
||||
</b-dropdown-item>
|
||||
|
||||
<hr
|
||||
role="presentation"
|
||||
class="dropdown-divider"
|
||||
aria-role="menuitem"
|
||||
v-if="
|
||||
currentActor.id === post.author.id ||
|
||||
isCurrentActorAGroupModerator
|
||||
"
|
||||
/>
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
v-if="!post.draft"
|
||||
@click="triggerShare()"
|
||||
@keyup.enter="triggerShare()"
|
||||
>
|
||||
<span>
|
||||
{{ $t("Share this event") }}
|
||||
<b-icon icon="share" />
|
||||
</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
v-if="ableToReport"
|
||||
@click="isReportModalActive = true"
|
||||
@keyup.enter="isReportModalActive = true"
|
||||
>
|
||||
<span>
|
||||
{{ $t("Report") }}
|
||||
<b-icon icon="flag" />
|
||||
</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -108,6 +175,21 @@
|
||||
<tag>{{ tag.title }}</tag>
|
||||
</router-link>
|
||||
</section>
|
||||
<b-modal
|
||||
:active.sync="isReportModalActive"
|
||||
has-modal-card
|
||||
ref="reportModal"
|
||||
>
|
||||
<report-modal
|
||||
:on-confirm="reportPost"
|
||||
:title="$t('Report this post')"
|
||||
:outside-domain="groupDomain"
|
||||
@close="$refs.reportModal.close()"
|
||||
/>
|
||||
</b-modal>
|
||||
<b-modal :active.sync="isShareModalActive" has-modal-card ref="shareModal">
|
||||
<share-post-modal :post="post" />
|
||||
</b-modal>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
@@ -122,8 +204,6 @@ import {
|
||||
PERSON_MEMBERSHIPS,
|
||||
PERSON_STATUS_GROUP,
|
||||
} from "../../graphql/actor";
|
||||
import { FETCH_POST } from "../../graphql/post";
|
||||
import { IPost } from "../../types/post.model";
|
||||
import { usernameWithDomain } from "../../types/actor";
|
||||
import RouteName from "../../router/name";
|
||||
import Tag from "../../components/Tag.vue";
|
||||
@@ -132,9 +212,17 @@ import ActorInline from "../../components/Account/ActorInline.vue";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
import { CURRENT_USER_CLIENT } from "@/graphql/user";
|
||||
import { ICurrentUser } from "@/types/current-user.model";
|
||||
import { CONFIG } from "@/graphql/config";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import SharePostModal from "../../components/Post/SharePostModal.vue";
|
||||
import { IReport } from "@/types/report.model";
|
||||
import { CREATE_REPORT } from "@/graphql/report";
|
||||
import ReportModal from "../../components/Report/ReportModal.vue";
|
||||
import PostMixin from "../../mixins/post";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
config: CONFIG,
|
||||
currentUser: CURRENT_USER_CLIENT,
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
memberships: {
|
||||
@@ -150,21 +238,6 @@ import { ICurrentUser } from "@/types/current-user.model";
|
||||
return !this.currentActor || !this.currentActor.id;
|
||||
},
|
||||
},
|
||||
post: {
|
||||
query: FETCH_POST,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables() {
|
||||
return {
|
||||
slug: this.slug,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.slug;
|
||||
},
|
||||
error({ graphQLErrors }) {
|
||||
this.handleErrors(graphQLErrors);
|
||||
},
|
||||
},
|
||||
person: {
|
||||
query: PERSON_STATUS_GROUP,
|
||||
fetchPolicy: "cache-and-network",
|
||||
@@ -187,6 +260,8 @@ import { ICurrentUser } from "@/types/current-user.model";
|
||||
Tag,
|
||||
LazyImageWrapper,
|
||||
ActorInline,
|
||||
SharePostModal,
|
||||
ReportModal,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
@@ -200,13 +275,13 @@ import { ICurrentUser } from "@/types/current-user.model";
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Post extends mixins(GroupMixin) {
|
||||
export default class Post extends mixins(GroupMixin, PostMixin) {
|
||||
@Prop({ required: true, type: String }) slug!: string;
|
||||
|
||||
post!: IPost;
|
||||
|
||||
memberships!: IMember[];
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
currentUser!: ICurrentUser;
|
||||
@@ -217,11 +292,9 @@ export default class Post extends mixins(GroupMixin) {
|
||||
|
||||
PostVisibility = PostVisibility;
|
||||
|
||||
handleErrors(errors: any[]): void {
|
||||
if (errors.some((error) => error.status_code === 404)) {
|
||||
this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
|
||||
}
|
||||
}
|
||||
isShareModalActive = false;
|
||||
|
||||
isReportModalActive = false;
|
||||
|
||||
get isCurrentActorMember(): boolean {
|
||||
if (!this.post.attributedTo || !this.memberships) return false;
|
||||
@@ -236,6 +309,62 @@ export default class Post extends mixins(GroupMixin) {
|
||||
ICurrentUserRole.MODERATOR,
|
||||
].includes(this.currentUser.role);
|
||||
}
|
||||
|
||||
get ableToReport(): boolean {
|
||||
return (
|
||||
this.config &&
|
||||
(this.currentActor.id != null || this.config.anonymous.reports.allowed)
|
||||
);
|
||||
}
|
||||
|
||||
triggerShare(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-start
|
||||
if (navigator.share) {
|
||||
navigator
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
.share({
|
||||
title: this.post.title,
|
||||
url: this.post.url,
|
||||
})
|
||||
.then(() => console.log("Successful share"))
|
||||
.catch((error: any) => console.log("Error sharing", error));
|
||||
} else {
|
||||
this.isShareModalActive = true;
|
||||
// send popup
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-end
|
||||
}
|
||||
|
||||
async reportPost(content: string, forward: boolean): Promise<void> {
|
||||
this.isReportModalActive = false;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.$refs.reportModal.close();
|
||||
const postTitle = this.post.title;
|
||||
|
||||
try {
|
||||
await this.$apollo.mutate<IReport>({
|
||||
mutation: CREATE_REPORT,
|
||||
variables: {
|
||||
postId: this.post.id,
|
||||
reportedId: this.post.attributedTo?.id,
|
||||
content,
|
||||
forward,
|
||||
},
|
||||
});
|
||||
this.$notifier.success(
|
||||
this.$t("Post {eventTitle} reported", { postTitle }) as string
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
get groupDomain(): string | undefined | null {
|
||||
return this.post.attributedTo?.domain;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -261,16 +390,31 @@ article.post {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.title-metadata {
|
||||
min-width: 300px;
|
||||
flex: 20;
|
||||
|
||||
.title-wrapper {
|
||||
display: inline;
|
||||
|
||||
.tag {
|
||||
height: 38px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
& > h1 {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
p.metadata {
|
||||
margin-top: 16px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
|
||||
*:not(:first-child) {
|
||||
padding-left: 5px;
|
||||
@@ -328,5 +472,14 @@ article.post {
|
||||
}
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
a.dropdown-item,
|
||||
.dropdown .dropdown-menu .has-link a,
|
||||
button.dropdown-item {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -60,11 +60,11 @@
|
||||
<hr
|
||||
role="presentation"
|
||||
class="dropdown-divider"
|
||||
v-if="config.resourceProviders.length"
|
||||
v-if="resourceProviders.length"
|
||||
/>
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
v-for="resourceProvider in config.resourceProviders"
|
||||
v-for="resourceProvider in resourceProviders"
|
||||
:key="resourceProvider.software"
|
||||
@click="createResourceFromProvider(resourceProvider)"
|
||||
>
|
||||
@@ -418,6 +418,10 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
return this.filteredPath.slice(-1)[0];
|
||||
}
|
||||
|
||||
get resourceProviders(): IProvider[] {
|
||||
return this.config?.resourceProviders || [];
|
||||
}
|
||||
|
||||
async createResource(): Promise<void> {
|
||||
if (!this.resource.actor) return;
|
||||
this.modalError = "";
|
||||
|
||||
Reference in New Issue
Block a user