Merge branch 'event-from-group' into 'master'
Allow to create an event from a group preconfigured with the organizer Closes #464 See merge request framasoft/mobilizon!877
This commit is contained in:
commit
0cccc9dd9c
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="list is-hoverable">
|
<div class="list is-hoverable">
|
||||||
<b-radio-button
|
<b-radio-button
|
||||||
v-model="currentActor"
|
v-model="selectedActor"
|
||||||
:native-value="availableActor"
|
:native-value="availableActor"
|
||||||
class="list-item"
|
class="list-item"
|
||||||
v-for="availableActor in actualAvailableActors"
|
v-for="availableActor in actualAvailableActors"
|
||||||
:class="{ 'is-active': availableActor.id === currentActor.id }"
|
:class="{ 'is-active': availableActor.id === selectedActor.id }"
|
||||||
:key="availableActor.id"
|
:key="availableActor.id"
|
||||||
>
|
>
|
||||||
<div class="media">
|
<div class="media">
|
||||||
|
@ -31,9 +31,13 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
import { IPerson, IActor, Actor } from "@/types/actor";
|
import { IPerson, IActor, Actor } from "@/types/actor";
|
||||||
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
import {
|
||||||
|
CURRENT_ACTOR_CLIENT,
|
||||||
|
IDENTITIES,
|
||||||
|
LOGGED_USER_MEMBERSHIPS,
|
||||||
|
} from "@/graphql/actor";
|
||||||
import { Paginate } from "@/types/paginate";
|
import { Paginate } from "@/types/paginate";
|
||||||
import { IMember } from "@/types/actor/member.model";
|
import { IMember } from "@/types/actor/member.model";
|
||||||
import { MemberRole } from "@/types/enums";
|
import { MemberRole } from "@/types/enums";
|
||||||
|
@ -41,29 +45,37 @@ import { MemberRole } from "@/types/enums";
|
||||||
@Component({
|
@Component({
|
||||||
apollo: {
|
apollo: {
|
||||||
groupMemberships: {
|
groupMemberships: {
|
||||||
query: PERSON_MEMBERSHIPS,
|
query: LOGGED_USER_MEMBERSHIPS,
|
||||||
variables() {
|
update: (data) => data.loggedUser.memberships,
|
||||||
return {
|
|
||||||
id: this.identity.id,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
update: (data) => data.person.memberships,
|
|
||||||
skip() {
|
|
||||||
return !this.identity.id;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
identities: IDENTITIES,
|
||||||
|
currentActor: CURRENT_ACTOR_CLIENT,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class OrganizerPicker extends Vue {
|
export default class OrganizerPicker extends Vue {
|
||||||
@Prop() value!: IActor;
|
@Prop() value!: IActor;
|
||||||
|
|
||||||
@Prop() identity!: IPerson;
|
|
||||||
|
|
||||||
@Prop({ required: false, default: false }) restrictModeratorLevel!: boolean;
|
@Prop({ required: false, default: false }) restrictModeratorLevel!: boolean;
|
||||||
|
|
||||||
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
|
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
|
||||||
|
|
||||||
currentActor: IActor = this.value;
|
currentActor!: IPerson;
|
||||||
|
|
||||||
|
get selectedActor(): IActor | undefined {
|
||||||
|
if (this.value?.id) {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
if (this.currentActor) {
|
||||||
|
return this.currentActor;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set selectedActor(actor: IActor | undefined) {
|
||||||
|
this.$emit("input", actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
identities: IActor[] = [];
|
||||||
|
|
||||||
Actor = Actor;
|
Actor = Actor;
|
||||||
|
|
||||||
|
@ -82,14 +94,12 @@ export default class OrganizerPicker extends Vue {
|
||||||
|
|
||||||
get actualAvailableActors(): IActor[] {
|
get actualAvailableActors(): IActor[] {
|
||||||
return [
|
return [
|
||||||
this.identity,
|
this.currentActor,
|
||||||
|
...this.identities.filter(
|
||||||
|
(identity: IActor) => identity.id !== this.currentActor?.id
|
||||||
|
),
|
||||||
...this.actualMemberships.map((member) => member.parent),
|
...this.actualMemberships.map((member) => member.parent),
|
||||||
];
|
].filter((elem) => elem);
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("currentActor")
|
|
||||||
async fetchMembersForGroup(): Promise<void> {
|
|
||||||
this.$emit("input", this.currentActor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="organizer-picker">
|
<div class="organizer-picker" v-if="selectedActor">
|
||||||
<!-- If we have a current actor (inline) -->
|
<!-- If we have a current actor (inline) -->
|
||||||
<div
|
<div
|
||||||
v-if="inline && currentActor.id"
|
v-if="inline && selectedActor.id"
|
||||||
class="inline box"
|
class="inline box"
|
||||||
@click="isComponentModalActive = true"
|
@click="isComponentModalActive = true"
|
||||||
>
|
>
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<div class="media-left">
|
<div class="media-left">
|
||||||
<figure class="image is-48x48" v-if="currentActor.avatar">
|
<figure class="image is-48x48" v-if="selectedActor.avatar">
|
||||||
<img
|
<img
|
||||||
class="image is-rounded"
|
class="image is-rounded"
|
||||||
:src="currentActor.avatar.url"
|
:src="selectedActor.avatar.url"
|
||||||
:alt="currentActor.avatar.alt"
|
:alt="selectedActor.avatar.alt"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<b-icon v-else size="is-large" icon="account-circle" />
|
<b-icon v-else size="is-large" icon="account-circle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content" v-if="currentActor.name">
|
<div class="media-content" v-if="selectedActor.name">
|
||||||
<p class="is-4">{{ currentActor.name }}</p>
|
<p class="is-4">{{ selectedActor.name }}</p>
|
||||||
<p class="is-6 has-text-grey">
|
<p class="is-6 has-text-grey">
|
||||||
{{ `@${currentActor.preferredUsername}` }}
|
{{ `@${selectedActor.preferredUsername}` }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content" v-else>
|
<div class="media-content" v-else>
|
||||||
{{ `@${currentActor.preferredUsername}` }}
|
{{ `@${selectedActor.preferredUsername}` }}
|
||||||
</div>
|
</div>
|
||||||
<b-button type="is-text" @click="isComponentModalActive = true">
|
<b-button type="is-text" @click="isComponentModalActive = true">
|
||||||
{{ $t("Change") }}
|
{{ $t("Change") }}
|
||||||
|
@ -33,45 +33,18 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- If we have a current actor -->
|
<!-- If we have a current actor -->
|
||||||
<span
|
<span
|
||||||
v-else-if="currentActor.id"
|
v-else-if="selectedActor.id"
|
||||||
class="block"
|
class="block"
|
||||||
@click="isComponentModalActive = true"
|
@click="isComponentModalActive = true"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="image is-48x48"
|
class="image is-48x48"
|
||||||
v-if="currentActor.avatar"
|
v-if="selectedActor.avatar"
|
||||||
:src="currentActor.avatar.url"
|
:src="selectedActor.avatar.url"
|
||||||
:alt="currentActor.avatar.alt"
|
:alt="selectedActor.avatar.alt"
|
||||||
/>
|
/>
|
||||||
<b-icon v-else size="is-large" icon="account-circle" />
|
<b-icon v-else size="is-large" icon="account-circle" />
|
||||||
</span>
|
</span>
|
||||||
<!-- If we have no current actor -->
|
|
||||||
<div v-if="groupMemberships.total === 0 || !currentActor.id" class="box">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-left">
|
|
||||||
<figure class="image is-48x48" v-if="identity.avatar">
|
|
||||||
<img
|
|
||||||
class="image is-rounded"
|
|
||||||
:src="identity.avatar.url"
|
|
||||||
:alt="identity.avatar.alt"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
<b-icon v-else size="is-large" icon="account-circle" />
|
|
||||||
</div>
|
|
||||||
<div class="media-content" v-if="identity.name">
|
|
||||||
<p class="is-4">{{ identity.name }}</p>
|
|
||||||
<p class="is-6 has-text-grey">
|
|
||||||
{{ `@${identity.preferredUsername}` }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="media-content" v-else>
|
|
||||||
{{ `@${identity.preferredUsername}` }}
|
|
||||||
</div>
|
|
||||||
<b-button type="is-text" @click="isComponentModalActive = true">
|
|
||||||
{{ $t("Change") }}
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<b-modal :active.sync="isComponentModalActive" has-modal-card>
|
<b-modal :active.sync="isComponentModalActive" has-modal-card>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
|
@ -81,20 +54,15 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<organizer-picker
|
<organizer-picker
|
||||||
v-model="currentActor"
|
v-model="selectedActor"
|
||||||
:identity.sync="identity"
|
|
||||||
@input="relay"
|
@input="relay"
|
||||||
:restrict-moderator-level="true"
|
:restrict-moderator-level="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div v-if="actorMembersForCurrentActor.length > 0">
|
<div v-if="actorMembers.length > 0">
|
||||||
<p>{{ $t("Add a contact") }}</p>
|
<p>{{ $t("Add a contact") }}</p>
|
||||||
<p
|
<p class="field" v-for="actor in actorMembers" :key="actor.id">
|
||||||
class="field"
|
|
||||||
v-for="actor in actorMembersForCurrentActor"
|
|
||||||
:key="actor.id"
|
|
||||||
>
|
|
||||||
<b-checkbox v-model="actualContacts" :native-value="actor.id">
|
<b-checkbox v-model="actualContacts" :native-value="actor.id">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<div class="media-left">
|
<div class="media-left">
|
||||||
|
@ -138,79 +106,121 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||||
import { IMember } from "@/types/actor/member.model";
|
import { IMember } from "@/types/actor/member.model";
|
||||||
import { IActor, IGroup, IPerson } from "../../types/actor";
|
import { IActor, IGroup, IPerson, usernameWithDomain } from "../../types/actor";
|
||||||
import OrganizerPicker from "./OrganizerPicker.vue";
|
import OrganizerPicker from "./OrganizerPicker.vue";
|
||||||
import { PERSON_MEMBERSHIPS_WITH_MEMBERS } from "../../graphql/actor";
|
import {
|
||||||
|
CURRENT_ACTOR_CLIENT,
|
||||||
|
LOGGED_USER_MEMBERSHIPS,
|
||||||
|
} from "../../graphql/actor";
|
||||||
import { Paginate } from "../../types/paginate";
|
import { Paginate } from "../../types/paginate";
|
||||||
|
import { GROUP_MEMBERS } from "@/graphql/member";
|
||||||
|
import { ActorType, MemberRole } from "@/types/enums";
|
||||||
|
|
||||||
|
const MEMBER_ROLES = [
|
||||||
|
MemberRole.CREATOR,
|
||||||
|
MemberRole.ADMINISTRATOR,
|
||||||
|
MemberRole.MODERATOR,
|
||||||
|
MemberRole.MEMBER,
|
||||||
|
];
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { OrganizerPicker },
|
components: { OrganizerPicker },
|
||||||
apollo: {
|
apollo: {
|
||||||
groupMemberships: {
|
members: {
|
||||||
query: PERSON_MEMBERSHIPS_WITH_MEMBERS,
|
query: GROUP_MEMBERS,
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
id: this.identity.id,
|
name: usernameWithDomain(this.selectedActor),
|
||||||
|
page: this.membersPage,
|
||||||
|
limit: 10,
|
||||||
|
roles: MEMBER_ROLES.join(","),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
update: (data) => data.person.memberships,
|
update: (data) => data.group.members,
|
||||||
skip() {
|
skip() {
|
||||||
return !this.identity.id;
|
return (
|
||||||
|
!this.selectedActor || this.selectedActor.type !== ActorType.GROUP
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentActor: CURRENT_ACTOR_CLIENT,
|
||||||
|
userMemberships: {
|
||||||
|
query: LOGGED_USER_MEMBERSHIPS,
|
||||||
|
variables: {
|
||||||
|
page: 1,
|
||||||
|
limit: 100,
|
||||||
|
},
|
||||||
|
update: (data) => data.loggedUser.memberships,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class OrganizerPickerWrapper extends Vue {
|
export default class OrganizerPickerWrapper extends Vue {
|
||||||
@Prop({ type: Object, required: true }) value!: IActor;
|
@Prop({ type: Object, required: false }) value!: IActor;
|
||||||
|
|
||||||
@Prop({ default: true, type: Boolean }) inline!: boolean;
|
@Prop({ default: true, type: Boolean }) inline!: boolean;
|
||||||
|
|
||||||
@Prop({ type: Object, required: true }) identity!: IPerson;
|
currentActor!: IPerson;
|
||||||
|
|
||||||
isComponentModalActive = false;
|
isComponentModalActive = false;
|
||||||
|
|
||||||
currentActor: IActor = this.value;
|
|
||||||
|
|
||||||
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
|
|
||||||
|
|
||||||
@Prop({ type: Array, required: false, default: () => [] })
|
@Prop({ type: Array, required: false, default: () => [] })
|
||||||
contacts!: IActor[];
|
contacts!: IActor[];
|
||||||
|
members: Paginate<IMember> = { elements: [], total: 0 };
|
||||||
|
|
||||||
actualContacts: (string | undefined)[] = this.contacts.map(({ id }) => id);
|
membersPage = 1;
|
||||||
|
|
||||||
@Watch("contacts")
|
userMemberships: Paginate<IMember> = { elements: [], total: 0 };
|
||||||
updateActualContacts(contacts: IActor[]): void {
|
|
||||||
this.actualContacts = contacts.map(({ id }) => id);
|
get actualContacts(): (string | undefined)[] {
|
||||||
|
return this.contacts.map(({ id }) => id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch("value")
|
set actualContacts(contactsIds: (string | undefined)[]) {
|
||||||
updateCurrentActor(value: IGroup): void {
|
this.$emit(
|
||||||
this.currentActor = value;
|
"update:contacts",
|
||||||
|
this.actorMembers.filter(({ id }) => contactsIds.includes(id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("userMemberships")
|
||||||
|
setInitialActor(): void {
|
||||||
|
if (this.$route.query?.actorId) {
|
||||||
|
const actorId = this.$route.query?.actorId as string;
|
||||||
|
this.$router.replace({ query: undefined });
|
||||||
|
const actor = this.userMemberships.elements.find(
|
||||||
|
({ parent: { id }, role }) =>
|
||||||
|
actorId === id && MEMBER_ROLES.includes(role)
|
||||||
|
)?.parent as IActor;
|
||||||
|
this.selectedActor = actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedActor(): IActor | undefined {
|
||||||
|
if (this.value?.id) {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
if (this.currentActor) {
|
||||||
|
return this.currentActor;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set selectedActor(selectedActor: IActor | undefined) {
|
||||||
|
this.$emit("input", selectedActor);
|
||||||
}
|
}
|
||||||
|
|
||||||
async relay(group: IGroup): Promise<void> {
|
async relay(group: IGroup): Promise<void> {
|
||||||
this.currentActor = group;
|
this.actualContacts = [];
|
||||||
|
this.selectedActor = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
pickActor(): void {
|
pickActor(): void {
|
||||||
this.$emit(
|
|
||||||
"update:contacts",
|
|
||||||
this.actorMembersForCurrentActor.filter(({ id }) =>
|
|
||||||
this.actualContacts.includes(id)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.$emit("input", this.currentActor);
|
|
||||||
this.isComponentModalActive = false;
|
this.isComponentModalActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get actorMembersForCurrentActor(): IActor[] {
|
get actorMembers(): IActor[] {
|
||||||
const currentMembership = this.groupMemberships.elements.find(
|
if (this.selectedActor?.type === ActorType.GROUP) {
|
||||||
({ parent: { id } }) => id === this.currentActor.id
|
return this.members.elements.map(({ actor }: { actor: IActor }) => actor);
|
||||||
);
|
|
||||||
if (currentMembership) {
|
|
||||||
return currentMembership.parent.members.elements.map(
|
|
||||||
({ actor }: { actor: IActor }) => actor
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
:to="{ name: RouteName.PREFERENCES }"
|
:to="{ name: RouteName.PREFERENCES }"
|
||||||
/>
|
/>
|
||||||
<SettingMenuItem
|
<SettingMenuItem
|
||||||
:title="this.$t('Email notifications')"
|
:title="this.$t('Notifications')"
|
||||||
:to="{ name: RouteName.NOTIFICATIONS }"
|
:to="{ name: RouteName.NOTIFICATIONS }"
|
||||||
/>
|
/>
|
||||||
</SettingMenuSection>
|
</SettingMenuSection>
|
||||||
|
|
|
@ -319,6 +319,7 @@ export const LOGGED_USER_MEMBERSHIPS = gql`
|
||||||
preferredUsername
|
preferredUsername
|
||||||
domain
|
domain
|
||||||
name
|
name
|
||||||
|
type
|
||||||
avatar {
|
avatar {
|
||||||
id
|
id
|
||||||
url
|
url
|
||||||
|
@ -359,6 +360,7 @@ export const IDENTITIES = gql`
|
||||||
id
|
id
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
type
|
||||||
preferredUsername
|
preferredUsername
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
@ -379,6 +381,7 @@ export const PERSON_MEMBERSHIPS = gql`
|
||||||
preferredUsername
|
preferredUsername
|
||||||
name
|
name
|
||||||
domain
|
domain
|
||||||
|
type
|
||||||
avatar {
|
avatar {
|
||||||
id
|
id
|
||||||
url
|
url
|
||||||
|
@ -397,55 +400,6 @@ export const PERSON_MEMBERSHIPS = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PERSON_MEMBERSHIPS_WITH_MEMBERS = gql`
|
|
||||||
query PersonMembershipsWithMembers($id: ID!) {
|
|
||||||
person(id: $id) {
|
|
||||||
id
|
|
||||||
memberships {
|
|
||||||
total
|
|
||||||
elements {
|
|
||||||
id
|
|
||||||
role
|
|
||||||
parent {
|
|
||||||
id
|
|
||||||
preferredUsername
|
|
||||||
name
|
|
||||||
domain
|
|
||||||
avatar {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
}
|
|
||||||
members {
|
|
||||||
total
|
|
||||||
elements {
|
|
||||||
id
|
|
||||||
role
|
|
||||||
actor {
|
|
||||||
id
|
|
||||||
preferredUsername
|
|
||||||
name
|
|
||||||
domain
|
|
||||||
avatar {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invitedBy {
|
|
||||||
id
|
|
||||||
preferredUsername
|
|
||||||
name
|
|
||||||
}
|
|
||||||
insertedAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const PERSON_MEMBERSHIP_GROUP = gql`
|
export const PERSON_MEMBERSHIP_GROUP = gql`
|
||||||
query PersonMembershipGroup($id: ID!, $group: String!) {
|
query PersonMembershipGroup($id: ID!, $group: String!) {
|
||||||
person(id: $id) {
|
person(id: $id) {
|
||||||
|
|
|
@ -241,3 +241,17 @@ export const UPDATE_USER_LOCALE = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const FEED_TOKENS_LOGGED_USER = gql`
|
||||||
|
query {
|
||||||
|
loggedUser {
|
||||||
|
id
|
||||||
|
feedTokens {
|
||||||
|
token
|
||||||
|
actor {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -969,5 +969,15 @@
|
||||||
"You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.",
|
"You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.",
|
||||||
"{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}.",
|
"{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}.",
|
||||||
"New post": "New post",
|
"New post": "New post",
|
||||||
"Comment text can't be empty": "Comment text can't be empty"
|
"Comment text can't be empty": "Comment text can't be empty",
|
||||||
|
"Notifications": "Notifications",
|
||||||
|
"Profile feeds": "Profile feeds",
|
||||||
|
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.",
|
||||||
|
"Regenerate new links": "Regenerate new links",
|
||||||
|
"Create new links": "Create new links",
|
||||||
|
"You'll need to change the URLs where there were previously entered.": "You'll need to change the URLs where there were previously entered.",
|
||||||
|
"Personal feeds": "Personal feeds",
|
||||||
|
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.",
|
||||||
|
"The event will show as attributed to this profile.": "The event will show as attributed to this profile.",
|
||||||
|
"You may show some members as contacts.": "You may show some members as contacts."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1063,5 +1063,15 @@
|
||||||
"You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.",
|
"You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.",
|
||||||
"{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}.",
|
"{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}.",
|
||||||
"New post": "Nouveau billet",
|
"New post": "Nouveau billet",
|
||||||
"Comment text can't be empty": "Le texte du commentaire ne peut être vide"
|
"Comment text can't be empty": "Le texte du commentaire ne peut être vide",
|
||||||
|
"Notifications": "Notifications",
|
||||||
|
"Profile feeds": "Flux du profil",
|
||||||
|
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Ces flux contiennent des informations sur les événements pour lesquels ce profil spécifique est un⋅e participant⋅e ou un⋅e créateur⋅ice. Vous devriez les garder privés. Vous pouvez trouver des flux pour l'ensemble de vos profils dans vos paramètres de notification.",
|
||||||
|
"Regenerate new links": "Regénérer de nouveaux liens",
|
||||||
|
"Create new links": "Créer de nouveaux liens",
|
||||||
|
"You'll need to change the URLs where there were previously entered.": "Vous devrez changer les URLs là où vous les avez entrées précédemment.",
|
||||||
|
"Personal feeds": "Flux personnels",
|
||||||
|
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Ces flux contiennent des informations sur les événements pour lesquels n'importe lequel de vos profils est un⋅e participant⋅e ou un⋅e créateur⋅ice. Vous devriez les garder privés. Vous pouvez trouver des flux spécifiques à chaque profil sur la page d'édition des profils.",
|
||||||
|
"The event will show as attributed to this profile.": "L'événement sera affiché comme attribué à ce profil.",
|
||||||
|
"You may show some members as contacts.": "Vous pouvez afficher certain⋅es membres en tant que contacts."
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,77 @@
|
||||||
$t("Delete this identity")
|
$t("Delete this identity")
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section v-if="isUpdate">
|
||||||
|
<div class="setting-title">
|
||||||
|
<h2>{{ $t("Profile feeds") }}</h2>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<div v-if="identity.feedTokens && identity.feedTokens.length > 0">
|
||||||
|
<div
|
||||||
|
class="buttons"
|
||||||
|
v-for="feedToken in identity.feedTokens"
|
||||||
|
:key="feedToken.token"
|
||||||
|
>
|
||||||
|
<b-tooltip
|
||||||
|
:label="$t('URL copied to clipboard')"
|
||||||
|
:active="showCopiedTooltip.atom"
|
||||||
|
always
|
||||||
|
type="is-success"
|
||||||
|
position="is-left"
|
||||||
|
>
|
||||||
|
<b-button
|
||||||
|
tag="a"
|
||||||
|
icon-left="rss"
|
||||||
|
@click="
|
||||||
|
(e) => copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom')
|
||||||
|
"
|
||||||
|
:href="tokenToURL(feedToken.token, 'atom')"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t("RSS/Atom Feed") }}</b-button
|
||||||
|
>
|
||||||
|
</b-tooltip>
|
||||||
|
<b-tooltip
|
||||||
|
:label="$t('URL copied to clipboard')"
|
||||||
|
:active="showCopiedTooltip.ics"
|
||||||
|
always
|
||||||
|
type="is-success"
|
||||||
|
position="is-left"
|
||||||
|
>
|
||||||
|
<b-button
|
||||||
|
tag="a"
|
||||||
|
@click="
|
||||||
|
(e) => copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics')
|
||||||
|
"
|
||||||
|
icon-left="calendar-sync"
|
||||||
|
:href="tokenToURL(feedToken.token, 'ics')"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t("ICS/WebCal Feed") }}</b-button
|
||||||
|
>
|
||||||
|
</b-tooltip>
|
||||||
|
<b-button
|
||||||
|
icon-left="refresh"
|
||||||
|
type="is-text"
|
||||||
|
@click="openRegenerateFeedTokensConfirmation"
|
||||||
|
>{{ $t("Regenerate new links") }}</b-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<b-button
|
||||||
|
icon-left="refresh"
|
||||||
|
type="is-text"
|
||||||
|
@click="generateFeedTokens"
|
||||||
|
>{{ $t("Create new links") }}</b-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -131,6 +202,10 @@ h1 {
|
||||||
.username-field + .field {
|
.username-field + .field {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep .buttons > *:not(:last-child) .button {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -151,6 +226,11 @@ import RouteName from "../../../router/name";
|
||||||
import { buildFileVariable } from "../../../utils/image";
|
import { buildFileVariable } from "../../../utils/image";
|
||||||
import { changeIdentity } from "../../../utils/auth";
|
import { changeIdentity } from "../../../utils/auth";
|
||||||
import identityEditionMixin from "../../../mixins/identityEdition";
|
import identityEditionMixin from "../../../mixins/identityEdition";
|
||||||
|
import {
|
||||||
|
CREATE_FEED_TOKEN_ACTOR,
|
||||||
|
DELETE_FEED_TOKEN,
|
||||||
|
} from "@/graphql/feed_tokens";
|
||||||
|
import { IFeedToken } from "@/types/feedtoken.model";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
|
@ -191,6 +271,8 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
|
showCopiedTooltip = { ics: false, atom: false };
|
||||||
|
|
||||||
get message(): string | null {
|
get message(): string | null {
|
||||||
if (this.isUpdate) return null;
|
if (this.isUpdate) return null;
|
||||||
return this.$t(
|
return this.$t(
|
||||||
|
@ -353,6 +435,63 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
|
||||||
return MOBILIZON_INSTANCE_HOST;
|
return MOBILIZON_INSTANCE_HOST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokenToURL(token: string, format: string): string {
|
||||||
|
return `${window.location.origin}/events/going/${token}/${format}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyURL(e: Event, url: string, format: "ics" | "atom"): void {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
e.preventDefault();
|
||||||
|
navigator.clipboard.writeText(url);
|
||||||
|
this.showCopiedTooltip[format] = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showCopiedTooltip[format] = false;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateFeedTokens(): Promise<void> {
|
||||||
|
const newToken = await this.createNewFeedToken();
|
||||||
|
this.identity.feedTokens.push(newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async regenerateFeedTokens(): Promise<void> {
|
||||||
|
if (this.identity?.feedTokens.length < 1) return;
|
||||||
|
await this.deleteFeedToken(this.identity.feedTokens[0].token);
|
||||||
|
const newToken = await this.createNewFeedToken();
|
||||||
|
this.identity.feedTokens.pop();
|
||||||
|
this.identity.feedTokens.push(newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteFeedToken(token: string): Promise<void> {
|
||||||
|
await this.$apollo.mutate({
|
||||||
|
mutation: DELETE_FEED_TOKEN,
|
||||||
|
variables: { token },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createNewFeedToken(): Promise<IFeedToken> {
|
||||||
|
const { data } = await this.$apollo.mutate({
|
||||||
|
mutation: CREATE_FEED_TOKEN_ACTOR,
|
||||||
|
variables: { actor_id: this.identity?.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.createFeedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
openRegenerateFeedTokensConfirmation(): void {
|
||||||
|
this.$buefy.dialog.confirm({
|
||||||
|
type: "is-warning",
|
||||||
|
title: this.$t("Regenerate new links") as string,
|
||||||
|
message: this.$t(
|
||||||
|
"You'll need to change the URLs where there were previously entered."
|
||||||
|
) as string,
|
||||||
|
confirmText: this.$t("Regenerate new links") as string,
|
||||||
|
cancelText: this.$t("Cancel") as string,
|
||||||
|
onConfirm: () => this.regenerateFeedTokens(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
openDeleteIdentityConfirmation(): void {
|
openDeleteIdentityConfirmation(): void {
|
||||||
this.$buefy.dialog.prompt({
|
this.$buefy.dialog.prompt({
|
||||||
type: "is-danger",
|
type: "is-danger",
|
||||||
|
|
|
@ -81,19 +81,21 @@
|
||||||
|
|
||||||
<subtitle>{{ $t("Organizers") }}</subtitle>
|
<subtitle>{{ $t("Organizers") }}</subtitle>
|
||||||
|
|
||||||
<div v-if="config && config.features.groups">
|
<div v-if="config && config.features.groups && organizerActor.id">
|
||||||
<b-field>
|
<b-field>
|
||||||
<organizer-picker-wrapper
|
<organizer-picker-wrapper
|
||||||
v-model="event.attributedTo"
|
v-model="organizerActor"
|
||||||
:contacts.sync="event.contacts"
|
:contacts.sync="event.contacts"
|
||||||
:identity="event.organizerActor"
|
|
||||||
/>
|
/>
|
||||||
</b-field>
|
</b-field>
|
||||||
<p v-if="!event.attributedTo.id || attributedToEqualToOrganizerActor">
|
<p v-if="!attributedToAGroup && organizerActorEqualToCurrentActor">
|
||||||
{{
|
{{
|
||||||
$t("The event will show as attributed to your personal profile.")
|
$t("The event will show as attributed to your personal profile.")
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
|
<p v-else-if="!attributedToAGroup">
|
||||||
|
{{ $t("The event will show as attributed to this profile.") }}
|
||||||
|
</p>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
<span>{{
|
<span>{{
|
||||||
$t("The event will show as attributed to this group.")
|
$t("The event will show as attributed to this group.")
|
||||||
|
@ -101,6 +103,7 @@
|
||||||
<span
|
<span
|
||||||
v-if="event.contacts && event.contacts.length"
|
v-if="event.contacts && event.contacts.length"
|
||||||
v-html="
|
v-html="
|
||||||
|
' ' +
|
||||||
$tc(
|
$tc(
|
||||||
'<b>{contact}</b> will be displayed as contact.',
|
'<b>{contact}</b> will be displayed as contact.',
|
||||||
event.contacts.length,
|
event.contacts.length,
|
||||||
|
@ -114,6 +117,9 @@
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<span v-else>
|
||||||
|
{{ $t("You may show some members as contacts.") }}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<subtitle>{{ $t("Who can view this event and participate") }}</subtitle>
|
<subtitle>{{ $t("Who can view this event and participate") }}</subtitle>
|
||||||
|
@ -432,6 +438,7 @@ import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||||
import { Route } from "vue-router";
|
import { Route } from "vue-router";
|
||||||
import { formatList } from "@/utils/i18n";
|
import { formatList } from "@/utils/i18n";
|
||||||
import {
|
import {
|
||||||
|
ActorType,
|
||||||
CommentModeration,
|
CommentModeration,
|
||||||
EventJoinOptions,
|
EventJoinOptions,
|
||||||
EventStatus,
|
EventStatus,
|
||||||
|
@ -448,10 +455,11 @@ import {
|
||||||
import { EventModel, IEvent } from "../../types/event.model";
|
import { EventModel, IEvent } from "../../types/event.model";
|
||||||
import {
|
import {
|
||||||
CURRENT_ACTOR_CLIENT,
|
CURRENT_ACTOR_CLIENT,
|
||||||
|
IDENTITIES,
|
||||||
LOGGED_USER_DRAFTS,
|
LOGGED_USER_DRAFTS,
|
||||||
LOGGED_USER_PARTICIPATIONS,
|
LOGGED_USER_PARTICIPATIONS,
|
||||||
} from "../../graphql/actor";
|
} from "../../graphql/actor";
|
||||||
import { IPerson, Person, displayNameAndUsername } from "../../types/actor";
|
import { displayNameAndUsername, IActor, IGroup } from "../../types/actor";
|
||||||
import { TAGS } from "../../graphql/tags";
|
import { TAGS } from "../../graphql/tags";
|
||||||
import { ITag } from "../../types/tag.model";
|
import { ITag } from "../../types/tag.model";
|
||||||
import {
|
import {
|
||||||
|
@ -480,6 +488,7 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
||||||
currentActor: CURRENT_ACTOR_CLIENT,
|
currentActor: CURRENT_ACTOR_CLIENT,
|
||||||
tags: TAGS,
|
tags: TAGS,
|
||||||
config: CONFIG,
|
config: CONFIG,
|
||||||
|
identities: IDENTITIES,
|
||||||
event: {
|
event: {
|
||||||
query: FETCH_EVENT,
|
query: FETCH_EVENT,
|
||||||
variables() {
|
variables() {
|
||||||
|
@ -513,12 +522,14 @@ export default class EditEvent extends Vue {
|
||||||
|
|
||||||
@Prop({ type: Boolean, default: false }) isDuplicate!: boolean;
|
@Prop({ type: Boolean, default: false }) isDuplicate!: boolean;
|
||||||
|
|
||||||
currentActor = new Person();
|
currentActor!: IActor;
|
||||||
|
|
||||||
tags: ITag[] = [];
|
tags: ITag[] = [];
|
||||||
|
|
||||||
event: IEvent = new EventModel();
|
event: IEvent = new EventModel();
|
||||||
|
|
||||||
|
identities: IActor[] = [];
|
||||||
|
|
||||||
config!: IConfig;
|
config!: IConfig;
|
||||||
|
|
||||||
unmodifiedEvent!: IEvent;
|
unmodifiedEvent!: IEvent;
|
||||||
|
@ -573,16 +584,32 @@ export default class EditEvent extends Vue {
|
||||||
|
|
||||||
this.event.beginsOn = now;
|
this.event.beginsOn = now;
|
||||||
this.event.endsOn = end;
|
this.event.endsOn = end;
|
||||||
this.event.organizerActor = this.getDefaultActor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDefaultActor() {
|
get organizerActor(): IActor {
|
||||||
if (this.event.organizerActor?.id) {
|
if (this.event?.attributedTo?.id) {
|
||||||
|
return this.event.attributedTo;
|
||||||
|
}
|
||||||
|
if (this.event?.organizerActor?.id) {
|
||||||
return this.event.organizerActor;
|
return this.event.organizerActor;
|
||||||
}
|
}
|
||||||
return this.currentActor;
|
return this.currentActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set organizerActor(actor: IActor) {
|
||||||
|
if (actor?.type === ActorType.GROUP) {
|
||||||
|
this.event.attributedTo = actor as IGroup;
|
||||||
|
this.event.organizerActor = this.currentActor;
|
||||||
|
} else {
|
||||||
|
this.event.attributedTo = undefined;
|
||||||
|
this.event.organizerActor = actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get attributedToAGroup(): boolean {
|
||||||
|
return this.event.attributedTo?.id !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async mounted(): Promise<void> {
|
async mounted(): Promise<void> {
|
||||||
this.observer = new IntersectionObserver(
|
this.observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
|
@ -724,8 +751,10 @@ export default class EditEvent extends Vue {
|
||||||
return !(
|
return !(
|
||||||
this.eventId &&
|
this.eventId &&
|
||||||
this.event.organizerActor?.id !== undefined &&
|
this.event.organizerActor?.id !== undefined &&
|
||||||
this.currentActor.id !== this.event.organizerActor.id
|
!this.identities
|
||||||
) as boolean;
|
.map(({ id }) => id)
|
||||||
|
.includes(this.event.organizerActor?.id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get updateEventMessage(): string {
|
get updateEventMessage(): string {
|
||||||
|
@ -752,8 +781,7 @@ export default class EditEvent extends Vue {
|
||||||
*/
|
*/
|
||||||
private postCreateOrUpdate(store: any, updateEvent: IEvent) {
|
private postCreateOrUpdate(store: any, updateEvent: IEvent) {
|
||||||
const resultEvent: IEvent = { ...updateEvent };
|
const resultEvent: IEvent = { ...updateEvent };
|
||||||
const organizerActor: IPerson = this.event.organizerActor as Person;
|
resultEvent.organizerActor = this.event.organizerActor;
|
||||||
resultEvent.organizerActor = organizerActor;
|
|
||||||
resultEvent.relatedEvents = [];
|
resultEvent.relatedEvents = [];
|
||||||
|
|
||||||
store.writeQuery({
|
store.writeQuery({
|
||||||
|
@ -766,12 +794,12 @@ export default class EditEvent extends Vue {
|
||||||
query: EVENT_PERSON_PARTICIPATION,
|
query: EVENT_PERSON_PARTICIPATION,
|
||||||
variables: {
|
variables: {
|
||||||
eventId: updateEvent.id,
|
eventId: updateEvent.id,
|
||||||
name: organizerActor.preferredUsername,
|
name: this.event.organizerActor?.preferredUsername,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
person: {
|
person: {
|
||||||
__typename: "Person",
|
__typename: "Person",
|
||||||
id: organizerActor.id,
|
id: this.event?.organizerActor?.id,
|
||||||
participations: {
|
participations: {
|
||||||
__typename: "PaginatedParticipantList",
|
__typename: "PaginatedParticipantList",
|
||||||
total: 1,
|
total: 1,
|
||||||
|
@ -782,7 +810,7 @@ export default class EditEvent extends Vue {
|
||||||
role: ParticipantRole.CREATOR,
|
role: ParticipantRole.CREATOR,
|
||||||
actor: {
|
actor: {
|
||||||
__typename: "Actor",
|
__typename: "Actor",
|
||||||
id: organizerActor.id,
|
id: this.event?.organizerActor?.id,
|
||||||
},
|
},
|
||||||
event: {
|
event: {
|
||||||
__typename: "Event",
|
__typename: "Event",
|
||||||
|
@ -819,28 +847,24 @@ export default class EditEvent extends Vue {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
get attributedToEqualToOrganizerActor(): boolean {
|
get organizerActorEqualToCurrentActor(): boolean {
|
||||||
return (this.event.organizerActor?.id !== undefined &&
|
return (
|
||||||
this.event.attributedTo?.id === this.event.organizerActor?.id) as boolean;
|
this.currentActor?.id !== undefined &&
|
||||||
|
this.organizerActor?.id === this.currentActor?.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build variables for Event GraphQL creation query
|
* Build variables for Event GraphQL creation query
|
||||||
*/
|
*/
|
||||||
private async buildVariables() {
|
private async buildVariables() {
|
||||||
this.event.organizerActor = this.event.organizerActor?.id
|
|
||||||
? this.event.organizerActor
|
|
||||||
: this.currentActor;
|
|
||||||
let res = this.event.toEditJSON();
|
let res = this.event.toEditJSON();
|
||||||
if (this.event.organizerActor) {
|
if (this.event.organizerActor) {
|
||||||
res = Object.assign(res, {
|
res = Object.assign(res, {
|
||||||
organizerActorId: this.event.organizerActor.id,
|
organizerActorId: this.event.organizerActor.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const attributedToId =
|
const attributedToId = this.event.attributedTo?.id
|
||||||
this.event.attributedTo &&
|
|
||||||
!this.attributedToEqualToOrganizerActor &&
|
|
||||||
this.event.attributedTo.id
|
|
||||||
? this.event.attributedTo.id
|
? this.event.attributedTo.id
|
||||||
: null;
|
: null;
|
||||||
res = Object.assign(res, { attributedToId });
|
res = Object.assign(res, { attributedToId });
|
||||||
|
|
|
@ -327,6 +327,7 @@
|
||||||
v-if="isCurrentActorAGroupModerator"
|
v-if="isCurrentActorAGroupModerator"
|
||||||
:to="{
|
:to="{
|
||||||
name: RouteName.CREATE_EVENT,
|
name: RouteName.CREATE_EVENT,
|
||||||
|
query: { actorId: group.id },
|
||||||
}"
|
}"
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
>{{ $t("+ Create an event") }}</router-link
|
>{{ $t("+ Create an event") }}</router-link
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="is-active">
|
<li class="is-active">
|
||||||
<router-link :to="{ name: RouteName.NOTIFICATIONS }">{{
|
<router-link :to="{ name: RouteName.NOTIFICATIONS }">{{
|
||||||
$t("Email notifications")
|
$t("Notifications")
|
||||||
}}</router-link>
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -118,23 +118,108 @@
|
||||||
</b-select>
|
</b-select>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<div class="setting-title">
|
||||||
|
<h2>{{ $t("Personal feeds") }}</h2>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<div v-if="feedTokens && feedTokens.length > 0">
|
||||||
|
<div
|
||||||
|
class="buttons"
|
||||||
|
v-for="feedToken in feedTokens"
|
||||||
|
:key="feedToken.token"
|
||||||
|
>
|
||||||
|
<b-tooltip
|
||||||
|
:label="$t('URL copied to clipboard')"
|
||||||
|
:active="showCopiedTooltip.atom"
|
||||||
|
always
|
||||||
|
type="is-success"
|
||||||
|
position="is-left"
|
||||||
|
>
|
||||||
|
<b-button
|
||||||
|
tag="a"
|
||||||
|
icon-left="rss"
|
||||||
|
@click="
|
||||||
|
(e) => copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom')
|
||||||
|
"
|
||||||
|
:href="tokenToURL(feedToken.token, 'atom')"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t("RSS/Atom Feed") }}</b-button
|
||||||
|
>
|
||||||
|
</b-tooltip>
|
||||||
|
<b-tooltip
|
||||||
|
:label="$t('URL copied to clipboard')"
|
||||||
|
:active="showCopiedTooltip.ics"
|
||||||
|
always
|
||||||
|
type="is-success"
|
||||||
|
position="is-left"
|
||||||
|
>
|
||||||
|
<b-button
|
||||||
|
tag="a"
|
||||||
|
@click="
|
||||||
|
(e) => copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics')
|
||||||
|
"
|
||||||
|
icon-left="calendar-sync"
|
||||||
|
:href="tokenToURL(feedToken.token, 'ics')"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t("ICS/WebCal Feed") }}</b-button
|
||||||
|
>
|
||||||
|
</b-tooltip>
|
||||||
|
<b-button
|
||||||
|
icon-left="refresh"
|
||||||
|
type="is-text"
|
||||||
|
@click="openRegenerateFeedTokensConfirmation"
|
||||||
|
>{{ $t("Regenerate new links") }}</b-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<b-button
|
||||||
|
icon-left="refresh"
|
||||||
|
type="is-text"
|
||||||
|
@click="generateFeedTokens"
|
||||||
|
>{{ $t("Create new links") }}</b-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue, Watch } from "vue-property-decorator";
|
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||||
import { INotificationPendingEnum } from "@/types/enums";
|
import { INotificationPendingEnum } from "@/types/enums";
|
||||||
import { USER_SETTINGS, SET_USER_SETTINGS } from "../../graphql/user";
|
import {
|
||||||
|
USER_SETTINGS,
|
||||||
|
SET_USER_SETTINGS,
|
||||||
|
FEED_TOKENS_LOGGED_USER,
|
||||||
|
} from "../../graphql/user";
|
||||||
import { IUser } from "../../types/current-user.model";
|
import { IUser } from "../../types/current-user.model";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
|
import { IFeedToken } from "@/types/feedtoken.model";
|
||||||
|
import { CREATE_FEED_TOKEN, DELETE_FEED_TOKEN } from "@/graphql/feed_tokens";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
apollo: {
|
apollo: {
|
||||||
loggedUser: USER_SETTINGS,
|
loggedUser: USER_SETTINGS,
|
||||||
|
feedTokens: {
|
||||||
|
query: FEED_TOKENS_LOGGED_USER,
|
||||||
|
update: (data) =>
|
||||||
|
data.loggedUser.feedTokens.filter(
|
||||||
|
(token: IFeedToken) => token.actor === null
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class Notifications extends Vue {
|
export default class Notifications extends Vue {
|
||||||
loggedUser!: IUser;
|
loggedUser!: IUser;
|
||||||
|
|
||||||
|
feedTokens: IFeedToken[] = [];
|
||||||
|
|
||||||
notificationOnDay: boolean | undefined = true;
|
notificationOnDay: boolean | undefined = true;
|
||||||
|
|
||||||
notificationEachWeek: boolean | undefined = false;
|
notificationEachWeek: boolean | undefined = false;
|
||||||
|
@ -148,6 +233,8 @@ export default class Notifications extends Vue {
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
|
showCopiedTooltip = { ics: false, atom: false };
|
||||||
|
|
||||||
mounted(): void {
|
mounted(): void {
|
||||||
this.notificationPendingParticipationValues = {
|
this.notificationPendingParticipationValues = {
|
||||||
[INotificationPendingEnum.NONE]: this.$t("Do not receive any mail"),
|
[INotificationPendingEnum.NONE]: this.$t("Do not receive any mail"),
|
||||||
|
@ -176,6 +263,62 @@ export default class Notifications extends Vue {
|
||||||
refetchQueries: [{ query: USER_SETTINGS }],
|
refetchQueries: [{ query: USER_SETTINGS }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokenToURL(token: string, format: string): string {
|
||||||
|
return `${window.location.origin}/events/going/${token}/${format}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyURL(e: Event, url: string, format: "ics" | "atom"): void {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
e.preventDefault();
|
||||||
|
navigator.clipboard.writeText(url);
|
||||||
|
this.showCopiedTooltip[format] = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showCopiedTooltip[format] = false;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openRegenerateFeedTokensConfirmation(): void {
|
||||||
|
this.$buefy.dialog.confirm({
|
||||||
|
type: "is-warning",
|
||||||
|
title: this.$t("Regenerate new links") as string,
|
||||||
|
message: this.$t(
|
||||||
|
"You'll need to change the URLs where there were previously entered."
|
||||||
|
) as string,
|
||||||
|
confirmText: this.$t("Regenerate new links") as string,
|
||||||
|
cancelText: this.$t("Cancel") as string,
|
||||||
|
onConfirm: () => this.regenerateFeedTokens(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async regenerateFeedTokens(): Promise<void> {
|
||||||
|
if (this.feedTokens.length < 1) return;
|
||||||
|
await this.deleteFeedToken(this.feedTokens[0].token);
|
||||||
|
const newToken = await this.createNewFeedToken();
|
||||||
|
this.feedTokens.pop();
|
||||||
|
this.feedTokens.push(newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateFeedTokens(): Promise<void> {
|
||||||
|
const newToken = await this.createNewFeedToken();
|
||||||
|
this.feedTokens.push(newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteFeedToken(token: string): Promise<void> {
|
||||||
|
await this.$apollo.mutate({
|
||||||
|
mutation: DELETE_FEED_TOKEN,
|
||||||
|
variables: { token },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createNewFeedToken(): Promise<IFeedToken> {
|
||||||
|
const { data } = await this.$apollo.mutate({
|
||||||
|
mutation: CREATE_FEED_TOKEN,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.createFeedToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -193,4 +336,8 @@ export default class Notifications extends Vue {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep .buttons > *:not(:last-child) .button {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -22,7 +22,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
|
||||||
) do
|
) do
|
||||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, feed_token} <- Events.create_feed_token(%{user_id: id, actor_id: actor_id}) do
|
{:ok, feed_token} <- Events.create_feed_token(%{user_id: id, actor_id: actor_id}) do
|
||||||
{:ok, feed_token}
|
{:ok, to_short_uuid(feed_token)}
|
||||||
else
|
else
|
||||||
{:is_owned, nil} ->
|
{:is_owned, nil} ->
|
||||||
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
|
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
|
||||||
|
@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
|
||||||
@spec create_feed_token(any, map, map) :: {:ok, FeedToken.t()}
|
@spec create_feed_token(any, map, map) :: {:ok, FeedToken.t()}
|
||||||
def create_feed_token(_parent, %{}, %{context: %{current_user: %User{id: id}}}) do
|
def create_feed_token(_parent, %{}, %{context: %{current_user: %User{id: id}}}) do
|
||||||
with {:ok, feed_token} <- Events.create_feed_token(%{user_id: id}) do
|
with {:ok, feed_token} <- Events.create_feed_token(%{user_id: id}) do
|
||||||
{:ok, feed_token}
|
{:ok, to_short_uuid(feed_token)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,7 +50,8 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
|
||||||
%{token: token},
|
%{token: token},
|
||||||
%{context: %{current_user: %User{id: id} = _user}}
|
%{context: %{current_user: %User{id: id} = _user}}
|
||||||
) do
|
) do
|
||||||
with {:ok, token} <- Ecto.UUID.cast(token),
|
with {:ok, token} <- ShortUUID.decode(token),
|
||||||
|
{:ok, token} <- Ecto.UUID.cast(token),
|
||||||
{:no_token, %FeedToken{actor: actor, user: %User{} = user} = feed_token} <-
|
{:no_token, %FeedToken{actor: actor, user: %User{} = user} = feed_token} <-
|
||||||
{:no_token, Events.get_feed_token(token)},
|
{:no_token, Events.get_feed_token(token)},
|
||||||
{:token_from_user, true} <- {:token_from_user, id == user.id},
|
{:token_from_user, true} <- {:token_from_user, id == user.id},
|
||||||
|
@ -65,6 +66,9 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
|
||||||
:error ->
|
:error ->
|
||||||
{:error, dgettext("errors", "Token is not a valid UUID")}
|
{:error, dgettext("errors", "Token is not a valid UUID")}
|
||||||
|
|
||||||
|
{:error, "Invalid input"} ->
|
||||||
|
{:error, dgettext("errors", "Token is not a valid UUID")}
|
||||||
|
|
||||||
{:no_token, _} ->
|
{:no_token, _} ->
|
||||||
{:error, dgettext("errors", "Token does not exist")}
|
{:error, dgettext("errors", "Token does not exist")}
|
||||||
|
|
||||||
|
@ -77,4 +81,8 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
|
||||||
def delete_feed_token(_parent, _args, %{}) do
|
def delete_feed_token(_parent, _args, %{}) do
|
||||||
{:error, dgettext("errors", "You are not allowed to delete a feed token if not connected")}
|
{:error, dgettext("errors", "You are not allowed to delete a feed token if not connected")}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp to_short_uuid(%FeedToken{token: token} = feed_token) do
|
||||||
|
%FeedToken{feed_token | token: ShortUUID.encode!(token)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -315,7 +315,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
context: %{current_user: user}
|
context: %{current_user: user}
|
||||||
}) do
|
}) do
|
||||||
with {:is_owned, %Actor{id: actor_id}} <- User.owns_actor(user, actor_id),
|
with {:is_owned, %Actor{id: actor_id}} <- User.owns_actor(user, actor_id),
|
||||||
%Actor{id: group_id} <- Actors.get_actor_by_name(group, :Group),
|
{:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(group, :Group)},
|
||||||
{:ok, %Member{} = membership} <- Actors.get_member(actor_id, group_id),
|
{:ok, %Member{} = membership} <- Actors.get_member(actor_id, group_id),
|
||||||
memberships <- %Page{
|
memberships <- %Page{
|
||||||
total: 1,
|
total: 1,
|
||||||
|
@ -326,6 +326,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
{:error, :member_not_found} ->
|
{:error, :member_not_found} ->
|
||||||
{:ok, %Page{total: 0, elements: []}}
|
{:ok, %Page{total: 0, elements: []}}
|
||||||
|
|
||||||
|
{:group, nil} ->
|
||||||
|
{:error, :group_not_found}
|
||||||
|
|
||||||
{:is_owned, nil} ->
|
{:is_owned, nil} ->
|
||||||
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
|
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
||||||
"""
|
"""
|
||||||
use Absinthe.Schema.Notation
|
use Absinthe.Schema.Notation
|
||||||
|
|
||||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
import Absinthe.Resolution.Helpers, only: [dataloader: 2]
|
||||||
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Media, Person}
|
alias Mobilizon.GraphQL.Resolvers.{Media, Person}
|
||||||
|
@ -53,7 +53,13 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
||||||
)
|
)
|
||||||
|
|
||||||
field(:feed_tokens, list_of(:feed_token),
|
field(:feed_tokens, list_of(:feed_token),
|
||||||
resolve: dataloader(Events),
|
resolve:
|
||||||
|
dataloader(
|
||||||
|
Events,
|
||||||
|
callback: fn feed_tokens, _parent, _args ->
|
||||||
|
{:ok, Enum.map(feed_tokens, &Map.put(&1, :token, ShortUUID.encode!(&1.token)))}
|
||||||
|
end
|
||||||
|
),
|
||||||
description: "A list of the feed tokens for this person"
|
description: "A list of the feed tokens for this person"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Mobilizon.GraphQL.Schema.Events.FeedTokenType do
|
||||||
description: "The actor that participates to the event"
|
description: "The actor that participates to the event"
|
||||||
)
|
)
|
||||||
|
|
||||||
field(:token, :string, description: "The role of this actor at this event")
|
field(:token, :string, description: "A ShortUUID private token")
|
||||||
end
|
end
|
||||||
|
|
||||||
@desc "Represents a deleted feed_token"
|
@desc "Represents a deleted feed_token"
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
|
||||||
"""
|
"""
|
||||||
use Absinthe.Schema.Notation
|
use Absinthe.Schema.Notation
|
||||||
|
|
||||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
import Absinthe.Resolution.Helpers, only: [dataloader: 2]
|
||||||
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Media, User}
|
alias Mobilizon.GraphQL.Resolvers.{Media, User}
|
||||||
|
@ -43,7 +43,13 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
|
||||||
)
|
)
|
||||||
|
|
||||||
field(:feed_tokens, list_of(:feed_token),
|
field(:feed_tokens, list_of(:feed_token),
|
||||||
resolve: dataloader(Events),
|
resolve:
|
||||||
|
dataloader(
|
||||||
|
Events,
|
||||||
|
callback: fn feed_tokens, _parent, _args ->
|
||||||
|
{:ok, Enum.map(feed_tokens, &Map.put(&1, :token, ShortUUID.encode!(&1.token)))}
|
||||||
|
end
|
||||||
|
),
|
||||||
description: "A list of the feed tokens for this user"
|
description: "A list of the feed tokens for this user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ defmodule Mobilizon.Service.Export.Common do
|
||||||
# Only events, not posts
|
# Only events, not posts
|
||||||
@spec fetch_events_from_token(String.t()) :: String.t()
|
@spec fetch_events_from_token(String.t()) :: String.t()
|
||||||
def fetch_events_from_token(token) do
|
def fetch_events_from_token(token) do
|
||||||
with {:ok, _uuid} <- Ecto.UUID.cast(token),
|
with {:ok, uuid} <- ShortUUID.decode(token),
|
||||||
%FeedToken{actor: actor, user: %User{} = user} <- Events.get_feed_token(token) do
|
{:ok, _uuid} <- Ecto.UUID.cast(uuid),
|
||||||
|
%FeedToken{actor: actor, user: %User{} = user} <- Events.get_feed_token(uuid) do
|
||||||
case actor do
|
case actor do
|
||||||
%Actor{} = actor ->
|
%Actor{} = actor ->
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -26,7 +26,7 @@ defmodule Mobilizon.Web.FeedController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def event(conn, %{"uuid" => uuid, "format" => "ics"}) do
|
def event(conn, %{"uuid" => uuid, "format" => "ics"}) do
|
||||||
return_data(conn, "ics", "event_" <> uuid, "event.ics")
|
return_data(conn, "ics", "event_" <> uuid, "event")
|
||||||
end
|
end
|
||||||
|
|
||||||
def event(_conn, _) do
|
def event(_conn, _) do
|
||||||
|
@ -34,7 +34,7 @@ defmodule Mobilizon.Web.FeedController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def going(conn, %{"token" => token, "format" => format}) when format in @formats do
|
def going(conn, %{"token" => token, "format" => format}) when format in @formats do
|
||||||
return_data(conn, format, "token_" <> token, "events.#{format}")
|
return_data(conn, format, "token_" <> token, "events")
|
||||||
end
|
end
|
||||||
|
|
||||||
def going(_conn, _) do
|
def going(_conn, _) do
|
||||||
|
|
|
@ -186,7 +186,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do
|
||||||
%{
|
%{
|
||||||
"feedTokens" => [
|
"feedTokens" => [
|
||||||
%{
|
%{
|
||||||
"token" => feed_token.token
|
"token" => ShortUUID.encode!(feed_token.token)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
deleteFeedToken(
|
deleteFeedToken(
|
||||||
token: "#{feed_token.token}",
|
token: "#{ShortUUID.encode!(feed_token.token)}",
|
||||||
) {
|
) {
|
||||||
actor {
|
actor {
|
||||||
id
|
id
|
||||||
|
@ -270,7 +270,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
deleteFeedToken(
|
deleteFeedToken(
|
||||||
token: "#{feed_token.token}",
|
token: "#{ShortUUID.encode!(feed_token.token)}",
|
||||||
) {
|
) {
|
||||||
actor {
|
actor {
|
||||||
id
|
id
|
||||||
|
@ -320,7 +320,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
deleteFeedToken(
|
deleteFeedToken(
|
||||||
token: "#{uuid}"
|
token: "#{ShortUUID.encode!(uuid)}"
|
||||||
) {
|
) {
|
||||||
actor {
|
actor {
|
||||||
id
|
id
|
||||||
|
|
|
@ -58,7 +58,7 @@ defmodule Mobilizon.Service.ICalendarTest do
|
||||||
event = insert(:event)
|
event = insert(:event)
|
||||||
insert(:participant, event: event, actor: actor, role: :participant)
|
insert(:participant, event: event, actor: actor, role: :participant)
|
||||||
|
|
||||||
{:commit, ics} = ICalendarService.create_cache("token_#{token}")
|
{:commit, ics} = ICalendarService.create_cache("token_#{ShortUUID.encode!(token)}")
|
||||||
assert ics =~ event.title
|
assert ics =~ event.title
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -225,7 +225,7 @@ defmodule Mobilizon.Web.FeedControllerTest do
|
||||||
conn
|
conn
|
||||||
|> get(
|
|> get(
|
||||||
Endpoint
|
Endpoint
|
||||||
|> Routes.feed_url(:going, feed_token.token, "atom")
|
|> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "atom")
|
||||||
|> URI.decode()
|
|> URI.decode()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ defmodule Mobilizon.Web.FeedControllerTest do
|
||||||
|> put_req_header("accept", "application/atom+xml")
|
|> put_req_header("accept", "application/atom+xml")
|
||||||
|> get(
|
|> get(
|
||||||
Endpoint
|
Endpoint
|
||||||
|> Routes.feed_url(:going, feed_token.token, "atom")
|
|> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "atom")
|
||||||
|> URI.decode()
|
|> URI.decode()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ defmodule Mobilizon.Web.FeedControllerTest do
|
||||||
|> put_req_header("accept", "text/calendar")
|
|> put_req_header("accept", "text/calendar")
|
||||||
|> get(
|
|> get(
|
||||||
Endpoint
|
Endpoint
|
||||||
|> Routes.feed_url(:going, feed_token.token, "ics")
|
|> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "ics")
|
||||||
|> URI.decode()
|
|> URI.decode()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -338,7 +338,7 @@ defmodule Mobilizon.Web.FeedControllerTest do
|
||||||
|> put_req_header("accept", "text/calendar")
|
|> put_req_header("accept", "text/calendar")
|
||||||
|> get(
|
|> get(
|
||||||
Endpoint
|
Endpoint
|
||||||
|> Routes.feed_url(:going, feed_token.token, "ics")
|
|> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "ics")
|
||||||
|> URI.decode()
|
|> URI.decode()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue