Merge branch 'group-improvement' into 'master'
Improvements to group page See merge request framasoft/mobilizon!561
This commit is contained in:
commit
ecff6b8ced
@ -13,7 +13,7 @@
|
|||||||
{{ actor.name || `@${usernameWithDomain(actor)}` }}
|
{{ actor.name || `@${usernameWithDomain(actor)}` }}
|
||||||
</p>
|
</p>
|
||||||
<p class="has-text-grey" v-if="actor.name">@{{ usernameWithDomain(actor) }}</p>
|
<p class="has-text-grey" v-if="actor.name">@{{ usernameWithDomain(actor) }}</p>
|
||||||
<p v-if="full" class="summary" :class="{ limit: limit }">{{ actor.summary }}</p>
|
<div v-if="full" class="summary" :class="{ limit: limit }" v-html="actor.summary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="!isBasicMode"
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
:class="{ 'is-active': isActive.heading({ level: 1 }) }"
|
:class="{ 'is-active': isActive.heading({ level: 1 }) }"
|
||||||
@click="commands.heading({ level: 1 })"
|
@click="commands.heading({ level: 1 })"
|
||||||
@ -49,6 +50,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="!isBasicMode"
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
:class="{ 'is-active': isActive.heading({ level: 2 }) }"
|
:class="{ 'is-active': isActive.heading({ level: 2 }) }"
|
||||||
@click="commands.heading({ level: 2 })"
|
@click="commands.heading({ level: 2 })"
|
||||||
@ -58,6 +60,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="!isBasicMode"
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
:class="{ 'is-active': isActive.heading({ level: 3 }) }"
|
:class="{ 'is-active': isActive.heading({ level: 3 }) }"
|
||||||
@click="commands.heading({ level: 3 })"
|
@click="commands.heading({ level: 3 })"
|
||||||
@ -75,12 +78,18 @@
|
|||||||
<b-icon icon="link" />
|
<b-icon icon="link" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="menubar__button" @click="showImagePrompt(commands.image)" type="button">
|
<button
|
||||||
|
class="menubar__button"
|
||||||
|
v-if="!isBasicMode"
|
||||||
|
@click="showImagePrompt(commands.image)"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
<b-icon icon="image" />
|
<b-icon icon="image" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
|
v-if="!isBasicMode"
|
||||||
:class="{ 'is-active': isActive.bullet_list() }"
|
:class="{ 'is-active': isActive.bullet_list() }"
|
||||||
@click="commands.bullet_list"
|
@click="commands.bullet_list"
|
||||||
type="button"
|
type="button"
|
||||||
@ -89,6 +98,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="!isBasicMode"
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
:class="{ 'is-active': isActive.ordered_list() }"
|
:class="{ 'is-active': isActive.ordered_list() }"
|
||||||
@click="commands.ordered_list"
|
@click="commands.ordered_list"
|
||||||
@ -98,6 +108,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="!isBasicMode"
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
:class="{ 'is-active': isActive.blockquote() }"
|
:class="{ 'is-active': isActive.blockquote() }"
|
||||||
@click="commands.blockquote"
|
@click="commands.blockquote"
|
||||||
@ -106,11 +117,11 @@
|
|||||||
<b-icon icon="format-quote-close" />
|
<b-icon icon="format-quote-close" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="menubar__button" @click="commands.undo" type="button">
|
<button v-if="!isBasicMode" class="menubar__button" @click="commands.undo" type="button">
|
||||||
<b-icon icon="undo" />
|
<b-icon icon="undo" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="menubar__button" @click="commands.redo" type="button">
|
<button v-if="!isBasicMode" class="menubar__button" @click="commands.redo" type="button">
|
||||||
<b-icon icon="redo" />
|
<b-icon icon="redo" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -229,26 +240,30 @@ export default class EditorComponent extends Vue {
|
|||||||
|
|
||||||
filteredActors: IActor[] = [];
|
filteredActors: IActor[] = [];
|
||||||
|
|
||||||
suggestionRange!: object | null;
|
suggestionRange!: Record<string, unknown> | null;
|
||||||
|
|
||||||
navigatedActorIndex = 0;
|
navigatedActorIndex = 0;
|
||||||
|
|
||||||
popup!: Instance[] | null;
|
popup!: Instance[] | null;
|
||||||
|
|
||||||
get isDescriptionMode() {
|
get isDescriptionMode(): boolean {
|
||||||
return this.mode === "description";
|
return this.mode === "description" || this.isBasicMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCommentMode() {
|
get isCommentMode(): boolean {
|
||||||
return this.mode === "comment";
|
return this.mode === "comment";
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasResults() {
|
get hasResults(): boolean {
|
||||||
return this.filteredActors.length;
|
return this.filteredActors.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showSuggestions() {
|
get showSuggestions(): boolean {
|
||||||
return this.query || this.hasResults;
|
return (this.query || this.hasResults) as boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isBasicMode(): boolean {
|
||||||
|
return this.mode === "basic";
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -258,7 +273,7 @@ export default class EditorComponent extends Vue {
|
|||||||
|
|
||||||
observer!: MutationObserver | null;
|
observer!: MutationObserver | null;
|
||||||
|
|
||||||
mounted() {
|
mounted(): void {
|
||||||
this.editor = new Editor({
|
this.editor = new Editor({
|
||||||
extensions: [
|
extensions: [
|
||||||
new Blockquote(),
|
new Blockquote(),
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<span>
|
<span>
|
||||||
{{ $t("Organized by {name}", { name: event.organizerActor.displayName() }) }}
|
{{ $t("Organized by {name}", { name: usernameWithDomain(event.organizerActor) }) }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +53,7 @@
|
|||||||
import { ParticipantRole, EventVisibility, IEventCardOptions, IEvent } from "@/types/event.model";
|
import { ParticipantRole, EventVisibility, IEventCardOptions, IEvent } from "@/types/event.model";
|
||||||
import { Component, Prop } from "vue-property-decorator";
|
import { Component, Prop } from "vue-property-decorator";
|
||||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||||
import { IPerson } from "@/types/actor";
|
import { IPerson, usernameWithDomain } from "@/types/actor";
|
||||||
import { mixins } from "vue-class-component";
|
import { mixins } from "vue-class-component";
|
||||||
import ActorMixin from "@/mixins/actor";
|
import ActorMixin from "@/mixins/actor";
|
||||||
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||||
@ -96,6 +96,8 @@ export default class EventListViewCard extends mixins(ActorMixin, EventMixin) {
|
|||||||
EventVisibility = EventVisibility;
|
EventVisibility = EventVisibility;
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
|
usernameWithDomain = usernameWithDomain;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -9,7 +9,46 @@
|
|||||||
<p v-if="event.physicalAddress" class="has-text-grey">
|
<p v-if="event.physicalAddress" class="has-text-grey">
|
||||||
{{ event.physicalAddress.description }}
|
{{ event.physicalAddress.description }}
|
||||||
</p>
|
</p>
|
||||||
<p v-else>3 demandes de participation à traiter</p>
|
<p v-else>
|
||||||
|
<span v-if="event.options.maximumAttendeeCapacity !== 0">
|
||||||
|
{{
|
||||||
|
$tc(
|
||||||
|
"{available}/{capacity} available places",
|
||||||
|
event.options.maximumAttendeeCapacity - event.participantStats.participant,
|
||||||
|
{
|
||||||
|
available:
|
||||||
|
event.options.maximumAttendeeCapacity - event.participantStats.participant,
|
||||||
|
capacity: event.options.maximumAttendeeCapacity,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{
|
||||||
|
$tc("{count} participants", event.participantStats.participant, {
|
||||||
|
count: event.participantStats.participant,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span v-if="event.participantStats.notApproved > 0">
|
||||||
|
<b-button
|
||||||
|
type="is-text"
|
||||||
|
@click="
|
||||||
|
gotToWithCheck(participation, {
|
||||||
|
name: RouteName.PARTICIPATIONS,
|
||||||
|
query: { role: ParticipantRole.NOT_APPROVED },
|
||||||
|
params: { eventId: event.uuid },
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$tc("{count} requests waiting", event.participantStats.notApproved, {
|
||||||
|
count: event.participantStats.notApproved,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</b-button>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
@ -78,12 +78,30 @@ export const GROUP_FIELDS_FRAGMENTS = gql`
|
|||||||
banner {
|
banner {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
organizedEvents {
|
organizedEvents(
|
||||||
|
afterDatetime: $afterDateTime
|
||||||
|
beforeDatetime: $beforeDateTime
|
||||||
|
page: $organisedEventsPage
|
||||||
|
limit: $organisedEventslimit
|
||||||
|
) {
|
||||||
elements {
|
elements {
|
||||||
id
|
id
|
||||||
uuid
|
uuid
|
||||||
title
|
title
|
||||||
beginsOn
|
beginsOn
|
||||||
|
options {
|
||||||
|
maximumAttendeeCapacity
|
||||||
|
}
|
||||||
|
participantStats {
|
||||||
|
participant
|
||||||
|
notApproved
|
||||||
|
}
|
||||||
|
organizerActor {
|
||||||
|
id
|
||||||
|
preferredUsername
|
||||||
|
name
|
||||||
|
domain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
@ -154,7 +172,13 @@ export const GROUP_FIELDS_FRAGMENTS = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const FETCH_GROUP = gql`
|
export const FETCH_GROUP = gql`
|
||||||
query($name: String!) {
|
query(
|
||||||
|
$name: String!
|
||||||
|
$afterDateTime: DateTime
|
||||||
|
$beforeDateTime: DateTime
|
||||||
|
$organisedEventsPage: Int
|
||||||
|
$organisedEventslimit: Int
|
||||||
|
) {
|
||||||
group(preferredUsername: $name) {
|
group(preferredUsername: $name) {
|
||||||
...GroupFullFields
|
...GroupFullFields
|
||||||
}
|
}
|
||||||
@ -166,7 +190,13 @@ export const FETCH_GROUP = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_GROUP = gql`
|
export const GET_GROUP = gql`
|
||||||
query($id: ID!) {
|
query(
|
||||||
|
$id: ID!
|
||||||
|
$afterDateTime: DateTime
|
||||||
|
$beforeDateTime: DateTime
|
||||||
|
$organisedEventsPage: Int
|
||||||
|
$organisedEventslimit: Int
|
||||||
|
) {
|
||||||
getGroup(id: $id) {
|
getGroup(id: $id) {
|
||||||
...GroupFullFields
|
...GroupFullFields
|
||||||
}
|
}
|
||||||
|
@ -34,14 +34,18 @@
|
|||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||||
<section v-if="group && group.organizedEvents.total > 0">
|
<section v-if="group">
|
||||||
<subtitle>
|
<subtitle>
|
||||||
{{ $t("Past events") }}
|
{{ showPassedEvents ? $t("Past events") : $t("Upcoming events") }}
|
||||||
</subtitle>
|
</subtitle>
|
||||||
|
<b-switch v-model="showPassedEvents">{{ $t("Past events") }}</b-switch>
|
||||||
<transition-group name="list" tag="p">
|
<transition-group name="list" tag="p">
|
||||||
<EventListViewCard v-for="event in group.organizedEvents.elements" :key="event.id" />
|
<EventListViewCard
|
||||||
|
v-for="event in group.organizedEvents.elements"
|
||||||
|
:key="event.id"
|
||||||
|
:event="event"
|
||||||
|
/>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</section>
|
|
||||||
<b-message
|
<b-message
|
||||||
v-if="group.organizedEvents.elements.length === 0 && $apollo.loading === false"
|
v-if="group.organizedEvents.elements.length === 0 && $apollo.loading === false"
|
||||||
type="is-danger"
|
type="is-danger"
|
||||||
@ -49,12 +53,15 @@
|
|||||||
{{ $t("No events found") }}
|
{{ $t("No events found") }}
|
||||||
</b-message>
|
</b-message>
|
||||||
</section>
|
</section>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import { FETCH_GROUP } from "@/graphql/group";
|
import { FETCH_GROUP } from "@/graphql/group";
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
|
import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||||
|
import EventListViewCard from "@/components/Event/EventListViewCard.vue";
|
||||||
import { IGroup, usernameWithDomain } from "../../types/actor";
|
import { IGroup, usernameWithDomain } from "../../types/actor";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -64,10 +71,16 @@ import { IGroup, usernameWithDomain } from "../../types/actor";
|
|||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
name: this.$route.params.preferredUsername,
|
name: this.$route.params.preferredUsername,
|
||||||
|
beforeDateTime: this.showPassedEvents ? new Date() : null,
|
||||||
|
afterDateTime: this.showPassedEvents ? null : new Date(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Subtitle,
|
||||||
|
EventListViewCard,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class GroupEvents extends Vue {
|
export default class GroupEvents extends Vue {
|
||||||
group!: IGroup;
|
group!: IGroup;
|
||||||
@ -75,5 +88,7 @@ export default class GroupEvents extends Vue {
|
|||||||
usernameWithDomain = usernameWithDomain;
|
usernameWithDomain = usernameWithDomain;
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
|
showPassedEvents = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -302,6 +302,10 @@
|
|||||||
{{ $t("No group found") }}
|
{{ $t("No group found") }}
|
||||||
</b-message>
|
</b-message>
|
||||||
<div v-else class="public-container">
|
<div v-else class="public-container">
|
||||||
|
<section>
|
||||||
|
<subtitle>{{ $t("About") }}</subtitle>
|
||||||
|
<div v-html="group.summary" />
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<subtitle>{{ $t("Upcoming events") }}</subtitle>
|
<subtitle>{{ $t("Upcoming events") }}</subtitle>
|
||||||
<div class="organized-events-wrapper" v-if="group && group.organizedEvents.total > 0">
|
<div class="organized-events-wrapper" v-if="group && group.organizedEvents.total > 0">
|
||||||
@ -318,16 +322,12 @@
|
|||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<subtitle>{{ $t("Latest posts") }}</subtitle>
|
<subtitle>{{ $t("Latest posts") }}</subtitle>
|
||||||
<div v-if="group && group.posts.total > 0">
|
<div v-if="group.posts.total > 0" class="posts-wrapper">
|
||||||
<router-link
|
<post-list-item v-for="post in group.posts.elements" :key="post.id" :post="post" />
|
||||||
v-for="post in group.posts.elements"
|
</div>
|
||||||
:key="post.id"
|
<div v-else-if="group" class="content has-text-grey has-text-centered">
|
||||||
:to="{ name: RouteName.POST, params: { slug: post.slug } }"
|
<p>{{ $t("No posts yet") }}</p>
|
||||||
>
|
|
||||||
{{ post.title }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
<span v-else-if="group">{{ $t("No public posts") }}</span>
|
|
||||||
<b-skeleton animated v-else></b-skeleton>
|
<b-skeleton animated v-else></b-skeleton>
|
||||||
</section>
|
</section>
|
||||||
<b-modal v-if="physicalAddress && physicalAddress.geom" :active.sync="showMap">
|
<b-modal v-if="physicalAddress && physicalAddress.geom" :active.sync="showMap">
|
||||||
@ -369,6 +369,7 @@ import FolderItem from "@/components/Resource/FolderItem.vue";
|
|||||||
import { Address } from "@/types/address.model";
|
import { Address } from "@/types/address.model";
|
||||||
import Invitations from "@/components/Group/Invitations.vue";
|
import Invitations from "@/components/Group/Invitations.vue";
|
||||||
import addMinutes from "date-fns/addMinutes";
|
import addMinutes from "date-fns/addMinutes";
|
||||||
|
import { Route } from "vue-router";
|
||||||
import GroupSection from "../../components/Group/GroupSection.vue";
|
import GroupSection from "../../components/Group/GroupSection.vue";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
|
|
||||||
@ -413,11 +414,13 @@ import RouteName from "../../router/name";
|
|||||||
metaInfo() {
|
metaInfo() {
|
||||||
return {
|
return {
|
||||||
// if no subcomponents specify a metaInfo.title, this title will be used
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
title: this.groupTitle,
|
title: this.groupTitle,
|
||||||
// all titles will be injected into this template
|
// all titles will be injected into this template
|
||||||
titleTemplate: "%s | Mobilizon",
|
titleTemplate: "%s | Mobilizon",
|
||||||
meta: [
|
meta: [
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
{ name: "description", content: this.groupSummary },
|
{ name: "description", content: this.groupSummary },
|
||||||
],
|
],
|
||||||
@ -442,14 +445,14 @@ export default class Group extends Vue {
|
|||||||
showMap = false;
|
showMap = false;
|
||||||
|
|
||||||
@Watch("currentActor")
|
@Watch("currentActor")
|
||||||
watchCurrentActor(currentActor: IActor, oldActor: IActor) {
|
watchCurrentActor(currentActor: IActor, oldActor: IActor): void {
|
||||||
if (currentActor.id && oldActor && currentActor.id !== oldActor.id) {
|
if (currentActor.id && oldActor && currentActor.id !== oldActor.id) {
|
||||||
this.$apollo.queries.group.refetch();
|
this.$apollo.queries.group.refetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async leaveGroup() {
|
async leaveGroup(): Promise<Route> {
|
||||||
const { data } = await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: LEAVE_GROUP,
|
mutation: LEAVE_GROUP,
|
||||||
variables: {
|
variables: {
|
||||||
groupId: this.group.id,
|
groupId: this.group.id,
|
||||||
@ -458,9 +461,10 @@ export default class Group extends Vue {
|
|||||||
return this.$router.push({ name: RouteName.MY_GROUPS });
|
return this.$router.push({ name: RouteName.MY_GROUPS });
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptInvitation() {
|
acceptInvitation(): void {
|
||||||
if (this.groupMember) {
|
if (this.groupMember) {
|
||||||
const index = this.person.memberships.elements.findIndex(
|
const index = this.person.memberships.elements.findIndex(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
({ id }: IMember) => id === this.groupMember.id
|
({ id }: IMember) => id === this.groupMember.id
|
||||||
);
|
);
|
||||||
@ -471,12 +475,12 @@ export default class Group extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get groupTitle() {
|
get groupTitle(): undefined | string {
|
||||||
if (!this.group) return undefined;
|
if (!this.group) return undefined;
|
||||||
return this.group.preferredUsername;
|
return this.group.preferredUsername;
|
||||||
}
|
}
|
||||||
|
|
||||||
get groupSummary() {
|
get groupSummary(): undefined | string {
|
||||||
if (!this.group) return undefined;
|
if (!this.group) return undefined;
|
||||||
return this.group.summary;
|
return this.group.summary;
|
||||||
}
|
}
|
||||||
@ -486,8 +490,8 @@ export default class Group extends Vue {
|
|||||||
return this.person.memberships.elements.find(({ parent: { id } }) => id === this.group.id);
|
return this.person.memberships.elements.find(({ parent: { id } }) => id === this.group.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get groupMemberships() {
|
get groupMemberships(): (string | undefined)[] {
|
||||||
if (!this.person || !this.person.id) return undefined;
|
if (!this.person || !this.person.id) return [];
|
||||||
return this.person.memberships.elements
|
return this.person.memberships.elements
|
||||||
.filter(
|
.filter(
|
||||||
(membership: IMember) =>
|
(membership: IMember) =>
|
||||||
@ -499,7 +503,7 @@ export default class Group extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isCurrentActorAGroupMember(): boolean {
|
get isCurrentActorAGroupMember(): boolean {
|
||||||
return this.groupMemberships != undefined && this.groupMemberships.includes(this.group.id);
|
return this.groupMemberships !== undefined && this.groupMemberships.includes(this.group.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCurrentActorARejectedGroupMember(): boolean {
|
get isCurrentActorARejectedGroupMember(): boolean {
|
||||||
@ -532,7 +536,8 @@ export default class Group extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New members, if on a different server, can take a while to refresh the group and fetch all private data
|
* New members, if on a different server,
|
||||||
|
* can take a while to refresh the group and fetch all private data
|
||||||
*/
|
*/
|
||||||
get isCurrentActorARecentMember(): boolean {
|
get isCurrentActorARecentMember(): boolean {
|
||||||
return (
|
return (
|
||||||
@ -673,5 +678,11 @@ div.container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-container {
|
||||||
|
section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<b-input v-model="group.name" />
|
<b-input v-model="group.name" />
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field :label="$t('Group short description')">
|
<b-field :label="$t('Group short description')">
|
||||||
<b-input type="textarea" v-model="group.summary"
|
<editor mode="basic" v-model="group.summary"
|
||||||
/></b-field>
|
/></b-field>
|
||||||
<p class="label">{{ $t("Group visibility") }}</p>
|
<p class="label">{{ $t("Group visibility") }}</p>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -105,12 +105,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
||||||
|
import { Route } from "vue-router";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import { FETCH_GROUP, UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
|
import { FETCH_GROUP, UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
|
||||||
import { IGroup, usernameWithDomain } from "../../types/actor";
|
import { IGroup, usernameWithDomain } from "../../types/actor";
|
||||||
import { Address, IAddress } from "../../types/address.model";
|
import { Address, IAddress } from "../../types/address.model";
|
||||||
import { IMember, Group } from "../../types/actor/group.model";
|
import { Group } from "../../types/actor/group.model";
|
||||||
import { Paginate } from "../../types/paginate";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
apollo: {
|
apollo: {
|
||||||
@ -129,6 +129,7 @@ import { Paginate } from "../../types/paginate";
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
FullAddressAutoComplete,
|
FullAddressAutoComplete,
|
||||||
|
editor: () => import("../../components/Editor.vue"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class GroupSettings extends Vue {
|
export default class GroupSettings extends Vue {
|
||||||
@ -149,7 +150,7 @@ export default class GroupSettings extends Vue {
|
|||||||
|
|
||||||
showCopiedTooltip = false;
|
showCopiedTooltip = false;
|
||||||
|
|
||||||
async updateGroup() {
|
async updateGroup(): Promise<void> {
|
||||||
const variables = { ...this.group };
|
const variables = { ...this.group };
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -165,7 +166,7 @@ export default class GroupSettings extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmDeleteGroup() {
|
confirmDeleteGroup(): void {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
title: this.$t("Delete group") as string,
|
title: this.$t("Delete group") as string,
|
||||||
message: this.$t(
|
message: this.$t(
|
||||||
@ -179,7 +180,7 @@ export default class GroupSettings extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteGroup() {
|
async deleteGroup(): Promise<Route> {
|
||||||
await this.$apollo.mutate<{ deleteGroup: IGroup }>({
|
await this.$apollo.mutate<{ deleteGroup: IGroup }>({
|
||||||
mutation: DELETE_GROUP,
|
mutation: DELETE_GROUP,
|
||||||
variables: {
|
variables: {
|
||||||
@ -189,7 +190,7 @@ export default class GroupSettings extends Vue {
|
|||||||
return this.$router.push({ name: RouteName.MY_GROUPS });
|
return this.$router.push({ name: RouteName.MY_GROUPS });
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyURL() {
|
async copyURL(): Promise<void> {
|
||||||
await window.navigator.clipboard.writeText(this.group.url);
|
await window.navigator.clipboard.writeText(this.group.url);
|
||||||
this.showCopiedTooltip = true;
|
this.showCopiedTooltip = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -197,6 +198,7 @@ export default class GroupSettings extends Vue {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
get canShowCopyButton(): boolean {
|
get canShowCopyButton(): boolean {
|
||||||
return window.isSecureContext;
|
return window.isSecureContext;
|
||||||
}
|
}
|
||||||
|
@ -89,12 +89,27 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||||||
def group_actor(%Actor{} = actor), do: actor
|
def group_actor(%Actor{} = actor), do: actor
|
||||||
|
|
||||||
defp prepare_args_for_actor(args) do
|
defp prepare_args_for_actor(args) do
|
||||||
with preferred_username <-
|
args
|
||||||
args |> Map.get(:preferred_username) |> HTML.strip_tags() |> String.trim(),
|
|> maybe_sanitize_username()
|
||||||
summary <- args |> Map.get(:summary, "") |> String.trim(),
|
|> maybe_sanitize_summary()
|
||||||
{summary, _mentions, _tags} <-
|
|
||||||
summary |> String.trim() |> APIUtils.make_content_html([], "text/html") do
|
|
||||||
%{args | preferred_username: preferred_username, summary: summary}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec maybe_sanitize_username(map()) :: map()
|
||||||
|
defp maybe_sanitize_username(%{preferred_username: preferred_username} = args) do
|
||||||
|
Map.put(args, :preferred_username, preferred_username |> HTML.strip_tags() |> String.trim())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_sanitize_username(args), do: args
|
||||||
|
|
||||||
|
@spec maybe_sanitize_summary(map()) :: map()
|
||||||
|
defp maybe_sanitize_summary(%{summary: summary} = args) do
|
||||||
|
{summary, _mentions, _tags} =
|
||||||
|
summary
|
||||||
|
|> String.trim()
|
||||||
|
|> APIUtils.make_content_html([], "text/html")
|
||||||
|
|
||||||
|
Map.put(args, :summary, summary)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_sanitize_summary(args), do: args
|
||||||
end
|
end
|
||||||
|
@ -9,7 +9,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
|||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.GraphQL.API
|
alias Mobilizon.GraphQL.API
|
||||||
alias Mobilizon.GraphQL.Resolvers.Person
|
alias Mobilizon.GraphQL.Resolvers.Person
|
||||||
alias Mobilizon.Storage.Page
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
@ -271,7 +270,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
|||||||
|
|
||||||
def find_events_for_group(
|
def find_events_for_group(
|
||||||
%Actor{id: group_id} = group,
|
%Actor{id: group_id} = group,
|
||||||
_args,
|
%{
|
||||||
|
page: page,
|
||||||
|
limit: limit
|
||||||
|
} = args,
|
||||||
%{
|
%{
|
||||||
context: %{
|
context: %{
|
||||||
current_user: %User{role: user_role} = user
|
current_user: %User{role: user_role} = user
|
||||||
@ -282,15 +284,38 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
|||||||
{:member, true} <-
|
{:member, true} <-
|
||||||
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
|
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
|
||||||
# TODO : Handle public / restricted to group members events
|
# TODO : Handle public / restricted to group members events
|
||||||
{:ok, Events.list_organized_events_for_group(group)}
|
{:ok,
|
||||||
|
Events.list_organized_events_for_group(
|
||||||
|
group,
|
||||||
|
:all,
|
||||||
|
Map.get(args, :after_datetime),
|
||||||
|
Map.get(args, :before_datetime),
|
||||||
|
page,
|
||||||
|
limit
|
||||||
|
)}
|
||||||
else
|
else
|
||||||
{:member, false} ->
|
{:member, false} ->
|
||||||
{:ok, %Page{total: 0, elements: []}}
|
find_events_for_group(group, args, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_events_for_group(_parent, _args, _resolution) do
|
def find_events_for_group(
|
||||||
{:ok, %Page{total: 0, elements: []}}
|
%Actor{} = group,
|
||||||
|
%{
|
||||||
|
page: page,
|
||||||
|
limit: limit
|
||||||
|
} = args,
|
||||||
|
_resolution
|
||||||
|
) do
|
||||||
|
{:ok,
|
||||||
|
Events.list_organized_events_for_group(
|
||||||
|
group,
|
||||||
|
:public,
|
||||||
|
Map.get(args, :after_datetime),
|
||||||
|
Map.get(args, :before_datetime),
|
||||||
|
page,
|
||||||
|
limit
|
||||||
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_fields_for_non_member_request(%Actor{} = group) do
|
defp restrict_fields_for_non_member_request(%Actor{} = group) do
|
||||||
|
@ -54,6 +54,10 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||||||
|
|
||||||
# This one should have a privacy setting
|
# This one should have a privacy setting
|
||||||
field :organized_events, :paginated_event_list do
|
field :organized_events, :paginated_event_list do
|
||||||
|
arg(:after_datetime, :datetime, default_value: nil)
|
||||||
|
arg(:before_datetime, :datetime, default_value: nil)
|
||||||
|
arg(:page, :integer, default_value: 1)
|
||||||
|
arg(:limit, :integer, default_value: 10)
|
||||||
resolve(&Group.find_events_for_group/3)
|
resolve(&Group.find_events_for_group/3)
|
||||||
description("A list of the events this actor has organized")
|
description("A list of the events this actor has organized")
|
||||||
end
|
end
|
||||||
|
@ -179,8 +179,8 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
@doc """
|
@doc """
|
||||||
Checks whether actor visibility is public.
|
Checks whether actor visibility is public.
|
||||||
"""
|
"""
|
||||||
@spec is_public_visibility(t) :: boolean
|
@spec is_public_visibility?(t) :: boolean
|
||||||
def is_public_visibility(%__MODULE__{visibility: visibility}) do
|
def is_public_visibility?(%__MODULE__{visibility: visibility}) do
|
||||||
visibility in [:public, :unlisted]
|
visibility in [:public, :unlisted]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -405,7 +405,7 @@ defmodule Mobilizon.Events do
|
|||||||
def list_public_events_for_actor(actor, page \\ nil, limit \\ nil)
|
def list_public_events_for_actor(actor, page \\ nil, limit \\ nil)
|
||||||
|
|
||||||
def list_public_events_for_actor(%Actor{type: :Group} = group, page, limit),
|
def list_public_events_for_actor(%Actor{type: :Group} = group, page, limit),
|
||||||
do: list_organized_events_for_group(group, page, limit)
|
do: list_organized_events_for_group(group, :public, nil, page, limit)
|
||||||
|
|
||||||
def list_public_events_for_actor(%Actor{id: actor_id}, page, limit) do
|
def list_public_events_for_actor(%Actor{id: actor_id}, page, limit) do
|
||||||
actor_id
|
actor_id
|
||||||
@ -424,10 +424,25 @@ defmodule Mobilizon.Events do
|
|||||||
|> Page.build_page(page, limit)
|
|> Page.build_page(page, limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec list_organized_events_for_group(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
@spec list_organized_events_for_group(
|
||||||
def list_organized_events_for_group(%Actor{id: group_id}, page \\ nil, limit \\ nil) do
|
Actor.t(),
|
||||||
|
DateTime.t() | nil,
|
||||||
|
DateTime.t() | nil,
|
||||||
|
integer | nil,
|
||||||
|
integer | nil
|
||||||
|
) :: Page.t()
|
||||||
|
def list_organized_events_for_group(
|
||||||
|
%Actor{id: group_id},
|
||||||
|
visibility \\ :public,
|
||||||
|
after_datetime \\ nil,
|
||||||
|
before_datetime \\ nil,
|
||||||
|
page \\ nil,
|
||||||
|
limit \\ nil
|
||||||
|
) do
|
||||||
group_id
|
group_id
|
||||||
|> event_for_group_query()
|
|> event_for_group_query()
|
||||||
|
|> event_filter_visibility(visibility)
|
||||||
|
|> event_filter_begins_on(after_datetime, before_datetime)
|
||||||
|> preload_for_event()
|
|> preload_for_event()
|
||||||
|> Page.build_page(page, limit)
|
|> Page.build_page(page, limit)
|
||||||
end
|
end
|
||||||
@ -1643,6 +1658,45 @@ defmodule Mobilizon.Events do
|
|||||||
from(p in query, where: p.role == ^role)
|
from(p in query, where: p.role == ^role)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp event_filter_visibility(query, :all), do: query
|
||||||
|
|
||||||
|
defp event_filter_visibility(query, :public) do
|
||||||
|
query
|
||||||
|
|> where(visibility: ^:public)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp event_filter_begins_on(query, nil, nil),
|
||||||
|
do: event_order_begins_on_desc(query)
|
||||||
|
|
||||||
|
defp event_filter_begins_on(query, %DateTime{} = after_datetime, nil) do
|
||||||
|
query
|
||||||
|
|> where([e], e.begins_on > ^after_datetime)
|
||||||
|
|> event_order_begins_on_asc()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp event_filter_begins_on(query, nil, %DateTime{} = before_datetime) do
|
||||||
|
query
|
||||||
|
|> where([e], e.begins_on < ^before_datetime)
|
||||||
|
|> event_order_begins_on_desc()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp event_filter_begins_on(
|
||||||
|
query,
|
||||||
|
%DateTime{} = after_datetime,
|
||||||
|
%DateTime{} = before_datetime
|
||||||
|
) do
|
||||||
|
query
|
||||||
|
|> where([e], e.begins_on < ^before_datetime)
|
||||||
|
|> where([e], e.begins_on > ^after_datetime)
|
||||||
|
|> event_order_begins_on_asc()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp event_order_begins_on_asc(query),
|
||||||
|
do: order_by(query, [e], asc: e.begins_on)
|
||||||
|
|
||||||
|
defp event_order_begins_on_desc(query),
|
||||||
|
do: order_by(query, [e], desc: e.begins_on)
|
||||||
|
|
||||||
defp participation_filter_begins_on(query, nil, nil),
|
defp participation_filter_begins_on(query, nil, nil),
|
||||||
do: participation_order_begins_on_desc(query)
|
do: participation_order_begins_on_desc(query)
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ defmodule Mobilizon.Service.Export.Feed do
|
|||||||
@spec fetch_actor_event_feed(String.t()) :: String.t()
|
@spec fetch_actor_event_feed(String.t()) :: String.t()
|
||||||
defp fetch_actor_event_feed(name) do
|
defp fetch_actor_event_feed(name) do
|
||||||
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
|
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
|
||||||
{:visibility, true} <- {:visibility, Actor.is_public_visibility(actor)},
|
{:visibility, true} <- {:visibility, Actor.is_public_visibility?(actor)},
|
||||||
%Page{elements: events} <- Events.list_public_events_for_actor(actor) do
|
%Page{elements: events} <- Events.list_public_events_for_actor(actor) do
|
||||||
{:ok, build_actor_feed(actor, events)}
|
{:ok, build_actor_feed(actor, events)}
|
||||||
else
|
else
|
||||||
|
@ -48,7 +48,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
|||||||
"""
|
"""
|
||||||
@spec export_public_actor(Actor.t()) :: String.t()
|
@spec export_public_actor(Actor.t()) :: String.t()
|
||||||
def export_public_actor(%Actor{} = actor) do
|
def export_public_actor(%Actor{} = actor) do
|
||||||
with true <- Actor.is_public_visibility(actor),
|
with {:visibility, true} <- {:visibility, Actor.is_public_visibility?(actor)},
|
||||||
%Page{elements: events} <-
|
%Page{elements: events} <-
|
||||||
Events.list_public_events_for_actor(actor) do
|
Events.list_public_events_for_actor(actor) do
|
||||||
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
||||||
|
@ -25,7 +25,7 @@ defmodule Mobilizon.Web.FeedController do
|
|||||||
|> put_resp_content_type("text/calendar")
|
|> put_resp_content_type("text/calendar")
|
||||||
|> send_resp(200, data)
|
|> send_resp(200, data)
|
||||||
|
|
||||||
_ ->
|
_err ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,8 +5,8 @@ defmodule Mobilizon.Web.JsonLD.ObjectView do
|
|||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Posts.Post
|
alias Mobilizon.Posts.Post
|
||||||
|
alias Mobilizon.Web.{Endpoint, MediaProxy}
|
||||||
alias Mobilizon.Web.JsonLD.ObjectView
|
alias Mobilizon.Web.JsonLD.ObjectView
|
||||||
alias Mobilizon.Web.MediaProxy
|
|
||||||
|
|
||||||
def render("group.json", %{group: %Actor{} = group}) do
|
def render("group.json", %{group: %Actor{} = group}) do
|
||||||
%{
|
%{
|
||||||
@ -37,18 +37,16 @@ defmodule Mobilizon.Web.JsonLD.ObjectView do
|
|||||||
if(event.status == :cancelled,
|
if(event.status == :cancelled,
|
||||||
do: "https://schema.org/EventCancelled",
|
do: "https://schema.org/EventCancelled",
|
||||||
else: "https://schema.org/EventScheduled"
|
else: "https://schema.org/EventScheduled"
|
||||||
|
),
|
||||||
|
"image" =>
|
||||||
|
if(event.picture,
|
||||||
|
do: [
|
||||||
|
event.picture.file.url |> MediaProxy.url()
|
||||||
|
],
|
||||||
|
else: ["#{Endpoint.url()}/img/mobilizon_default_card.png"]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
json_ld =
|
|
||||||
if event.picture do
|
|
||||||
Map.put(json_ld, "image", [
|
|
||||||
event.picture.file.url |> MediaProxy.url()
|
|
||||||
])
|
|
||||||
else
|
|
||||||
json_ld
|
|
||||||
end
|
|
||||||
|
|
||||||
json_ld =
|
json_ld =
|
||||||
if event.begins_on,
|
if event.begins_on,
|
||||||
do: Map.put(json_ld, "startDate", DateTime.to_iso8601(event.begins_on)),
|
do: Map.put(json_ld, "startDate", DateTime.to_iso8601(event.begins_on)),
|
||||||
|
@ -53,7 +53,8 @@ defmodule Mobilizon.Factory do
|
|||||||
outbox_url: Actor.build_url(preferred_username, :outbox),
|
outbox_url: Actor.build_url(preferred_username, :outbox),
|
||||||
shared_inbox_url: "#{Endpoint.url()}/inbox",
|
shared_inbox_url: "#{Endpoint.url()}/inbox",
|
||||||
last_refreshed_at: DateTime.utc_now(),
|
last_refreshed_at: DateTime.utc_now(),
|
||||||
user: build(:user)
|
user: build(:user),
|
||||||
|
visibility: :public
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ defmodule Mobilizon.Web.FeedControllerTest do
|
|||||||
|
|
||||||
test "it returns a 404 for the actor's public events Atom feed if the actor is not publicly visible",
|
test "it returns a 404 for the actor's public events Atom feed if the actor is not publicly visible",
|
||||||
%{conn: conn} do
|
%{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor, visibility: :private)
|
||||||
tag1 = insert(:tag, title: "RSS", slug: "rss")
|
tag1 = insert(:tag, title: "RSS", slug: "rss")
|
||||||
tag2 = insert(:tag, title: "ATOM", slug: "atom")
|
tag2 = insert(:tag, title: "ATOM", slug: "atom")
|
||||||
insert(:event, organizer_actor: actor, tags: [tag1])
|
insert(:event, organizer_actor: actor, tags: [tag1])
|
||||||
|
Loading…
Reference in New Issue
Block a user