forked from potsda.mn/mobilizon
Merge branch 'fix-group-events-past' into 'master'
Fix group events past Closes #492 See merge request framasoft/mobilizon!750
This commit is contained in:
commit
a0db8aedfb
|
@ -384,7 +384,7 @@ export default class EditorComponent extends Vue {
|
||||||
searchText: query,
|
searchText: query,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// TODO: TipTap doesn't handle async for onFilter, hence the following line.
|
// TipTap doesn't handle async for onFilter, hence the following line.
|
||||||
this.filteredActors = result.data.searchPersons.elements;
|
this.filteredActors = result.data.searchPersons.elements;
|
||||||
return this.filteredActors;
|
return this.filteredActors;
|
||||||
},
|
},
|
||||||
|
|
|
@ -72,7 +72,6 @@
|
||||||
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"
|
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"
|
||||||
><b-icon icon="email" size="is-large" type="is-primary"
|
><b-icon icon="email" size="is-large" type="is-primary"
|
||||||
/></a>
|
/></a>
|
||||||
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
import { GROUP_FIELDS_FRAGMENTS } from "./group";
|
||||||
|
|
||||||
const participantQuery = `
|
const participantQuery = `
|
||||||
role,
|
role,
|
||||||
|
@ -622,3 +623,54 @@ export const GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const FETCH_GROUP_EVENTS = gql`
|
||||||
|
query(
|
||||||
|
$name: String!
|
||||||
|
$afterDateTime: DateTime
|
||||||
|
$beforeDateTime: DateTime
|
||||||
|
$organisedEventsPage: Int
|
||||||
|
$organisedEventslimit: Int
|
||||||
|
) {
|
||||||
|
group(preferredUsername: $name) {
|
||||||
|
id
|
||||||
|
preferredUsername
|
||||||
|
domain
|
||||||
|
name
|
||||||
|
organizedEvents(
|
||||||
|
afterDatetime: $afterDateTime
|
||||||
|
beforeDatetime: $beforeDateTime
|
||||||
|
page: $organisedEventsPage
|
||||||
|
limit: $organisedEventslimit
|
||||||
|
) {
|
||||||
|
elements {
|
||||||
|
id
|
||||||
|
uuid
|
||||||
|
title
|
||||||
|
beginsOn
|
||||||
|
draft
|
||||||
|
options {
|
||||||
|
maximumAttendeeCapacity
|
||||||
|
}
|
||||||
|
participantStats {
|
||||||
|
participant
|
||||||
|
notApproved
|
||||||
|
}
|
||||||
|
attributedTo {
|
||||||
|
id
|
||||||
|
preferredUsername
|
||||||
|
name
|
||||||
|
domain
|
||||||
|
}
|
||||||
|
organizerActor {
|
||||||
|
id
|
||||||
|
preferredUsername
|
||||||
|
name
|
||||||
|
domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -442,7 +442,6 @@
|
||||||
"Actor": "Actor",
|
"Actor": "Actor",
|
||||||
"Text": "Text",
|
"Text": "Text",
|
||||||
"Upcoming events": "Upcoming events",
|
"Upcoming events": "Upcoming events",
|
||||||
"View all upcoming events": "View all upcoming events",
|
|
||||||
"Resources": "Resources",
|
"Resources": "Resources",
|
||||||
"Public page": "Public page",
|
"Public page": "Public page",
|
||||||
"Discussions": "Discussions",
|
"Discussions": "Discussions",
|
||||||
|
@ -810,5 +809,6 @@
|
||||||
"Your participation will be validated once you click the confirmation link into the email.": "Your participation will be validated once you click the confirmation link into the email.",
|
"Your participation will be validated once you click the confirmation link into the email.": "Your participation will be validated once you click the confirmation link into the email.",
|
||||||
"Unable to load event for participation. The error details are provided below:": "Unable to load event for participation. The error details are provided below:",
|
"Unable to load event for participation. The error details are provided below:": "Unable to load event for participation. The error details are provided below:",
|
||||||
"Unable to save your participation in this browser.": "Unable to save your participation in this browser.",
|
"Unable to save your participation in this browser.": "Unable to save your participation in this browser.",
|
||||||
"return to the event's page": "return to the event's page"
|
"return to the event's page": "return to the event's page",
|
||||||
|
"View all events": "View all events"
|
||||||
}
|
}
|
||||||
|
|
|
@ -903,5 +903,6 @@
|
||||||
"Unable to load event for participation. The error details are provided below:": "Impossible de charger l'événement pour la participation. Les détails de l'erreur sont disponibles ci-dessous :",
|
"Unable to load event for participation. The error details are provided below:": "Impossible de charger l'événement pour la participation. Les détails de l'erreur sont disponibles ci-dessous :",
|
||||||
"Unable to save your participation in this browser.": "Échec de la sauvegarde de votre participation dans ce navigateur.",
|
"Unable to save your participation in this browser.": "Échec de la sauvegarde de votre participation dans ce navigateur.",
|
||||||
"return to the event's page": "retourner sur la page de l'événement",
|
"return to the event's page": "retourner sur la page de l'événement",
|
||||||
"You may now close this window, or {return_to_event}.": "Vous pouvez maintenant fermer cette fenêtre, ou bien {return_to_event}."
|
"You may now close this window, or {return_to_event}.": "Vous pouvez maintenant fermer cette fenêtre, ou bien {return_to_event}.",
|
||||||
|
"View all events": "Voir tous les événements"
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { Component, Vue } from "vue-property-decorator";
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
name: this.$route.params.preferredUsername,
|
name: this.$route.params.preferredUsername,
|
||||||
|
beforeDateTime: null,
|
||||||
|
afterDateTime: new Date(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
skip() {
|
skip() {
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
{{ showPassedEvents ? $t("Past events") : $t("Upcoming events") }}
|
{{ showPassedEvents ? $t("Past events") : $t("Upcoming events") }}
|
||||||
</subtitle>
|
</subtitle>
|
||||||
<b-switch v-model="showPassedEvents">{{ $t("Past events") }}</b-switch>
|
<b-switch v-model="showPassedEvents">{{ $t("Past events") }}</b-switch>
|
||||||
<transition-group name="list" tag="p">
|
<transition-group name="list" tag="div" class="event-list">
|
||||||
<EventListViewCard
|
<EventListViewCard
|
||||||
v-for="event in group.organizedEvents.elements"
|
v-for="event in group.organizedEvents.elements"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
|
@ -68,6 +68,16 @@
|
||||||
>
|
>
|
||||||
{{ $t("No events found") }}
|
{{ $t("No events found") }}
|
||||||
</b-message>
|
</b-message>
|
||||||
|
<b-pagination
|
||||||
|
:total="group.organizedEvents.total"
|
||||||
|
v-model="eventsPage"
|
||||||
|
:per-page="EVENTS_PAGE_LIMIT"
|
||||||
|
:aria-next-label="$t('Next page')"
|
||||||
|
:aria-previous-label="$t('Previous page')"
|
||||||
|
:aria-page-label="$t('Page')"
|
||||||
|
:aria-current-label="$t('Current page')"
|
||||||
|
>
|
||||||
|
</b-pagination>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,15 +85,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component } from "vue-property-decorator";
|
import { Component } from "vue-property-decorator";
|
||||||
import { mixins } from "vue-class-component";
|
import { mixins } from "vue-class-component";
|
||||||
import { FETCH_GROUP } from "@/graphql/group";
|
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
import Subtitle from "@/components/Utils/Subtitle.vue";
|
import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||||
import EventListViewCard from "@/components/Event/EventListViewCard.vue";
|
import EventListViewCard from "@/components/Event/EventListViewCard.vue";
|
||||||
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
||||||
import GroupMixin from "@/mixins/group";
|
import GroupMixin from "@/mixins/group";
|
||||||
import { IMember } from "@/types/actor/member.model";
|
import { IMember } from "@/types/actor/member.model";
|
||||||
|
import { FETCH_GROUP_EVENTS } from "@/graphql/event";
|
||||||
import { IGroup, IPerson, usernameWithDomain } from "../../types/actor";
|
import { IGroup, IPerson, usernameWithDomain } from "../../types/actor";
|
||||||
|
|
||||||
|
const EVENTS_PAGE_LIMIT = 10;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
apollo: {
|
apollo: {
|
||||||
currentActor: CURRENT_ACTOR_CLIENT,
|
currentActor: CURRENT_ACTOR_CLIENT,
|
||||||
|
@ -101,12 +113,14 @@ import { IGroup, IPerson, usernameWithDomain } from "../../types/actor";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
query: FETCH_GROUP,
|
query: FETCH_GROUP_EVENTS,
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
name: this.$route.params.preferredUsername,
|
name: this.$route.params.preferredUsername,
|
||||||
beforeDateTime: this.showPassedEvents ? new Date() : null,
|
beforeDateTime: this.showPassedEvents ? new Date() : null,
|
||||||
afterDateTime: this.showPassedEvents ? null : new Date(),
|
afterDateTime: this.showPassedEvents ? null : new Date(),
|
||||||
|
organisedEventsPage: this.eventsPage,
|
||||||
|
organisedEventslimit: EVENTS_PAGE_LIMIT,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -123,11 +137,13 @@ export default class GroupEvents extends mixins(GroupMixin) {
|
||||||
|
|
||||||
currentActor!: IPerson;
|
currentActor!: IPerson;
|
||||||
|
|
||||||
|
eventsPage = 1;
|
||||||
|
|
||||||
usernameWithDomain = usernameWithDomain;
|
usernameWithDomain = usernameWithDomain;
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
showPassedEvents = false;
|
EVENTS_PAGE_LIMIT = EVENTS_PAGE_LIMIT;
|
||||||
|
|
||||||
get isCurrentActorMember(): boolean {
|
get isCurrentActorMember(): boolean {
|
||||||
if (!this.group || !this.memberships) return false;
|
if (!this.group || !this.memberships) return false;
|
||||||
|
@ -135,10 +151,25 @@ export default class GroupEvents extends mixins(GroupMixin) {
|
||||||
.map(({ parent: { id } }) => id)
|
.map(({ parent: { id } }) => id)
|
||||||
.includes(this.group.id);
|
.includes(this.group.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showPassedEvents(): boolean {
|
||||||
|
return (
|
||||||
|
this.$route.query.future !== undefined &&
|
||||||
|
this.$route.query.future.toString() === "false"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
set showPassedEvents(value: boolean) {
|
||||||
|
this.$router.push({ query: { future: this.showPassedEvents.toString() } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container.section {
|
.container.section {
|
||||||
background: $white;
|
background: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.event-list {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -362,18 +362,26 @@
|
||||||
:key="event.uuid"
|
:key="event.uuid"
|
||||||
class="organized-event"
|
class="organized-event"
|
||||||
/>
|
/>
|
||||||
<router-link
|
</div>
|
||||||
:to="{
|
<div
|
||||||
name: RouteName.GROUP_EVENTS,
|
v-else-if="group && group.organizedEvents.elements.length == 0"
|
||||||
params: { preferredUsername: usernameWithDomain(group) },
|
class="content has-text-grey has-text-centered"
|
||||||
}"
|
>
|
||||||
>{{ $t("View all upcoming events") }}</router-link
|
<p>{{ $t("No public upcoming events") }}</p>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="group" class="content has-text-grey has-text-centered">
|
<div v-else-if="group" class="content has-text-grey has-text-centered">
|
||||||
<p>{{ $t("No public upcoming events") }}</p>
|
<p>{{ $t("No public upcoming events") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<b-skeleton animated v-else></b-skeleton>
|
<b-skeleton animated v-else-if="$apollo.loading"></b-skeleton>
|
||||||
|
<router-link
|
||||||
|
v-if="group.organizedEvents.total > 0"
|
||||||
|
:to="{
|
||||||
|
name: RouteName.GROUP_EVENTS,
|
||||||
|
params: { preferredUsername: usernameWithDomain(group) },
|
||||||
|
query: { future: group.organizedEvents.elements.length > 0 },
|
||||||
|
}"
|
||||||
|
>{{ $t("View all events") }}</router-link
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<subtitle>{{ $t("Latest posts") }}</subtitle>
|
<subtitle>{{ $t("Latest posts") }}</subtitle>
|
||||||
|
@ -383,18 +391,19 @@
|
||||||
:key="post.id"
|
:key="post.id"
|
||||||
:post="post"
|
:post="post"
|
||||||
/>
|
/>
|
||||||
<router-link
|
|
||||||
:to="{
|
|
||||||
name: RouteName.POSTS,
|
|
||||||
params: { preferredUsername: usernameWithDomain(group) },
|
|
||||||
}"
|
|
||||||
>{{ $t("View all posts") }}</router-link
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="group" class="content has-text-grey has-text-centered">
|
<div v-else-if="group" class="content has-text-grey has-text-centered">
|
||||||
<p>{{ $t("No posts yet") }}</p>
|
<p>{{ $t("No posts yet") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<b-skeleton animated v-else></b-skeleton>
|
<b-skeleton animated v-else-if="$apollo.loading"></b-skeleton>
|
||||||
|
<router-link
|
||||||
|
v-if="group.posts.total > 0"
|
||||||
|
:to="{
|
||||||
|
name: RouteName.POSTS,
|
||||||
|
params: { preferredUsername: usernameWithDomain(group) },
|
||||||
|
}"
|
||||||
|
>{{ $t("View all posts") }}</router-link
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
<b-modal
|
<b-modal
|
||||||
v-if="physicalAddress && physicalAddress.geom"
|
v-if="physicalAddress && physicalAddress.geom"
|
||||||
|
@ -607,7 +616,6 @@ export default class Group extends mixins(GroupMixin) {
|
||||||
|
|
||||||
@Watch("isCurrentActorAGroupMember")
|
@Watch("isCurrentActorAGroupMember")
|
||||||
refetchGroupData(): void {
|
refetchGroupData(): void {
|
||||||
console.log("refetchGroupData");
|
|
||||||
this.$apollo.queries.group.refetch();
|
this.$apollo.queries.group.refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
95
js/tests/unit/specs/components/Group/GroupSection.spec.ts
Normal file
95
js/tests/unit/specs/components/Group/GroupSection.spec.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import { config, createLocalVue, mount } from "@vue/test-utils";
|
||||||
|
import GroupSection from "@/components/Group/GroupSection.vue";
|
||||||
|
import Buefy from "buefy";
|
||||||
|
import VueRouter, { Location } from "vue-router";
|
||||||
|
import RouteName from "@/router/name";
|
||||||
|
import { routes } from "@/router";
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Buefy);
|
||||||
|
config.mocks.$t = (key: string): string => key;
|
||||||
|
localVue.use(VueRouter);
|
||||||
|
const router = new VueRouter({ routes, mode: "history" });
|
||||||
|
|
||||||
|
const groupPreferredUsername = "my_group";
|
||||||
|
const groupDomain = "remotedomain.net";
|
||||||
|
const groupUsername = `${groupPreferredUsername}@${groupDomain}`;
|
||||||
|
|
||||||
|
const defaultSlotText = "A list of elements";
|
||||||
|
const createSlotButtonText = "+ Post a public message";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
|
privateSection?: boolean;
|
||||||
|
route?: Location;
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseProps: Props = {
|
||||||
|
title: "My group section",
|
||||||
|
icon: "bullhorn",
|
||||||
|
route: {
|
||||||
|
name: RouteName.POSTS,
|
||||||
|
params: {
|
||||||
|
preferredUsername: groupUsername,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateWrapper = (customProps: Props = {}) => {
|
||||||
|
return mount(GroupSection, {
|
||||||
|
localVue,
|
||||||
|
router,
|
||||||
|
propsData: { ...baseProps, ...customProps },
|
||||||
|
slots: {
|
||||||
|
default: `<div>${defaultSlotText}</div>`,
|
||||||
|
create: `<router-link :to="{
|
||||||
|
name: 'POST_CREATE',
|
||||||
|
params: { preferredUsername: '${groupUsername}' },
|
||||||
|
}"
|
||||||
|
class="button is-primary"
|
||||||
|
>{{ $t("${createSlotButtonText}") }}</router-link
|
||||||
|
>`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("GroupSection", () => {
|
||||||
|
it("renders group section with basic informations", () => {
|
||||||
|
const wrapper = generateWrapper({});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(".group-section-title h2 span.icon i")
|
||||||
|
.classes(`mdi-${baseProps.icon}`)
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(wrapper.find(".group-section-title h2 span:last-child").text()).toBe(
|
||||||
|
baseProps.title
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(".group-section-title a").attributes("href")).toBe(
|
||||||
|
`/@${groupUsername}/p`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(".group-section-title").classes("privateSection")).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(".main-slot div").text()).toBe(defaultSlotText);
|
||||||
|
expect(wrapper.find(".create-slot a").text()).toBe(createSlotButtonText);
|
||||||
|
expect(wrapper.find(".create-slot a").attributes("href")).toBe(
|
||||||
|
`/@${groupUsername}/p/new`
|
||||||
|
);
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders public group section", () => {
|
||||||
|
const wrapper = generateWrapper({ privateSection: false });
|
||||||
|
|
||||||
|
expect(wrapper.find(".group-section-title").classes("privateSection")).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`GroupSection renders group section with basic informations 1`] = `
|
||||||
|
<section>
|
||||||
|
<div class="group-section-title privateSection">
|
||||||
|
<h2><span class="icon"><i class="mdi mdi-bullhorn mdi-24px"></i></span> <span>My group section</span></h2> <a href="/@my_group@remotedomain.net/p" class="">View all</a>
|
||||||
|
</div>
|
||||||
|
<div class="main-slot">
|
||||||
|
<div>A list of elements</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Post a public message</a></div>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`GroupSection renders public group section 1`] = `
|
||||||
|
<section>
|
||||||
|
<div class="group-section-title">
|
||||||
|
<h2><span class="icon"><i class="mdi mdi-bullhorn mdi-24px"></i></span> <span>My group section</span></h2> <a href="/@my_group@remotedomain.net/p" class="">View all</a>
|
||||||
|
</div>
|
||||||
|
<div class="main-slot">
|
||||||
|
<div>A list of elements</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Post a public message</a></div>
|
||||||
|
</section>
|
||||||
|
`;
|
5
vetur.config.js
Normal file
5
vetur.config.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// vetur.config.js
|
||||||
|
/** @type {import('vls').VeturConfig} */
|
||||||
|
module.exports = {
|
||||||
|
projects: ["./js"],
|
||||||
|
};
|
Loading…
Reference in a new issue