Merge branch 'fixes' into 'main'

Update schema.graphql file

Closes #1088

See merge request framasoft/mobilizon!1212
This commit is contained in:
Thomas Citharel 2022-04-20 17:55:03 +00:00
commit 04ec8fe2d3
44 changed files with 925 additions and 713 deletions

View File

@ -240,8 +240,6 @@ build-docker-tag:
package-app: package-app:
image: mobilizon/buildpack:1.13.4-erlang-24.3.3-${OS} image: mobilizon/buildpack:1.13.4-erlang-24.3.3-${OS}
stage: package stage: package
before_script:
- apt-get update && apt-get install -yq build-essential git curl cmake
variables: &release-variables variables: &release-variables
MIX_ENV: "prod" MIX_ENV: "prod"
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive

View File

@ -1,16 +1,19 @@
<template> <template>
<div class="actor-inline"> <div class="inline-flex items-start">
<div class="actor-avatar"> <div class="flex-none mr-2">
<figure class="image is-24x24" v-if="actor.avatar"> <figure class="image is-48x48" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" /> <img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure> </figure>
<b-icon v-else size="is-medium" icon="account-circle" /> <b-icon v-else size="is-large" icon="account-circle" />
</div> </div>
<div class="actor-name"> <div class="flex-auto">
<p> <p class="text-base line-clamp-3 md:line-clamp-2 max-w-xl">
{{ displayName(actor) }} {{ displayName(actor) }}
</p> </p>
<p class="text-sm text-gray-500 truncate">
@{{ usernameWithDomain(actor) }}
</p>
</div> </div>
</div> </div>
</template> </template>

View File

@ -190,7 +190,7 @@ export default class ShareEventModal extends Vue {
} }
get mastodonShareUrl(): string { get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent( return `https://toot.kytta.dev/?text=${encodeURIComponent(
this.basicTextToEncode this.basicTextToEncode
)}`; )}`;
} }

View File

@ -10,7 +10,7 @@
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue"; import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
import { FETCH_GROUP } from "@/graphql/group"; import { FETCH_GROUP } from "@/graphql/group";
import { IGroup } from "@/types/actor"; import { displayName, IGroup } from "@/types/actor";
@Component({ @Component({
components: { RedirectWithAccount }, components: { RedirectWithAccount },
@ -52,7 +52,7 @@ export default class JoinGroupWithAccount extends Vue {
} }
get groupTitle(): undefined | string { get groupTitle(): undefined | string {
return this.group?.name || this.group?.preferredUsername; return this.group && displayName(this.group);
} }
sentence = this.$t( sentence = this.$t(

View File

@ -117,7 +117,7 @@ import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { GroupVisibility } from "@/types/enums"; import { GroupVisibility } from "@/types/enums";
import DiasporaLogo from "../Share/DiasporaLogo.vue"; import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue"; import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/MastodonLogo.vue"; import TelegramLogo from "../Share/TelegramLogo.vue";
import { displayName, IGroup } from "@/types/actor"; import { displayName, IGroup } from "@/types/actor";
@Component({ @Component({
@ -177,7 +177,7 @@ export default class ShareGroupModal extends Vue {
} }
get mastodonShareUrl(): string { get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent( return `https://toot.kytta.dev/?text=${encodeURIComponent(
this.basicTextToEncode this.basicTextToEncode
)}`; )}`;
} }

View File

@ -9,7 +9,7 @@
:rounded="true" :rounded="true"
style="height: 120px" style="height: 120px"
/> />
<div class="title-info-wrapper has-text-grey-dark"> <div class="title-info-wrapper has-text-grey-dark px-1">
<h3 class="post-minimalist-title" :lang="post.language"> <h3 class="post-minimalist-title" :lang="post.language">
{{ post.title }} {{ post.title }}
</h3> </h3>

View File

@ -179,7 +179,7 @@ export default class SharePostModal extends Vue {
} }
get mastodonShareUrl(): string { get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent( return `https://toot.kytta.dev/?text=${encodeURIComponent(
this.basicTextToEncode this.basicTextToEncode
)}`; )}`;
} }

View File

@ -9,7 +9,7 @@
:class="{ 'is-titleless': !title }" :class="{ 'is-titleless': !title }"
> >
<div class="media"> <div class="media">
<div class="media-left"> <div class="media-left hidden md:block">
<b-icon icon="alert" type="is-warning" size="is-large" /> <b-icon icon="alert" type="is-warning" size="is-large" />
</div> </div>
<div class="media-content"> <div class="media-content">

View File

@ -9,7 +9,7 @@
<!-- @slot Mandatory title --> <!-- @slot Mandatory title -->
<slot /> <slot />
</h2> </h2>
<p v-show="$slots.desc"> <p v-show="$slots.desc" :class="descriptionClasses">
<!-- @slot Optional description --> <!-- @slot Optional description -->
<slot name="desc" /> <slot name="desc" />
</p> </p>
@ -21,6 +21,8 @@ import { Component, Prop, Vue } from "vue-property-decorator";
@Component @Component
export default class EmptyContent extends Vue { export default class EmptyContent extends Vue {
@Prop({ type: String, required: true }) icon!: string; @Prop({ type: String, required: true }) icon!: string;
@Prop({ type: String, required: false, default: "" })
descriptionClasses!: string;
@Prop({ type: Boolean, required: false, default: false }) inline!: boolean; @Prop({ type: Boolean, required: false, default: false }) inline!: boolean;
@Prop({ type: Boolean, required: false, default: false }) center!: boolean; @Prop({ type: Boolean, required: false, default: false }) center!: boolean;
} }

View File

@ -1321,5 +1321,14 @@
"You may now close this page or {return_to_the_homepage}.": "You may now close this page or {return_to_the_homepage}.", "You may now close this page or {return_to_the_homepage}.": "You may now close this page or {return_to_the_homepage}.",
"This group is a remote group, it's possible the original instance has more informations.": "This group is a remote group, it's possible the original instance has more informations.", "This group is a remote group, it's possible the original instance has more informations.": "This group is a remote group, it's possible the original instance has more informations.",
"View the group profile on the original instance": "View the group profile on the original instance", "View the group profile on the original instance": "View the group profile on the original instance",
"View past events": "View past events" "View past events": "View past events",
"Get informed of the upcoming public events": "Get informed of the upcoming public events",
"Join": "Join",
"Become part of the community and start organizing events": "Become part of the community and start organizing events",
"Follow requests will be approved by a group moderator": "Follow requests will be approved by a group moderator",
"Follow request pending approval": "Follow request pending approval",
"Your membership is pending approval": "Your membership is pending approval",
"Activate notifications": "Activate notifications",
"Deactivate notifications": "Deactivate notifications",
"Membership requests will be approved by a group moderator": "Membership requests will be approved by a group moderator"
} }

View File

@ -1312,5 +1312,14 @@
"No instance found.": "Aucune instance trouvée.", "No instance found.": "Aucune instance trouvée.",
"This group is a remote group, it's possible the original instance has more informations.": "Ce groupe est un groupe distant, il est possible que l'instance d'origine ait plus d'informations.", "This group is a remote group, it's possible the original instance has more informations.": "Ce groupe est un groupe distant, il est possible que l'instance d'origine ait plus d'informations.",
"View the group profile on the original instance": "Afficher le profil du groupe sur l'instance d'origine", "View the group profile on the original instance": "Afficher le profil du groupe sur l'instance d'origine",
"View past events": "Voir les événements passés" "View past events": "Voir les événements passés",
"Get informed of the upcoming public events": "Soyez informé⋅e des événements publics à venir",
"Join": "Rejoindre",
"Become part of the community and start organizing events": "Faites partie de la communauté et commencez à organiser des événements",
"Follow requests will be approved by a group moderator": "Les demandes de suivi seront approuvées par un⋅e modérateur⋅ice du groupe",
"Follow request pending approval": "Demande de suivi en attente d'approbation",
"Your membership is pending approval": "Votre adhésion est en attente d'approbation",
"Activate notifications": "Activer les notifications",
"Deactivate notifications": "Désactiver les notifications",
"Membership requests will be approved by a group moderator": "Les demandes d'adhésion seront approuvées par un⋅e modérateur⋅ice du groupe"
} }

View File

@ -68,7 +68,7 @@ export function usernameWithDomain(actor: IActor, force = false): string {
} }
export function displayName(actor: IActor): string { export function displayName(actor: IActor): string {
return actor.name != null && actor.name !== "" return actor && actor.name != null && actor.name !== ""
? actor.name ? actor.name
: usernameWithDomain(actor); : usernameWithDomain(actor);
} }

View File

@ -4,7 +4,7 @@
<p class="modal-card-title">{{ $t("Pick an identity") }}</p> <p class="modal-card-title">{{ $t("Pick an identity") }}</p>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div class="list is-hoverable"> <div class="list is-hoverable list-none">
<a <a
class="list-item" class="list-item"
v-for="identity in identities" v-for="identity in identities"
@ -12,7 +12,7 @@
:class="{ :class="{
'is-active': currentIdentity && identity.id === currentIdentity.id, 'is-active': currentIdentity && identity.id === currentIdentity.id,
}" }"
@click="changeCurrentIdentity(identity)" @click="currentIdentity = identity"
> >
<div class="media"> <div class="media">
<img <img
@ -60,10 +60,11 @@ export default class IdentityPicker extends Vue {
identities: IActor[] = []; identities: IActor[] = [];
currentIdentity: IActor = this.value; get currentIdentity(): IActor {
return this.value;
}
changeCurrentIdentity(identity: IActor): void { set currentIdentity(identity: IActor) {
this.currentIdentity = identity;
this.$emit("input", identity); this.$emit("input", identity);
} }
} }

View File

@ -8,7 +8,9 @@
]" ]"
/> />
<h1 class="text-2xl">{{ instance.domain }}</h1> <h1 class="text-2xl">{{ instance.domain }}</h1>
<div class="grid md:grid-cols-4 gap-2 content-center text-center mt-2"> <div
class="grid md:grid-cols-2 xl:grid-cols-4 gap-2 content-center text-center mt-2"
>
<div class="bg-gray-50 rounded-xl p-8"> <div class="bg-gray-50 rounded-xl p-8">
<router-link <router-link
:to="{ :to="{
@ -64,7 +66,7 @@
<span class="text-sm block">{{ $t("Uploaded media size") }}</span> <span class="text-sm block">{{ $t("Uploaded media size") }}</span>
</div> </div>
</div> </div>
<div class="mt-3 grid md:grid-cols-2 gap-4" v-if="instance.hasRelay"> <div class="mt-3 grid xl:grid-cols-2 gap-4" v-if="instance.hasRelay">
<div class="border bg-white p-6 shadow-md rounded-md"> <div class="border bg-white p-6 shadow-md rounded-md">
<button <button
@click="removeInstanceFollow" @click="removeInstanceFollow"
@ -88,7 +90,7 @@
{{ $t("Follow instance") }} {{ $t("Follow instance") }}
</button> </button>
</div> </div>
<div class="border bg-white p-6 shadow-md rounded-md"> <div class="border bg-white p-6 shadow-md rounded-md flex flex-col gap-2">
<button <button
@click="acceptInstance" @click="acceptInstance"
v-if="instance.followerStatus == InstanceFollowStatus.PENDING" v-if="instance.followerStatus == InstanceFollowStatus.PENDING"
@ -98,12 +100,12 @@
</button> </button>
<button <button
@click="rejectInstance" @click="rejectInstance"
v-else-if="instance.followerStatus != InstanceFollowStatus.NONE" v-if="instance.followerStatus != InstanceFollowStatus.NONE"
class="bg-red-700 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto" class="bg-red-700 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
> >
{{ $t("Reject follow") }} {{ $t("Reject follow") }}
</button> </button>
<p v-else> <p v-if="instance.followerStatus == InstanceFollowStatus.NONE">
{{ $t("This instance doesn't follow yours.") }} {{ $t("This instance doesn't follow yours.") }}
</p> </p>
</div> </div>
@ -124,7 +126,6 @@ import {
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { formatBytes } from "@/utils/datetime"; import { formatBytes } from "@/utils/datetime";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { IInstance } from "@/types/instance.model"; import { IInstance } from "@/types/instance.model";
import { ApolloCache, gql, Reference } from "@apollo/client/core"; import { ApolloCache, gql, Reference } from "@apollo/client/core";
import { InstanceFollowStatus } from "@/types/enums"; import { InstanceFollowStatus } from "@/types/enums";
@ -154,38 +155,61 @@ export default class Instance extends Vue {
async acceptInstance(): Promise<void> { async acceptInstance(): Promise<void> {
try { try {
const { instance } = this;
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: ACCEPT_RELAY, mutation: ACCEPT_RELAY,
variables: { variables: {
address: `relay@${this.domain}`, address: `relay@${this.domain}`,
}, },
update(cache: ApolloCache<any>) {
cache.writeFragment({
id: cache.identify(instance as unknown as Reference),
fragment: gql`
fragment InstanceFollowerStatus on Instance {
followerStatus
}
`,
data: {
followerStatus: InstanceFollowStatus.APPROVED,
},
}); });
} catch (e: any) { },
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
}); });
} catch (error: any) {
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
} }
} }
} }
/**
* Reject instance follow
*/
async rejectInstance(): Promise<void> { async rejectInstance(): Promise<void> {
try { try {
const { instance } = this;
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: REJECT_RELAY, mutation: REJECT_RELAY,
variables: { variables: {
address: `relay@${this.domain}`, address: `relay@${this.domain}`,
}, },
update(cache: ApolloCache<any>) {
cache.writeFragment({
id: cache.identify(instance as unknown as Reference),
fragment: gql`
fragment InstanceFollowerStatus on Instance {
followerStatus
}
`,
data: {
followerStatus: InstanceFollowStatus.NONE,
},
}); });
} catch (e: any) { },
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
}); });
} catch (error: any) {
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
} }
} }
} }
@ -199,17 +223,16 @@ export default class Instance extends Vue {
domain: this.domain, domain: this.domain,
}, },
}); });
} catch (err: any) { } catch (error: any) {
if (err.message) { if (error.graphQLErrors && error.graphQLErrors.length > 0) {
Snackbar.open({ this.$notifier.error(error.graphQLErrors[0].message);
message: err.message,
type: "is-danger",
position: "is-bottom",
});
} }
} }
} }
/**
* Stop following instance
*/
async removeInstanceFollow(): Promise<void> { async removeInstanceFollow(): Promise<void> {
const { instance } = this; const { instance } = this;
try { try {
@ -232,13 +255,9 @@ export default class Instance extends Vue {
}); });
}, },
}); });
} catch (e: any) { } catch (error: any) {
if (e.message) { if (error.graphQLErrors && error.graphQLErrors.length > 0) {
Snackbar.open({ this.$notifier.error(error.graphQLErrors[0].message);
message: e.message,
type: "is-danger",
position: "is-bottom",
});
} }
} }
} }

View File

@ -124,6 +124,17 @@
</p> </p>
</div> </div>
</router-link> </router-link>
<b-pagination
v-show="instances.total > INSTANCES_PAGE_LIMIT"
:total="instances.total"
v-model="instancePage"
:per-page="INSTANCES_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div> </div>
<div v-else-if="instances && instances.elements.length == 0"> <div v-else-if="instances && instances.elements.length == 0">
<empty-content icon="lan-disconnect" :inline="true"> <empty-content icon="lan-disconnect" :inline="true">
@ -163,6 +174,8 @@ import {
import { SnackbarProgrammatic as Snackbar } from "buefy"; import { SnackbarProgrammatic as Snackbar } from "buefy";
const { isNavigationFailure, NavigationFailureType } = VueRouter; const { isNavigationFailure, NavigationFailureType } = VueRouter;
const INSTANCES_PAGE_LIMIT = 10;
@Component({ @Component({
apollo: { apollo: {
instances: { instances: {
@ -171,7 +184,7 @@ const { isNavigationFailure, NavigationFailureType } = VueRouter;
variables() { variables() {
return { return {
page: this.instancePage, page: this.instancePage,
limit: 10, limit: INSTANCES_PAGE_LIMIT,
filterDomain: this.filterDomain, filterDomain: this.filterDomain,
filterFollowStatus: this.followStatus, filterFollowStatus: this.followStatus,
}; };
@ -204,6 +217,8 @@ export default class Follows extends Vue {
InstanceFollowStatus = InstanceFollowStatus; InstanceFollowStatus = InstanceFollowStatus;
INSTANCES_PAGE_LIMIT = INSTANCES_PAGE_LIMIT;
data(): Record<string, unknown> { data(): Record<string, unknown> {
return { return {
debouncedUpdateDomainFilter: debounce(this.updateDomainFilter, 500), debouncedUpdateDomainFilter: debounce(this.updateDomainFilter, 500),

View File

@ -18,7 +18,7 @@
<h1 class="title" v-if="group"> <h1 class="title" v-if="group">
{{ {{
$t("{group}'s events", { $t("{group}'s events", {
group: group.name || group.preferredUsername, group: displayName(group),
}) })
}} }}
</h1> </h1>
@ -78,7 +78,7 @@
<b-pagination <b-pagination
class="mt-4" class="mt-4"
:total="group.organizedEvents.total" :total="group.organizedEvents.total"
v-model="eventsPage" v-model="page"
:per-page="EVENTS_PAGE_LIMIT" :per-page="EVENTS_PAGE_LIMIT"
:aria-next-label="$t('Next page')" :aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')" :aria-previous-label="$t('Previous page')"
@ -91,17 +91,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import Subtitle from "@/components/Utils/Subtitle.vue"; import Subtitle from "@/components/Utils/Subtitle.vue";
import GroupedMultiEventMinimalistCard from "@/components/Event/GroupedMultiEventMinimalistCard.vue"; import GroupedMultiEventMinimalistCard from "@/components/Event/GroupedMultiEventMinimalistCard.vue";
import { PERSON_MEMBERSHIPS } from "@/graphql/actor"; import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
import GroupMixin from "@/mixins/group";
import { IMember } from "@/types/actor/member.model"; import { IMember } from "@/types/actor/member.model";
import { FETCH_GROUP_EVENTS } from "@/graphql/event"; import { FETCH_GROUP_EVENTS } from "@/graphql/event";
import EmptyContent from "../../components/Utils/EmptyContent.vue"; import EmptyContent from "../../components/Utils/EmptyContent.vue";
import { displayName, usernameWithDomain } from "../../types/actor"; import { displayName, IGroup, usernameWithDomain } from "../../types/actor";
const EVENTS_PAGE_LIMIT = 10; const EVENTS_PAGE_LIMIT = 10;
@ -127,10 +125,11 @@ const EVENTS_PAGE_LIMIT = 10;
name: this.$route.params.preferredUsername, name: this.$route.params.preferredUsername,
beforeDateTime: this.showPassedEvents ? new Date() : null, beforeDateTime: this.showPassedEvents ? new Date() : null,
afterDateTime: this.showPassedEvents ? null : new Date(), afterDateTime: this.showPassedEvents ? null : new Date(),
organisedEventsPage: this.eventsPage, organisedEventsPage: this.page,
organisedEventsLimit: EVENTS_PAGE_LIMIT, organisedEventsLimit: EVENTS_PAGE_LIMIT,
}; };
}, },
update: (data) => data.group,
}, },
}, },
components: { components: {
@ -144,15 +143,27 @@ const EVENTS_PAGE_LIMIT = 10;
const { group } = this; const { group } = this;
return { return {
title: this.$t("{group} events", { title: this.$t("{group} events", {
group: group?.name || usernameWithDomain(group), group: displayName(group),
}) as string, }) as string,
}; };
}, },
}) })
export default class GroupEvents extends mixins(GroupMixin) { export default class GroupEvents extends Vue {
group!: IGroup;
memberships!: IMember[]; memberships!: IMember[];
eventsPage = 1; get page(): number {
return parseInt((this.$route.query.page as string) || "1", 10);
}
set page(page: number) {
this.$router.push({
name: RouteName.GROUP_EVENTS,
query: { ...this.$route.query, page: page.toString() },
});
this.$apollo.queries.group.refetch();
}
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
@ -170,14 +181,11 @@ export default class GroupEvents extends mixins(GroupMixin) {
} }
get showPassedEvents(): boolean { get showPassedEvents(): boolean {
return ( return this.$route.query.future === "false";
this.$route.query.future !== undefined &&
this.$route.query.future.toString() === "false"
);
} }
set showPassedEvents(value: boolean) { set showPassedEvents(value: boolean) {
this.$router.push({ query: { future: this.showPassedEvents.toString() } }); this.$router.replace({ query: { future: (!value).toString() } });
} }
} }
</script> </script>

View File

@ -27,11 +27,11 @@
<div class="title-container"> <div class="title-container">
<h1 v-if="group.name">{{ group.name }}</h1> <h1 v-if="group.name">{{ group.name }}</h1>
<b-skeleton v-else :animated="true" /> <b-skeleton v-else :animated="true" />
<small <span
dir="ltr" dir="ltr"
class="has-text-grey-dark" class="has-text-grey-dark"
v-if="group.preferredUsername" v-if="group.preferredUsername"
>@{{ usernameWithDomain(group) }}</small >@{{ usernameWithDomain(group) }}</span
> >
<b-skeleton v-else :animated="true" /> <b-skeleton v-else :animated="true" />
<br /> <br />
@ -78,7 +78,7 @@
> >
</p> </p>
</div> </div>
<div class="buttons"> <div class="flex gap-2">
<b-button <b-button
outlined outlined
icon-left="timeline-text" icon-left="timeline-text"
@ -101,78 +101,123 @@
}" }"
>{{ $t("Group settings") }}</b-button >{{ $t("Group settings") }}</b-button
> >
<b-tooltip <b-dropdown
v-if=" aria-role="list"
(!isCurrentActorAGroupMember || previewPublic) && trap-focus
group.openness === Openness.INVITE_ONLY v-show="showJoinButton && showFollowButton"
"
:label="$t('This group is invite-only')"
position="is-bottom"
>
<b-button disabled type="is-primary">{{
$t("Join group")
}}</b-button></b-tooltip
> >
<template #trigger>
<b-button <b-button
v-else-if=" :label="$t('Follow')"
((!isCurrentActorAGroupMember &&
!isCurrentActorAPendingGroupMember) ||
previewPublic) &&
currentActor.id
"
@click="joinGroup"
@keyup.enter="joinGroup"
type="is-primary" type="is-primary"
:disabled="previewPublic" icon-left="rss"
>{{ $t("Join group") }}</b-button icon-right="menu-down"
/>
</template>
<b-dropdown-item
aria-role="listitem"
class="p-0"
custom
:focusable="false"
:disabled="
isCurrentActorPendingFollow && currentActor.id !== undefined
"
> >
<button class="media py-4 px-2 w-full" @click="followGroup">
<b-icon class="media-left" icon="rss" />
<div class="media-content">
<h3 class="font-medium text-lg">{{ $t("Follow") }}</h3>
<p class="whitespace-normal md:whitespace-nowrap text-sm">
{{ $t("Get informed of the upcoming public events") }}
</p>
<p
v-if="
doesGroupManuallyApprovesFollowers &&
!isCurrentActorPendingFollow
"
class="whitespace-normal md:whitespace-nowrap text-sm italic"
>
{{
$t(
"Follow requests will be approved by a group moderator"
)
}}
</p>
<p
v-if="isCurrentActorPendingFollow && currentActor.id"
class="whitespace-normal md:whitespace-nowrap text-sm italic"
>
{{ $t("Follow request pending approval") }}
</p>
</div>
</button>
</b-dropdown-item>
<b-dropdown-item
aria-role="listitem"
class="p-0 border-t border-solid"
custom
:focusable="false"
:disabled="
isGroupInviteOnly || isCurrentActorAPendingGroupMember
"
>
<button class="media py-4 px-2 w-full" @click="joinGroup">
<b-icon
class="media-left"
icon="account-multiple-plus"
></b-icon>
<div class="media-content">
<h3 class="font-medium text-lg">{{ $t("Join") }}</h3>
<div v-if="showJoinButton">
<p
class="whitespace-normal md:whitespace-nowrap text-sm"
>
{{
$t(
"Become part of the community and start organizing events"
)
}}
</p>
<p
v-if="isGroupInviteOnly"
class="whitespace-normal md:whitespace-nowrap text-sm italic"
>
{{ $t("This group is invite-only") }}
</p>
<p
v-if="
areGroupMembershipsModerated &&
!isCurrentActorAPendingGroupMember
"
class="whitespace-normal md:whitespace-nowrap text-sm italic"
>
{{
$t(
"Membership requests will be approved by a group moderator"
)
}}
</p>
<p
v-if="isCurrentActorAPendingGroupMember"
class="whitespace-normal md:whitespace-nowrap text-sm italic"
>
{{ $t("Your membership is pending approval") }}
</p>
</div>
</div>
</button>
</b-dropdown-item>
</b-dropdown>
<b-button <b-button
outlined outlined
v-else-if="isCurrentActorAPendingGroupMember" v-if="isCurrentActorAPendingGroupMember"
@click="leaveGroup" @click="leaveGroup"
@keyup.enter="leaveGroup" @keyup.enter="leaveGroup"
type="is-primary" type="is-primary"
>{{ $t("Cancel membership request") }}</b-button >{{ $t("Cancel membership request") }}</b-button
> >
<b-button
tag="router-link"
:to="{
name: RouteName.GROUP_JOIN,
params: { preferredUsername: usernameWithDomain(group) },
}"
v-else-if="!isCurrentActorAGroupMember || previewPublic"
:disabled="previewPublic"
type="is-primary"
>{{ $t("Join group") }}</b-button
>
<b-button
v-if="
((!isCurrentActorFollowing && !isCurrentActorAGroupMember) ||
previewPublic) &&
!isCurrentActorPendingFollow &&
currentActor.id
"
@click="followGroup"
@keyup.enter="followGroup"
type="is-primary"
:disabled="isCurrentActorPendingFollow"
>{{ $t("Follow") }}</b-button
>
<b-button
tag="router-link"
:to="{
name: RouteName.GROUP_FOLLOW,
params: { preferredUsername: usernameWithDomain(group) },
}"
v-else-if="
!isCurrentActorPendingFollow &&
!isCurrentActorFollowing &&
previewPublic
"
:disabled="previewPublic"
type="is-primary"
>{{ $t("Follow") }}</b-button
>
<b-button <b-button
outlined outlined
v-if="isCurrentActorPendingFollow && currentActor.id" v-if="isCurrentActorPendingFollow && currentActor.id"
@ -192,12 +237,20 @@
v-if="isCurrentActorFollowing" v-if="isCurrentActorFollowing"
@click="toggleFollowNotify" @click="toggleFollowNotify"
@keyup.enter="toggleFollowNotify" @keyup.enter="toggleFollowNotify"
class="notification-button p-1.5"
outlined
:icon-left=" :icon-left="
isCurrentActorFollowingNotify isCurrentActorFollowingNotify
? 'bell-outline' ? 'bell-outline'
: 'bell-off-outline' : 'bell-off-outline'
" "
></b-button> >
<span class="sr-only">{{
isCurrentActorFollowingNotify
? $t("Activate notifications")
: $t("Deactivate notifications")
}}</span>
</b-button>
<b-button <b-button
outlined outlined
icon-left="share" icon-left="share"
@ -308,28 +361,6 @@
) )
}} }}
</b-message> </b-message>
<b-message
v-if="
!isCurrentActorAGroupMember &&
!isCurrentActorAPendingGroupMember &&
!isCurrentActorPendingFollow &&
!isCurrentActorFollowing
"
type="is-info"
has-icon
class="m-3"
>
<i18n
path="Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts."
>
<b slot="group_upcoming_public_events">{{
$t("group's upcoming public events")
}}</b>
<b slot="access_to_group_private_content_as_well">{{
$t("access to the group's private content as well")
}}</b>
</i18n>
</b-message>
</div> </div>
</header> </header>
</div> </div>
@ -506,6 +537,12 @@
$t("View full profile") $t("View full profile")
}}</a> }}</a>
</b-message> </b-message>
<event-metadata-block
:title="$t('About')"
v-if="group.summary && group.summary !== '<p></p>'"
>
<div dir="auto" v-html="group.summary" />
</event-metadata-block>
<event-metadata-block :title="$t('Members')" icon="account-group"> <event-metadata-block :title="$t('Members')" icon="account-group">
{{ {{
$tc("{count} members", group.members.total, { $tc("{count} members", group.members.total, {
@ -553,17 +590,6 @@
</div> </div>
</aside> </aside>
<div class="main-content"> <div class="main-content">
<section>
<subtitle>{{ $t("About") }}</subtitle>
<div
dir="auto"
v-html="group.summary"
v-if="group.summary && group.summary !== '<p></p>'"
/>
<empty-content v-else-if="group" icon="image-text" :inline="true">
{{ $t("This group doesn't have a description yet.") }}
</empty-content>
</section>
<section> <section>
<subtitle>{{ $t("Upcoming events") }}</subtitle> <subtitle>{{ $t("Upcoming events") }}</subtitle>
<div <div
@ -577,7 +603,12 @@
class="organized-event" class="organized-event"
/> />
</div> </div>
<empty-content v-else-if="group" icon="calendar" :inline="true"> <empty-content
v-else-if="group"
icon="calendar"
:inline="true"
description-classes="flex flex-col items-stretch"
>
{{ $t("No public upcoming events") }} {{ $t("No public upcoming events") }}
<template #desc> <template #desc>
<template v-if="isCurrentActorFollowing"> <template v-if="isCurrentActorFollowing">
@ -594,7 +625,7 @@
</template> </template>
<b-button <b-button
tag="router-link" tag="router-link"
class="my-2" class="my-2 self-center"
type="is-text" type="is-text"
:to="{ :to="{
name: RouteName.GROUP_EVENTS, name: RouteName.GROUP_EVENTS,
@ -621,8 +652,8 @@
> >
</div> </div>
</section> </section>
<section> <section class="flex flex-col items-stretch">
<subtitle>{{ $t("Latest posts") }}</subtitle> <subtitle class="ml-0">{{ $t("Latest posts") }}</subtitle>
<multi-post-list-item <multi-post-list-item
v-if=" v-if="
@ -642,13 +673,16 @@
{{ $t("No posts yet") }} {{ $t("No posts yet") }}
</empty-content> </empty-content>
<b-skeleton animated v-else-if="$apollo.loading"></b-skeleton> <b-skeleton animated v-else-if="$apollo.loading"></b-skeleton>
<router-link <b-button
class="self-center my-2"
v-if="posts.total > 0" v-if="posts.total > 0"
tag="router-link"
type="is-text"
:to="{ :to="{
name: RouteName.POSTS, name: RouteName.POSTS,
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
>{{ $t("View all posts") }}</router-link >{{ $t("View all posts") }}</b-button
> >
</section> </section>
</div> </div>
@ -806,11 +840,19 @@ export default class Group extends mixins(GroupMixin) {
} }
async joinGroup(): Promise<void> { async joinGroup(): Promise<void> {
if (!this.currentActor?.id) {
this.$router.push({
name: RouteName.GROUP_JOIN,
params: { preferredUsername: usernameWithDomain(this.group) },
});
return;
}
try {
const [group, currentActorId] = [ const [group, currentActorId] = [
usernameWithDomain(this.group), usernameWithDomain(this.group),
this.currentActor.id, this.currentActor.id,
]; ];
this.$apollo.mutate({ await this.$apollo.mutate({
mutation: JOIN_GROUP, mutation: JOIN_GROUP,
variables: { variables: {
groupId: this.group.id, groupId: this.group.id,
@ -825,6 +867,11 @@ export default class Group extends mixins(GroupMixin) {
}, },
], ],
}); });
} catch (error: any) {
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
}
}
} }
protected async openLeaveGroupModal(): Promise<void> { protected async openLeaveGroupModal(): Promise<void> {
@ -870,6 +917,13 @@ export default class Group extends mixins(GroupMixin) {
} }
async followGroup(): Promise<void> { async followGroup(): Promise<void> {
if (!this.currentActor?.id) {
this.$router.push({
name: RouteName.GROUP_FOLLOW,
params: { preferredUsername: usernameWithDomain(this.group) },
});
return;
}
try { try {
const [group, currentActorId] = [ const [group, currentActorId] = [
usernameWithDomain(this.group), usernameWithDomain(this.group),
@ -1088,6 +1142,41 @@ export default class Group extends mixins(GroupMixin) {
}), }),
}; };
} }
get showFollowButton(): boolean {
return (
(!this.isCurrentActorFollowing || this.previewPublic) &&
this.currentActor?.id !== undefined
);
}
get showJoinButton(): boolean {
return (
(!this.isCurrentActorAGroupMember || this.previewPublic) &&
this.currentActor?.id !== undefined
);
}
get isGroupInviteOnly(): boolean {
return (
(!this.isCurrentActorAGroupMember || this.previewPublic) &&
this.group?.openness === Openness.INVITE_ONLY
);
}
get areGroupMembershipsModerated(): boolean {
return (
(!this.isCurrentActorAGroupMember || this.previewPublic) &&
this.group?.openness === Openness.MODERATED
);
}
get doesGroupManuallyApprovesFollowers(): boolean {
return (
(!this.isCurrentActorAGroupMember || this.previewPublic) &&
this.group?.manuallyApprovesFollowers
);
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -1380,4 +1469,7 @@ div.container {
height: 60vh; height: 60vh;
width: 100%; width: 100%;
} }
button.button.notification-button ::v-deep span.icon.is-small {
margin: 0 !important;
}
</style> </style>

View File

@ -15,7 +15,9 @@
v-if="post.draft" v-if="post.draft"
>{{ $t("Draft") }}</b-tag >{{ $t("Draft") }}</b-tag
> >
<h1 class="title" :lang="post.language">{{ post.title }}</h1> <h1 class="title text-3xl" :lang="post.language">
{{ post.title }}
</h1>
</div> </div>
<p class="metadata"> <p class="metadata">
<router-link <router-link
@ -441,7 +443,6 @@ article.post {
h1.title { h1.title {
margin: 0; margin: 0;
font-weight: 500; font-weight: 500;
font-size: 38px;
font-family: "Roboto", "Helvetica", "Arial", serif; font-family: "Roboto", "Helvetica", "Arial", serif;
} }

View File

@ -67,7 +67,7 @@ exports[`CommentTree renders an empty comment tree 1`] = `
</article> </article>
</form> </form>
<transition-group-stub tag="div" name="comment-empty-list"> <transition-group-stub tag="div" name="comment-empty-list">
<empty-content-stub icon="comment" inline="true"><span>No comments yet</span></empty-content-stub> <empty-content-stub icon="comment" descriptionclasses="" inline="true"><span>No comments yet</span></empty-content-stub>
</transition-group-stub> </transition-group-stub>
</div> </div>
`; `;

View File

@ -3,7 +3,7 @@
exports[`PostListItem renders post list item with basic informations 1`] = ` exports[`PostListItem renders post list item with basic informations 1`] = `
<a href="/p/my-blog-post-some-uuid" class="post-minimalist-card-wrapper" dir="auto"> <a href="/p/my-blog-post-some-uuid" class="post-minimalist-card-wrapper" dir="auto">
<!----> <!---->
<div class="title-info-wrapper has-text-grey-dark"> <div class="title-info-wrapper has-text-grey-dark px-1">
<h3 lang="en" class="post-minimalist-title"> <h3 lang="en" class="post-minimalist-title">
My Blog Post My Blog Post
</h3> </h3>
@ -17,7 +17,7 @@ exports[`PostListItem renders post list item with basic informations 1`] = `
exports[`PostListItem renders post list item with publisher name 1`] = ` exports[`PostListItem renders post list item with publisher name 1`] = `
<a href="/p/my-blog-post-some-uuid" class="post-minimalist-card-wrapper" dir="auto"> <a href="/p/my-blog-post-some-uuid" class="post-minimalist-card-wrapper" dir="auto">
<!----> <!---->
<div class="title-info-wrapper has-text-grey-dark"> <div class="title-info-wrapper has-text-grey-dark px-1">
<h3 lang="en" class="post-minimalist-title"> <h3 lang="en" class="post-minimalist-title">
My Blog Post My Blog Post
</h3> </h3>
@ -31,7 +31,7 @@ exports[`PostListItem renders post list item with publisher name 1`] = `
exports[`PostListItem renders post list item with tags 1`] = ` exports[`PostListItem renders post list item with tags 1`] = `
<a href="/p/my-blog-post-some-uuid" class="post-minimalist-card-wrapper" dir="auto"> <a href="/p/my-blog-post-some-uuid" class="post-minimalist-card-wrapper" dir="auto">
<!----> <!---->
<div class="title-info-wrapper has-text-grey-dark"> <div class="title-info-wrapper has-text-grey-dark px-1">
<h3 lang="en" class="post-minimalist-title"> <h3 lang="en" class="post-minimalist-title">
My Blog Post My Blog Post
</h3> </h3>

View File

@ -1297,9 +1297,9 @@
integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==
"@jridgewell/trace-mapping@^0.3.0": "@jridgewell/trace-mapping@^0.3.0":
version "0.3.4" version "0.3.7"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.7.tgz#941982134e9b7fad031c857ccfc4a0634fc6a471"
integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== integrity sha512-8XC0l0PwCbdg2Uc8zIIf6djNX3lYiz9GqQlC1LJ9WQvTYvcfP8IA9K2IKRnPm5tAX6X/+orF+WwKZ0doGcgJlg==
dependencies: dependencies:
"@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/sourcemap-codec" "^1.4.10"
@ -1929,9 +1929,9 @@
"@types/geojson" "*" "@types/geojson" "*"
"@types/lodash@^4.14.141": "@types/lodash@^4.14.141":
version "4.14.181" version "4.14.182"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag== integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
"@types/mime@^1": "@types/mime@^1":
version "1.3.2" version "1.3.2"
@ -1949,9 +1949,9 @@
integrity sha512-rr20mmx41OkWx4q5du2dv2sESR/6xH2tzScUQXwO8SiaQWa6PYTuan1nqBtA76FR9qkVfZY7nwQwZNC9StX/Ww== integrity sha512-rr20mmx41OkWx4q5du2dv2sESR/6xH2tzScUQXwO8SiaQWa6PYTuan1nqBtA76FR9qkVfZY7nwQwZNC9StX/Ww==
"@types/node@*": "@types/node@*":
version "17.0.24" version "17.0.25"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.24.tgz#20ba1bf69c1b4ab405c7a01e950c4f446b05029f" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
integrity sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g== integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
"@types/normalize-package-data@^2.4.0": "@types/normalize-package-data@^2.4.0":
version "2.4.1" version "2.4.1"
@ -2165,13 +2165,13 @@
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.3.0": "@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.3.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz#9608a4b6d0427104bccf132f058cba629a6553c0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
integrity sha512-w59GpFqDYGnWFim9p6TGJz7a3qWeENJuAKCqjGSx+Hq/bwq3RZwXYqy98KIfN85yDqz9mq6QXiY5h0FjGQLyEg== integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "5.19.0" "@typescript-eslint/scope-manager" "5.20.0"
"@typescript-eslint/type-utils" "5.19.0" "@typescript-eslint/type-utils" "5.20.0"
"@typescript-eslint/utils" "5.19.0" "@typescript-eslint/utils" "5.20.0"
debug "^4.3.2" debug "^4.3.2"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
ignore "^5.1.8" ignore "^5.1.8"
@ -2202,29 +2202,29 @@
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.3.0": "@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.3.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.19.0.tgz#05e587c1492868929b931afa0cb5579b0f728e75" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
integrity sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ== integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "5.19.0" "@typescript-eslint/scope-manager" "5.20.0"
"@typescript-eslint/types" "5.19.0" "@typescript-eslint/types" "5.20.0"
"@typescript-eslint/typescript-estree" "5.19.0" "@typescript-eslint/typescript-estree" "5.20.0"
debug "^4.3.2" debug "^4.3.2"
"@typescript-eslint/scope-manager@5.19.0": "@typescript-eslint/scope-manager@5.20.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz#97e59b0bcbcb54dbcdfba96fc103b9020bbe9cb4" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
integrity sha512-Fz+VrjLmwq5fbQn5W7cIJZ066HxLMKvDEmf4eu1tZ8O956aoX45jAuBB76miAECMTODyUxH61AQM7q4/GOMQ5g== integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
dependencies: dependencies:
"@typescript-eslint/types" "5.19.0" "@typescript-eslint/types" "5.20.0"
"@typescript-eslint/visitor-keys" "5.19.0" "@typescript-eslint/visitor-keys" "5.20.0"
"@typescript-eslint/type-utils@5.19.0": "@typescript-eslint/type-utils@5.20.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.19.0.tgz#80f2125b0dfe82494bbae1ea99f1c0186d420282" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
integrity sha512-O6XQ4RI4rQcBGshTQAYBUIGsKqrKeuIOz9v8bckXZnSeXjn/1+BDZndHLe10UplQeJLXDNbaZYrAytKNQO2T4Q== integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
dependencies: dependencies:
"@typescript-eslint/utils" "5.19.0" "@typescript-eslint/utils" "5.20.0"
debug "^4.3.2" debug "^4.3.2"
tsutils "^3.21.0" tsutils "^3.21.0"
@ -2233,10 +2233,10 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
"@typescript-eslint/types@5.19.0": "@typescript-eslint/types@5.20.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.19.0.tgz#12d3d600d754259da771806ee8b2c842d3be8d12" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
integrity sha512-zR1ithF4Iyq1wLwkDcT+qFnhs8L5VUtjgac212ftiOP/ZZUOCuuF2DeGiZZGQXGoHA50OreZqLH5NjDcDqn34w== integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
"@typescript-eslint/typescript-estree@3.10.1": "@typescript-eslint/typescript-estree@3.10.1":
version "3.10.1" version "3.10.1"
@ -2252,28 +2252,28 @@
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@5.19.0": "@typescript-eslint/typescript-estree@5.20.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.19.0.tgz#fc987b8f62883f9ea6a5b488bdbcd20d33c0025f" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
integrity sha512-dRPuD4ocXdaE1BM/dNR21elSEUPKaWgowCA0bqJ6YbYkvtrPVEvZ+zqcX5a8ECYn3q5iBSSUcBBD42ubaOp0Hw== integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
dependencies: dependencies:
"@typescript-eslint/types" "5.19.0" "@typescript-eslint/types" "5.20.0"
"@typescript-eslint/visitor-keys" "5.19.0" "@typescript-eslint/visitor-keys" "5.20.0"
debug "^4.3.2" debug "^4.3.2"
globby "^11.0.4" globby "^11.0.4"
is-glob "^4.0.3" is-glob "^4.0.3"
semver "^7.3.5" semver "^7.3.5"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/utils@5.19.0": "@typescript-eslint/utils@5.20.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.19.0.tgz#fe87f1e3003d9973ec361ed10d36b4342f1ded1e" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
integrity sha512-ZuEckdupXpXamKvFz/Ql8YnePh2ZWcwz7APICzJL985Rp5C2AYcHO62oJzIqNhAMtMK6XvrlBTZeNG8n7gS3lQ== integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
dependencies: dependencies:
"@types/json-schema" "^7.0.9" "@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.19.0" "@typescript-eslint/scope-manager" "5.20.0"
"@typescript-eslint/types" "5.19.0" "@typescript-eslint/types" "5.20.0"
"@typescript-eslint/typescript-estree" "5.19.0" "@typescript-eslint/typescript-estree" "5.20.0"
eslint-scope "^5.1.1" eslint-scope "^5.1.1"
eslint-utils "^3.0.0" eslint-utils "^3.0.0"
@ -2284,12 +2284,12 @@
dependencies: dependencies:
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/visitor-keys@5.19.0": "@typescript-eslint/visitor-keys@5.20.0":
version "5.19.0" version "5.20.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.19.0.tgz#c84ebc7f6c744707a361ca5ec7f7f64cd85b8af6" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
integrity sha512-Ym7zZoMDZcAKWsULi2s7UMLREdVQdScPQ/fKWMYefarCztWlHPFVJo8racf8R0Gc8FAEJ2eD4of8As1oFtnQlQ== integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
dependencies: dependencies:
"@typescript-eslint/types" "5.19.0" "@typescript-eslint/types" "5.20.0"
eslint-visitor-keys "^3.0.0" eslint-visitor-keys "^3.0.0"
"@vue-a11y/announcer@^2.1.0": "@vue-a11y/announcer@^2.1.0":
@ -3126,11 +3126,6 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async@0.9.x:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
async@^2.6.2: async@^2.6.2:
version "2.6.4" version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
@ -3138,6 +3133,11 @@ async@^2.6.2:
dependencies: dependencies:
lodash "^4.17.14" lodash "^4.17.14"
async@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
asynckit@^0.4.0: asynckit@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -3180,9 +3180,9 @@ babel-jest@^27.1.0, babel-jest@^27.5.1:
slash "^3.0.0" slash "^3.0.0"
babel-loader@^8.2.2: babel-loader@^8.2.2:
version "8.2.4" version "8.2.5"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A== integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
dependencies: dependencies:
find-cache-dir "^3.3.1" find-cache-dir "^3.3.1"
loader-utils "^2.0.0" loader-utils "^2.0.0"
@ -3372,6 +3372,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
braces@^3.0.2, braces@~3.0.2: braces@^3.0.2, braces@~3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@ -3848,9 +3855,9 @@ copy-webpack-plugin@^9.0.1:
serialize-javascript "^6.0.0" serialize-javascript "^6.0.0"
core-js-compat@^3.20.2, core-js-compat@^3.21.0, core-js-compat@^3.8.3: core-js-compat@^3.20.2, core-js-compat@^3.21.0, core-js-compat@^3.8.3:
version "3.22.0" version "3.22.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.0.tgz#7ce17ab57c378be2c717c7c8ed8f82a50a25b3e4" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.1.tgz#47b9c5e79efbf13935f637449fa1cdec8cd9515f"
integrity sha512-WwA7xbfRGrk8BGaaHlakauVXrlYmAIkk8PNGb1FDQS+Rbrewc3pgFfwJFRw6psmJVAll7Px9UHRYE16oRQnwAQ== integrity sha512-CWbNqTluLMvZg1cjsQUbGiCM91dobSHKfDIyCoxuqxthdjGuUlaMbCsSehP3CBiVvG0C7P6UIrC1v0hgFE75jw==
dependencies: dependencies:
browserslist "^4.20.2" browserslist "^4.20.2"
semver "7.0.0" semver "7.0.0"
@ -3866,9 +3873,9 @@ core-js@^2.4.0, core-js@^2.5.0:
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.6.4, core-js@^3.8.3: core-js@^3.6.4, core-js@^3.8.3:
version "3.22.0" version "3.22.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.0.tgz#b52007870c5e091517352e833b77f0b2d2b259f3" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.1.tgz#1936e4f1da82675fe22ae10ee60ef638cd9752fd"
integrity sha512-8h9jBweRjMiY+ORO7bdWSeWfHhLPO7whobj7Z2Bl0IDo00C228EdGgH7FE4jGumbEjzcFfkfW8bXgdkEDhnwHQ== integrity sha512-l6CwCLq7XgITOQGhv1dIUmwCFoqFjyQ6zQHUCQlS0xKmb9d6OHIg8jDiEoswhaettT21BSF5qKr6kbvE+aKwxw==
core-util-is@~1.0.0: core-util-is@~1.0.0:
version "1.0.3" version "1.0.3"
@ -4371,9 +4378,9 @@ ejs@^3.1.6:
jake "^10.6.1" jake "^10.6.1"
electron-to-chromium@^1.4.84: electron-to-chromium@^1.4.84:
version "1.4.111" version "1.4.114"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz#897613f6504f3f17c9381c7499a635b413e4df4e" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.114.tgz#d85ec0808dd50b0cf6e6b262480ffd385f71c873"
integrity sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw== integrity sha512-gRwLpVYWHGbERPU6o8pKfR168V6enWEXzZc6zQNNXbgJ7UJna+9qzAIHY94+9KOv71D/CH+QebLA9pChD2q8zA==
emittery@^0.8.1: emittery@^0.8.1:
version "0.8.1" version "0.8.1"
@ -4994,11 +5001,11 @@ file-entry-cache@^6.0.1:
flat-cache "^3.0.4" flat-cache "^3.0.4"
filelist@^1.0.1: filelist@^1.0.1:
version "1.0.2" version "1.0.3"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83"
integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==
dependencies: dependencies:
minimatch "^3.0.4" minimatch "^5.0.1"
fill-range@^7.0.1: fill-range@^7.0.1:
version "7.0.1" version "7.0.1"
@ -5156,9 +5163,9 @@ functional-red-black-tree@^1.0.1:
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
functions-have-names@^1.2.2: functions-have-names@^1.2.2:
version "1.2.2" version "1.2.3"
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
gensync@^1.0.0-beta.2: gensync@^1.0.0-beta.2:
version "1.0.0-beta.2" version "1.0.0-beta.2"
@ -5318,9 +5325,9 @@ has-ansi@^2.0.0:
ansi-regex "^2.0.0" ansi-regex "^2.0.0"
has-bigints@^1.0.1: has-bigints@^1.0.1:
version "1.0.1" version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-flag@^3.0.0: has-flag@^3.0.0:
version "3.0.0" version "3.0.0"
@ -5502,9 +5509,9 @@ http-proxy-agent@^4.0.1:
debug "4" debug "4"
http-proxy-middleware@^2.0.3: http-proxy-middleware@^2.0.3:
version "2.0.4" version "2.0.5"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.5.tgz#2d12fb41a414889372643a1f54279a2f6470aa93"
integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== integrity sha512-ORErEaxkjyrhifofwCuQttHPUSestLtiPDwV0qQOFB0ww6695H953wIGRnkakw1K+GAP+t8/RPbfDB75RFL4Fg==
dependencies: dependencies:
"@types/http-proxy" "^1.17.8" "@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1" http-proxy "^1.18.1"
@ -5694,9 +5701,9 @@ is-ci@^1.0.10:
ci-info "^1.5.0" ci-info "^1.5.0"
is-core-module@^2.8.1: is-core-module@^2.8.1:
version "2.8.1" version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
@ -5946,11 +5953,11 @@ iterall@^1.2.2:
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
jake@^10.6.1: jake@^10.6.1:
version "10.8.4" version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.4.tgz#f6a8b7bf90c6306f768aa82bb7b98bf4ca15e84a" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
integrity sha512-MtWeTkl1qGsWUtbl/Jsca/8xSoK3x0UmS82sNbjqxxG/de/M/3b1DntdjHgPMC50enlTNwXOCRqPXLLt5cCfZA== integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
dependencies: dependencies:
async "0.9.x" async "^3.2.3"
chalk "^4.0.2" chalk "^4.0.2"
filelist "^1.0.1" filelist "^1.0.1"
minimatch "^3.0.4" minimatch "^3.0.4"
@ -6913,6 +6920,13 @@ minimatch@^3.0.4, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.6" version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
@ -8338,9 +8352,9 @@ sass-loader@^12.0.0:
neo-async "^2.6.2" neo-async "^2.6.2"
sass@^1.34.1: sass@^1.34.1:
version "1.50.0" version "1.50.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.0.tgz#3e407e2ebc53b12f1e35ce45efb226ea6063c7c8" resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.1.tgz#e9b078a1748863013c4712d2466ce8ca4e4ed292"
integrity sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ== integrity sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"

View File

@ -28,7 +28,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Update do
Logger.debug("updating an activity") Logger.debug("updating an activity")
Logger.debug(inspect(args)) Logger.debug(inspect(args))
case Managable.update(old_entity, args, additional) do case Managable.update(old_entity, args, Map.put(additional, :local, local)) do
{:ok, entity, update_data} -> {:ok, entity, update_data} ->
{:ok, activity} = create_activity(update_data, local) {:ok, activity} = create_activity(update_data, local)
maybe_federate(activity) maybe_federate(activity)

View File

@ -8,7 +8,9 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay, Transmogrifier, Visibility} alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay, Transmogrifier, Visibility}
alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Federation.HTTPSignatures.Signature
require Logger require Logger
import Mobilizon.Federation.ActivityPub.Utils, only: [remote_actors: 1]
import Mobilizon.Federation.ActivityPub.Utils,
only: [remote_actors: 1, create_full_domain_string: 1]
@doc """ @doc """
Publish an activity to all appropriated audiences inboxes Publish an activity to all appropriated audiences inboxes
@ -77,7 +79,7 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
Tesla.Env.result() Tesla.Env.result()
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
Logger.info("Federating #{id} to #{inbox}") Logger.info("Federating #{id} to #{inbox}")
%URI{host: host, path: path} = URI.parse(inbox) %URI{path: path} = uri = URI.new!(inbox)
digest = Signature.build_digest(json) digest = Signature.build_digest(json)
date = Signature.generate_date_header() date = Signature.generate_date_header()
@ -87,7 +89,7 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
signature = signature =
Signature.sign(actor, %{ Signature.sign(actor, %{
"(request-target)": "post #{path}", "(request-target)": "post #{path}",
host: host, host: create_full_domain_string(uri),
"content-length": byte_size(json), "content-length": byte_size(json),
digest: digest, digest: digest,
date: date date: date

View File

@ -10,6 +10,8 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils} alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils}
require Logger require Logger
@collection_element_task_processing_time 60_000
@doc """ @doc """
Refresh a remote profile Refresh a remote profile
""" """
@ -158,7 +160,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
items items
|> Enum.map(fn item -> Task.async(fn -> handling_element(item) end) end) |> Enum.map(fn item -> Task.async(fn -> handling_element(item) end) end)
|> Task.await_many() |> Task.await_many(@collection_element_task_processing_time)
Logger.debug("Finished processing a collection") Logger.debug("Finished processing a collection")
:ok :ok

View File

@ -14,9 +14,9 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.WebFinger alias Mobilizon.Federation.WebFinger
alias Mobilizon.Service.Workers.Background
alias Mobilizon.GraphQL.API.Follows alias Mobilizon.GraphQL.API.Follows
alias Mobilizon.Service.Workers.Background
import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1]
require Logger require Logger
@ -172,14 +172,14 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
defp fetch_actor("http://" <> address), do: fetch_actor(address) defp fetch_actor("http://" <> address), do: fetch_actor(address)
defp fetch_actor(address) do defp fetch_actor(address) do
%URI{host: host} = URI.parse("http://" <> address) %URI{host: host} = uri = URI.parse("http://" <> address)
cond do cond do
String.contains?(address, "@") -> String.contains?(address, "@") ->
check_actor(address) check_actor(address)
!is_nil(host) -> !is_nil(host) ->
check_actor("relay@#{host}") uri |> create_full_domain_string() |> check_actor()
true -> true ->
{:error, :bad_url} {:error, :bad_url}

View File

@ -41,7 +41,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
params = %{ params = %{
reporter_id: params["reporter"].id, reporter_id: params["reporter"].id,
reported_id: params["reported"].id, reported_id: params["reported"].id,
comments_ids: params["comments"] |> Enum.map(& &1.id), comments_ids:
if(params["comments"], do: params["comments"] |> Enum.map(& &1.id), else: []),
content: params["content"] || "", content: params["content"] || "",
additional: %{ additional: %{
"cc" => [params["reported"].url] "cc" => [params["reported"].url]
@ -406,6 +407,13 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
Actions.Update.update(old_actor, object_data, false, %{updater_actor: author}) do Actions.Update.update(old_actor, object_data, false, %{updater_actor: author}) do
{:ok, activity, new_actor} {:ok, activity, new_actor}
else else
{:error, :update_not_allowed} ->
Logger.warn("Activity tried to update an actor that's local or not a group",
activity: params
)
:error
e -> e ->
Sentry.capture_message("Error while handling an Update activity", Sentry.capture_message("Error while handling an Update activity",
extra: %{params: params} extra: %{params: params}
@ -614,11 +622,16 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:error, :unknown_actor} {:error, :unknown_actor}
{:ok, %Actor{} = actor} -> {:ok, %Actor{} = actor} ->
# If the actor itself is being deleted, no need to check anything other than the object being remote
if remote_actor_is_being_deleted(data) do
Actions.Delete.delete(actor, actor, false)
else
case is_group_object_gone(object_id) do case is_group_object_gone(object_id) do
{:ok, object} -> # The group object is no longer there, we can remove the element
{:ok, entity} ->
if Utils.origin_check_from_id?(actor_url, object_id) || if Utils.origin_check_from_id?(actor_url, object_id) ||
Permission.can_delete_group_object?(actor, object) do Permission.can_delete_group_object?(actor, entity) do
Actions.Delete.delete(object, actor, false) Actions.Delete.delete(entity, actor, false)
else else
Logger.warn("Object origin check failed") Logger.warn("Object origin check failed")
:error :error
@ -630,6 +643,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
end end
end end
end
def handle_incoming( def handle_incoming(
%{"type" => "Move", "object" => %{"type" => type} = object, "actor" => _actor} = data %{"type" => "Move", "object" => %{"type" => type} = object, "actor" => _actor} = data
@ -1207,4 +1221,9 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
moderator.domain == group.domain moderator.domain == group.domain
end end
end end
defp remote_actor_is_being_deleted(%{"object" => object} = data) do
object_id = Utils.get_url(object)
Utils.get_actor(data) == object_id and not Utils.are_same_origin?(object_id, Endpoint.url())
end
end end

View File

@ -45,6 +45,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
def update(%Actor{} = old_actor, args, additional) do def update(%Actor{} = old_actor, args, additional) do
updater_actor = Map.get(args, :updater_actor) || Map.get(additional, :updater_actor) updater_actor = Map.get(args, :updater_actor) || Map.get(additional, :updater_actor)
if Map.get(additional, :local, false) == true or not match?(%Actor{domain: nil}, old_actor) or
match?(%Actor{type: :Group}, old_actor) do
case Actors.update_actor(old_actor, args) do case Actors.update_actor(old_actor, args) do
{:ok, %Actor{} = new_actor} -> {:ok, %Actor{} = new_actor} ->
GroupActivity.insert_activity(new_actor, GroupActivity.insert_activity(new_actor,
@ -65,6 +67,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
{:error, %Ecto.Changeset{} = err} -> {:error, %Ecto.Changeset{} = err} ->
{:error, err} {:error, err}
end end
else
{:error, :update_not_allowed}
end
end end
@public_ap "https://www.w3.org/ns/activitystreams#Public" @public_ap "https://www.w3.org/ns/activitystreams#Public"

View File

@ -672,8 +672,15 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
@doc """ @doc """
Converts PEM encoded keys to a public key representation Converts PEM encoded keys to a public key representation
""" """
@spec pem_to_public_key_pem(String.t()) :: String.t()
def pem_to_public_key_pem(pem) do
public_key = pem_to_public_key(pem)
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
:public_key.pem_encode([public_key])
end
@spec pem_to_public_key(String.t()) :: {:RSAPublicKey, any(), any()} @spec pem_to_public_key(String.t()) :: {:RSAPublicKey, any(), any()}
def pem_to_public_key(pem) do defp pem_to_public_key(pem) do
[key_code] = :public_key.pem_decode(pem) [key_code] = :public_key.pem_decode(pem)
key = :public_key.pem_entry_decode(key_code) key = :public_key.pem_entry_decode(key_code)
@ -686,14 +693,8 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
end end
end end
@spec pem_to_public_key_pem(String.t()) :: String.t() @spec make_signature(Actor.t(), String.t(), DateTime.t()) :: list({atom(), String.t()})
def pem_to_public_key_pem(pem) do defp make_signature(actor, id, date) do
public_key = pem_to_public_key(pem)
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
:public_key.pem_encode([public_key])
end
def make_signature(actor, id, date) do
uri = URI.parse(id) uri = URI.parse(id)
signature = signature =
@ -780,4 +781,16 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
params params
end end
end end
@schemes_with_no_port ["http", "https"]
def create_full_domain_string(%URI{host: host, port: nil}), do: host
def create_full_domain_string(%URI{host: host, port: port}) do
if port in Enum.map(@schemes_with_no_port, &URI.default_port/1) do
host
else
"#{host}:#{port}"
end
end
end end

View File

@ -17,6 +17,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
alias Mobilizon.Service.RichMedia.Parser alias Mobilizon.Service.RichMedia.Parser
alias Mobilizon.Web.Upload alias Mobilizon.Web.Upload
import Mobilizon.Federation.ActivityStream.Converter.Utils, only: [get_address: 1] import Mobilizon.Federation.ActivityStream.Converter.Utils, only: [get_address: 1]
import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1]
@behaviour Converter @behaviour Converter
@ -54,7 +55,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
outbox_url: data["outbox"], outbox_url: data["outbox"],
following_url: data["following"], following_url: data["following"],
followers_url: data["followers"], followers_url: data["followers"],
domain: URI.parse(data["id"]).host, domain: data["id"] |> URI.new!() |> create_full_domain_string(),
manually_approves_followers: data["manuallyApprovesFollowers"], manually_approves_followers: data["manuallyApprovesFollowers"],
type: data["type"], type: data["type"],
visibility: if(Map.get(data, "discoverable", false) == true, do: :public, else: :unlisted), visibility: if(Map.get(data, "discoverable", false) == true, do: :public, else: :unlisted),

View File

@ -67,33 +67,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
def as_to_model(%{"object" => objects} = object) do def as_to_model(%{"object" => objects} = object) do
with {:ok, %Actor{} = reporter} <- with {:ok, %Actor{} = reporter} <-
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]), ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
%Actor{} = reported <- %Actor{} = reported <- find_reported(objects),
Enum.reduce_while(objects, nil, fn url, _ -> event <- find_event(objects),
case ActivityPubActor.get_or_fetch_actor_by_url(url) do comments <- find_comments(objects, reported, event) do
{:ok, %Actor{} = actor} ->
{:halt, actor}
_ ->
{:cont, nil}
end
end),
event <-
Enum.reduce_while(objects, nil, fn url, _ ->
case Events.get_event_by_url(url) do
%Event{} = event ->
{:halt, event}
_ ->
{:cont, nil}
end
end),
# Remove the reported actor and the event from the object list.
comments <-
Enum.filter(objects, fn url ->
!(url == reported.url || (!is_nil(event) && event.url == url))
end),
comments <- Enum.map(comments, &Discussions.get_comment_from_url/1) do
%{ %{
"reporter" => reporter, "reporter" => reporter,
"uri" => object["id"], "uri" => object["id"],
@ -104,4 +80,41 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
} }
end end
end end
@spec find_reported(list(String.t())) :: Actor.t() | nil
defp find_reported(objects) do
Enum.reduce_while(objects, nil, fn url, _ ->
case ActivityPubActor.get_or_fetch_actor_by_url(url) do
{:ok, %Actor{} = actor} ->
{:halt, actor}
_ ->
{:cont, nil}
end
end)
end
# Remove the reported actor and the event from the object list.
@spec find_comments(list(String.t()), Actor.t() | nil, Event.t() | nil) :: list(Comment.t())
defp find_comments(objects, reported, event) do
objects
|> Enum.filter(fn url ->
!((!is_nil(reported) && url == reported.url) || (!is_nil(event) && event.url == url))
end)
|> Enum.map(&Discussions.get_comment_from_url/1)
|> Enum.filter(& &1)
end
@spec find_event(list(String.t())) :: Event.t() | nil
defp find_event(objects) do
Enum.reduce_while(objects, nil, fn url, _ ->
case Events.get_event_by_url(url) do
%Event{} = event ->
{:halt, event}
_ ->
{:cont, nil}
end
end)
end
end end

View File

@ -34,6 +34,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
@spec fetch_mentions([map()]) :: [map()] @spec fetch_mentions([map()]) :: [map()]
def fetch_mentions(mentions) when is_list(mentions) do def fetch_mentions(mentions) when is_list(mentions) do
Logger.debug("fetching mentions") Logger.debug("fetching mentions")
Logger.debug(inspect(mentions))
Enum.reduce(mentions, [], fn mention, acc -> create_mention(mention, acc) end) Enum.reduce(mentions, [], fn mention, acc -> create_mention(mention, acc) end)
end end

View File

@ -95,6 +95,9 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
actor_url = key_id_to_actor_url(kid) actor_url = key_id_to_actor_url(kid)
Logger.debug("Refetching public key for #{actor_url}") Logger.debug("Refetching public key for #{actor_url}")
# In this specific case we don't sign object fetches because
# this would cause infinite recursion when servers both need
# to fetch each other's keys
with {:ok, %Actor{} = actor} <- with {:ok, %Actor{} = actor} <-
ActivityPubActor.make_actor_from_url(actor_url, ignore_sign_object_fetches: true) do ActivityPubActor.make_actor_from_url(actor_url, ignore_sign_object_fetches: true) do
get_actor_public_key(actor) get_actor_public_key(actor)

View File

@ -129,6 +129,7 @@ defmodule Mobilizon.Federation.WebFinger do
| :address_invalid | :address_invalid
| :http_error | :http_error
| :webfinger_information_not_json | :webfinger_information_not_json
| :webfinger_information_not_valid
| :no_url_in_webfinger_data | :no_url_in_webfinger_data
@doc """ @doc """
@ -164,7 +165,9 @@ defmodule Mobilizon.Federation.WebFinger do
end end
@spec fetch_webfinger_data(String.t()) :: @spec fetch_webfinger_data(String.t()) ::
{:ok, map()} | {:error, :webfinger_information_not_json | :http_error} {:ok, map()}
| {:error,
:webfinger_information_not_json | :webfinger_information_not_valid | :http_error}
defp fetch_webfinger_data(address) do defp fetch_webfinger_data(address) do
Logger.debug("Calling WebfingerClient with #{inspect(address)}") Logger.debug("Calling WebfingerClient with #{inspect(address)}")
@ -202,9 +205,10 @@ defmodule Mobilizon.Federation.WebFinger do
{:ok, String.t()} | {:error, :link_not_found} | {:error, any()} {:ok, String.t()} | {:error, :link_not_found} | {:error, any()}
defp find_webfinger_endpoint(domain) when is_binary(domain) do defp find_webfinger_endpoint(domain) when is_binary(domain) do
Logger.debug("Calling HostMetaClient for #{domain}") Logger.debug("Calling HostMetaClient for #{domain}")
prefix = if Application.fetch_env!(:mobilizon, :env) !== :dev, do: "https", else: "http"
with {:ok, %Tesla.Env{status: 200, body: body}} <- with {:ok, %Tesla.Env{status: 200, body: body}} <-
HostMetaClient.get("https://#{domain}/.well-known/host-meta"), HostMetaClient.get("#{prefix}://#{domain}/.well-known/host-meta"),
link_template when is_binary(link_template) <- find_link_from_template(body) do link_template when is_binary(link_template) <- find_link_from_template(body) do
{:ok, link_template} {:ok, link_template}
else else
@ -225,7 +229,8 @@ defmodule Mobilizon.Federation.WebFinger do
_ -> _ ->
Logger.debug("Using default webfinger location") Logger.debug("Using default webfinger location")
"https://#{domain}/.well-known/webfinger?resource=acct:#{actor}" prefix = if Application.fetch_env!(:mobilizon, :env) !== :dev, do: "https", else: "http"
"#{prefix}://#{domain}/.well-known/webfinger?resource=acct:#{actor}"
end end
end end
end end
@ -243,10 +248,15 @@ defmodule Mobilizon.Federation.WebFinger do
end end
@spec webfinger_from_json(map() | String.t()) :: @spec webfinger_from_json(map() | String.t()) ::
{:ok, map()} | {:error, :webfinger_information_not_json} {:ok, map()}
| {:error, :webfinger_information_not_json | :webfinger_information_not_valid}
defp webfinger_from_json(doc) when is_map(doc) do defp webfinger_from_json(doc) when is_map(doc) do
links = Map.get(doc, "links")
subject = Map.get(doc, "subject")
if !is_nil(links) && !is_nil(subject) do
data = data =
Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data -> Enum.reduce(links, %{"subject" => subject}, fn link, data ->
case {link["type"], link["rel"]} do case {link["type"], link["rel"]} do
{"application/activity+json", "self"} -> {"application/activity+json", "self"} ->
Map.put(data, "url", link["href"]) Map.put(data, "url", link["href"])
@ -257,7 +267,7 @@ defmodule Mobilizon.Federation.WebFinger do
_ -> _ ->
Logger.debug(fn -> Logger.debug(fn ->
"Unhandled type to finger: #{inspect(link["type"])}" "Unhandled type to finger: #{inspect(link)}"
end) end)
data data
@ -265,6 +275,9 @@ defmodule Mobilizon.Federation.WebFinger do
end) end)
{:ok, data} {:ok, data}
else
{:error, :webfinger_information_not_valid}
end
end end
defp webfinger_from_json(_doc), do: {:error, :webfinger_information_not_json} defp webfinger_from_json(_doc), do: {:error, :webfinger_information_not_json}

View File

@ -69,10 +69,6 @@ defmodule Mobilizon.GraphQL.API.Follows do
) )
case Actors.check_follow(follower, followed) do case Actors.check_follow(follower, followed) do
%Follower{approved: false} = follow ->
Actors.delete_follower(follow)
{:error, "Follow already rejected"}
%Follower{} = follow -> %Follower{} = follow ->
Actions.Reject.reject( Actions.Reject.reject(
:follow, :follow,

View File

@ -6,6 +6,8 @@ defmodule Mobilizon.Service.DateTime do
@typep to_string_format :: :short | :medium | :long | :full @typep to_string_format :: :short | :medium | :long | :full
@utc_timezone "Etc/UTC"
@spec datetime_to_string(DateTime.t(), String.t(), to_string_format()) :: String.t() @spec datetime_to_string(DateTime.t(), String.t(), to_string_format()) :: String.t()
def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do
Mobilizon.Cldr.DateTime.to_string!(datetime, Mobilizon.Cldr.DateTime.to_string!(datetime,
@ -75,7 +77,7 @@ defmodule Mobilizon.Service.DateTime do
def calculate_next_day_notification(%Date{} = day, options \\ []) do def calculate_next_day_notification(%Date{} = day, options \\ []) do
compare_to = Keyword.get(options, :compare_to, DateTime.utc_now()) compare_to = Keyword.get(options, :compare_to, DateTime.utc_now())
notification_time = Keyword.get(options, :notification_time, ~T[18:00:00]) notification_time = Keyword.get(options, :notification_time, ~T[18:00:00])
timezone = Keyword.get(options, :timezone, "Etc/UTC") || "Etc/UTC" timezone = options |> Keyword.get(:timezone, @utc_timezone) |> fallback_tz()
send_at = DateTime.new!(day, notification_time, timezone) send_at = DateTime.new!(day, notification_time, timezone)
@ -145,7 +147,7 @@ defmodule Mobilizon.Service.DateTime do
@spec appropriate_first_day_of_week(DateTime.t(), keyword) :: DateTime.t() | nil @spec appropriate_first_day_of_week(DateTime.t(), keyword) :: DateTime.t() | nil
defp appropriate_first_day_of_week(%DateTime{} = datetime, options) do defp appropriate_first_day_of_week(%DateTime{} = datetime, options) do
locale = Keyword.get(options, :locale, "en") locale = Keyword.get(options, :locale, "en")
timezone = Keyword.get(options, :timezone, "Etc/UTC") timezone = options |> Keyword.get(:timezone, @utc_timezone) |> fallback_tz()
local_datetime = datetime_tz_convert(datetime, timezone) local_datetime = datetime_tz_convert(datetime, timezone)
@ -170,7 +172,7 @@ defmodule Mobilizon.Service.DateTime do
options options
) do ) do
notification_time = Keyword.get(options, :notification_time, ~T[08:00:00]) notification_time = Keyword.get(options, :notification_time, ~T[08:00:00])
timezone = Keyword.get(options, :timezone, "Etc/UTC") timezone = options |> Keyword.get(:timezone, @utc_timezone) |> fallback_tz()
DateTime.new!(date, notification_time, timezone) DateTime.new!(date, notification_time, timezone)
end end
@ -182,7 +184,7 @@ defmodule Mobilizon.Service.DateTime do
compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today()) compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today())
compare_to = Keyword.get(options, :compare_to_datetime, DateTime.utc_now()) compare_to = Keyword.get(options, :compare_to_datetime, DateTime.utc_now())
start_time = Keyword.get(options, :start_time, @start_time) start_time = Keyword.get(options, :start_time, @start_time)
timezone = Keyword.get(options, :timezone, "Etc/UTC") || "Etc/UTC" timezone = options |> Keyword.get(:timezone, @utc_timezone) |> fallback_tz()
end_time = Keyword.get(options, :end_time, @end_time) end_time = Keyword.get(options, :end_time, @end_time)
DateTime.compare(compare_to, DateTime.new!(compare_to_day, start_time, timezone)) in [ DateTime.compare(compare_to, DateTime.new!(compare_to_day, start_time, timezone)) in [
@ -213,4 +215,13 @@ defmodule Mobilizon.Service.DateTime do
def is_same_day?(%DateTime{} = one, %DateTime{} = two) do def is_same_day?(%DateTime{} = one, %DateTime{} = two) do
DateTime.to_date(one) == DateTime.to_date(two) DateTime.to_date(one) == DateTime.to_date(two)
end end
@spec fallback_tz(String.t()) :: String.t()
defp fallback_tz(timezone) do
if Tzdata.zone_exists?(timezone) do
timezone
else
@utc_timezone
end
end
end end

View File

@ -8,8 +8,8 @@ defmodule Mobilizon.Service.Formatter do
Formats input text to structured data, extracts mentions and hashtags. Formats input text to structured data, extracts mentions and hashtags.
""" """
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Service.Formatter.HTML alias Mobilizon.Service.Formatter.HTML
alias Phoenix.HTML.Tag alias Phoenix.HTML.Tag
@ -22,26 +22,26 @@ defmodule Mobilizon.Service.Formatter do
@spec escape_mention_handler(String.t(), String.t(), any(), any()) :: String.t() @spec escape_mention_handler(String.t(), String.t(), any(), any()) :: String.t()
defp escape_mention_handler("@" <> nickname = mention, buffer, _, _) do defp escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case Actors.get_actor_by_name(nickname) do case ActivityPubActor.find_or_make_actor_from_nickname(nickname) do
%Actor{} -> {:ok, %Actor{}} ->
# escape markdown characters with `\\` # escape markdown characters with `\\`
# (we don't want something like @user__name to be parsed by markdown) # (we don't want something like @user__name to be parsed by markdown)
String.replace(mention, @markdown_characters_regex, "\\\\\\1") String.replace(mention, @markdown_characters_regex, "\\\\\\1")
nil -> {:error, _err} ->
buffer buffer
end end
end end
@spec mention_handler(String.t(), String.t(), any(), map()) :: {String.t(), map()} @spec mention_handler(String.t(), String.t(), any(), map()) :: {String.t(), map()}
def mention_handler("@" <> nickname, buffer, _opts, acc) do def mention_handler("@" <> nickname, buffer, _opts, acc) do
case Actors.get_actor_by_name(nickname) do case ActivityPubActor.find_or_make_actor_from_nickname(nickname) do
# %Actor{preferred_username: preferred_username} = actor -> # %Actor{preferred_username: preferred_username} = actor ->
# link = "<span class='h-card mention'>@<span>#{preferred_username}</span></span>" # link = "<span class='h-card mention'>@<span>#{preferred_username}</span></span>"
# #
# {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}} # {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}}
%Actor{type: :Person, id: id, preferred_username: preferred_username} = actor -> {:ok, %Actor{type: :Person, id: id, preferred_username: preferred_username} = actor} ->
# link = # link =
# "<span class='h-card mention' data-user='#{id}'>@<span>#{preferred_username}</span></span>" # "<span class='h-card mention' data-user='#{id}'>@<span>#{preferred_username}</span></span>"
@ -62,7 +62,7 @@ defmodule Mobilizon.Service.Formatter do
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}} {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}}
nil -> {:error, _} ->
{buffer, acc} {buffer, acc}
end end
end end

View File

@ -1,6 +1,6 @@
defmodule Mobilizon.Service.Workers.CleanSuspendedActors do defmodule Mobilizon.Service.Workers.CleanSuspendedActors do
@moduledoc """ @moduledoc """
Worker to clean unattached media Worker to clean suspended actors
""" """
use Oban.Worker, queue: "background" use Oban.Worker, queue: "background"

View File

@ -24,6 +24,9 @@ defmodule Mobilizon.Service.Workers.RefreshInstances do
| {:error, | {:error,
Mobilizon.Federation.ActivityPub.Actor.make_actor_errors() Mobilizon.Federation.ActivityPub.Actor.make_actor_errors()
| Mobilizon.Federation.WebFinger.finger_errors()} | Mobilizon.Federation.WebFinger.finger_errors()}
defp refresh_instance_actor(%Instance{domain: nil}) do
{:error, :not_remote_instance}
end
defp refresh_instance_actor(%Instance{domain: domain}) do defp refresh_instance_actor(%Instance{domain: domain}) do
ActivityPubActor.find_or_make_actor_from_nickname("relay@#{domain}") ActivityPubActor.find_or_make_actor_from_nickname("relay@#{domain}")

View File

@ -167,7 +167,7 @@ defmodule Mobilizon.Mixfile do
{:mogrify, "~> 0.9"}, {:mogrify, "~> 0.9"},
{:linkify, "~> 0.3"}, {:linkify, "~> 0.3"},
{:http_signatures, "~> 0.1.0"}, {:http_signatures, "~> 0.1.0"},
{:ex_cldr, "2.27.1"}, {:ex_cldr, "~> 2.28.0"},
{:ex_cldr_dates_times, "~> 2.2"}, {:ex_cldr_dates_times, "~> 2.2"},
{:ex_optimizer, "~> 0.1"}, {:ex_optimizer, "~> 0.1"},
{:progress_bar, "~> 2.0"}, {:progress_bar, "~> 2.0"},

View File

@ -34,12 +34,12 @@
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlport": {:hex, :erlport, "0.10.1", "c96ffa51bbcab0298232fcdfe8c3e110f1598011de71ae6b9082b80c9e2e476a", [:rebar3], [], "hexpm", "34931e8cb62a131d1bc8a2bd04d4007c73c03e4f10e22ee4a218e7172227a918"}, "erlport": {:hex, :erlport, "0.10.1", "c96ffa51bbcab0298232fcdfe8c3e110f1598011de71ae6b9082b80c9e2e476a", [:rebar3], [], "hexpm", "34931e8cb62a131d1bc8a2bd04d4007c73c03e4f10e22ee4a218e7172227a918"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex_cldr": {:hex, :ex_cldr, "2.27.1", "b4fdedc29d6566b5201aea9bb7b554db16ac7b30366188aa0c50790b4393c268", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b5c5c31fb7cf43d47e1267a7ede8e46c2cf12de3308ea1ba614241bd29bdcebf"}, "ex_cldr": {:hex, :ex_cldr, "2.28.0", "8f7a8c70a49dc31f656eb02d4c6280550ab52abd340406e8341dd4ba2390798d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "564608c4a344c9cca54874e95fb77cd7149f593ccf522db5cbe8943c0b183630"},
"ex_cldr_calendars": {:hex, :ex_cldr_calendars, "1.18.0", "aa86e673f02b3c65d9cc29c483a4dfec1878b2e2460619c2b06c121ef801334e", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: true]}, {:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_cldr_units, "~> 3.12", [hex: :ex_cldr_units, repo: "hexpm", optional: true]}, {:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "5b47bf4e90bdd6746ac9ca9cb3f9d36c3e656c18768ace8366061e3e02899cdc"}, "ex_cldr_calendars": {:hex, :ex_cldr_calendars, "1.18.0", "aa86e673f02b3c65d9cc29c483a4dfec1878b2e2460619c2b06c121ef801334e", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: true]}, {:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_cldr_units, "~> 3.12", [hex: :ex_cldr_units, repo: "hexpm", optional: true]}, {:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "5b47bf4e90bdd6746ac9ca9cb3f9d36c3e656c18768ace8366061e3e02899cdc"},
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.13.0", "13188b99e527d724ed3dc2af37e54f0dac42dec42b620a34c4ed4d4902fad6dd", [:mix], [{:ex_cldr, "~> 2.24", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "64731e49ac3530aa88872b52c319eb5231bfb1a3ebb0956044c34abc0ed4f520"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.13.0", "13188b99e527d724ed3dc2af37e54f0dac42dec42b620a34c4ed4d4902fad6dd", [:mix], [{:ex_cldr, "~> 2.24", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "64731e49ac3530aa88872b52c319eb5231bfb1a3ebb0956044c34abc0ed4f520"},
"ex_cldr_dates_times": {:hex, :ex_cldr_dates_times, "2.11.0", "eb00d2def8c16feb250ea2436c2e07b31b6e0ad22f9ff569c7714e807c8327df", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_calendars, "~> 1.18", [hex: :ex_cldr_calendars, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "36b2dd6bea88f295b9761d6ca26cccce573708cffe6e196f9930a24ca57baecf"}, "ex_cldr_dates_times": {:hex, :ex_cldr_dates_times, "2.11.0", "eb00d2def8c16feb250ea2436c2e07b31b6e0ad22f9ff569c7714e807c8327df", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_calendars, "~> 1.18", [hex: :ex_cldr_calendars, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "36b2dd6bea88f295b9761d6ca26cccce573708cffe6e196f9930a24ca57baecf"},
"ex_cldr_languages": {:hex, :ex_cldr_languages, "0.3.3", "9787002803552b15a7ade19496c9e46fc921baca992ea80d0394e11fe3acea45", [:mix], [{:ex_cldr, "~> 2.25", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "22fb1fef72b7b4b4872d243b34e7b83734247a78ad87377986bf719089cc447a"}, "ex_cldr_languages": {:hex, :ex_cldr_languages, "0.3.3", "9787002803552b15a7ade19496c9e46fc921baca992ea80d0394e11fe3acea45", [:mix], [{:ex_cldr, "~> 2.25", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "22fb1fef72b7b4b4872d243b34e7b83734247a78ad87377986bf719089cc447a"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.25.2", "9bd1f21bb7a300e1e0fd080b99f0c912d2adf90292e7d7c0335a6da84758c8a8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.26", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.13", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5de180d21018797bc412aae727402b3004df4c5666ed2602863448993b035c9b"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.26.0", "f1104498a96666fc0b0c0995f17266c8700593e984418aa05e7b731bfd8c5670", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.28", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.13", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "35b1c7bd6c87149e7f31ef24705d254419b35f9f26fe439af88d02950710e11b"},
"ex_doc": {:hex, :ex_doc, "0.28.3", "6eea2f69995f5fba94cd6dd398df369fe4e777a47cd887714a0976930615c9e6", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "05387a6a2655b5f9820f3f627450ed20b4325c25977b2ee69bed90af6688e718"}, "ex_doc": {:hex, :ex_doc, "0.28.3", "6eea2f69995f5fba94cd6dd398df369fe4e777a47cd887714a0976930615c9e6", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "05387a6a2655b5f9820f3f627450ed20b4325c25977b2ee69bed90af6688e718"},
"ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"}, "ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},

File diff suppressed because it is too large Load Diff

View File

@ -175,6 +175,9 @@ type Config {
"The instance's admins languages" "The instance's admins languages"
languages: [String] languages: [String]
"The instance list of event categories possibilities"
eventCategories: [EventCategoryOption]
"Whether the registrations are opened" "Whether the registrations are opened"
registrationsOpen: Boolean registrationsOpen: Boolean
@ -246,6 +249,9 @@ type Config {
"The instance list of export formats" "The instance list of export formats"
exportFormats: ExportFormats exportFormats: ExportFormats
"Configuration for diverse analytics services"
analytics: [Analytics]
} }
"A tag" "A tag"
@ -368,6 +374,15 @@ human-readable ID strings.
""" """
scalar UUID scalar UUID
"A paginated list of instances"
type PaginatedInstanceList {
"A list of instances"
elements: [Instance]
"The total number of instances in the list"
total: Int
}
"A paginated list of discussions" "A paginated list of discussions"
type PaginatedDiscussionList { type PaginatedDiscussionList {
"A list of discussion" "A list of discussion"
@ -667,6 +682,26 @@ type RootSubscriptionType {
): Discussion ): Discussion
} }
"Event categories list configuration"
type EventCategoryOption {
"The ID of the event category"
id: String
"The translated name of the event category"
label: String
}
type AnalyticsConfiguration {
"The key for the analytics configuration element"
key: String
"The value for the analytics configuration element"
value: String
"The analytics configuration type"
type: AnalyticsConfigurationType
}
"Represents a deleted feed_token" "Represents a deleted feed_token"
type DeletedFeedToken { type DeletedFeedToken {
"The user that owned the deleted feed token" "The user that owned the deleted feed token"
@ -930,6 +965,27 @@ type PaginatedFollowerList {
total: Int total: Int
} }
type Analytics {
"ID of the analytics service"
id: String
"Whether the service is activated or not"
enabled: Boolean
"A list of key-values configuration"
configuration: [AnalyticsConfiguration]
}
enum InstancesSortFields {
EVENT_COUNT
PERSON_COUNT
GROUP_COUNT
FOLLOWERS_COUNT
FOLLOWINGS_COUNT
REPORTS_COUNT
MEDIA_SIZE
}
"The list of possible options for the event's status" "The list of possible options for the event's status"
enum EventStatus { enum EventStatus {
"The event is tentative" "The event is tentative"
@ -1154,11 +1210,14 @@ type Person implements ActionLogObject & Actor {
limit: Int limit: Int
): PaginatedParticipantList ): PaginatedParticipantList
"The list of group this person is member of" "The list of groups this person is member of"
memberships( memberships(
"Filter by group federated username" "Filter by group federated username"
group: String group: String
"Filter by group ID"
groupId: ID
"The page in the paginated memberships list" "The page in the paginated memberships list"
page: Int page: Int
@ -1514,7 +1573,7 @@ type RootMutationType {
attributedToId: ID attributedToId: ID
"The event's category" "The event's category"
category: String category: EventCategory
"The event's physical address" "The event's physical address"
physicalAddress: AddressInput physicalAddress: AddressInput
@ -1580,7 +1639,7 @@ type RootMutationType {
attributedToId: ID attributedToId: ID
"The event's category" "The event's category"
category: String category: EventCategory
"The event's physical address" "The event's physical address"
physicalAddress: AddressInput physicalAddress: AddressInput
@ -1841,6 +1900,12 @@ type RootMutationType {
noteId: ID! noteId: ID!
): DeletedObject ): DeletedObject
"Add an instance subscription"
addInstance(
"The instance domain to add"
domain: String!
): Instance
"Add a relay subscription" "Add a relay subscription"
addRelay( addRelay(
"The relay hostname to add" "The relay hostname to add"
@ -1910,6 +1975,24 @@ type RootMutationType {
instanceLanguages: [String] instanceLanguages: [String]
): AdminSettings ): AdminSettings
"For an admin to update an user"
adminUpdateUser(
"The user's ID"
id: ID!
"The user's new email"
email: String
"Manually confirm the user's account"
confirmed: Boolean
"Set user's new role"
role: UserRole
"Whether or not to notify the user of the change"
notify: Boolean
): User
"Create a todo list" "Create a todo list"
createTodoList( createTodoList(
"The todo list title" "The todo list title"
@ -2225,6 +2308,9 @@ type RootQueryType {
"Whether the event is online or in person" "Whether the event is online or in person"
type: EventType type: EventType
"The category for the event"
category: String
"Radius around the location to search in" "Radius around the location to search in"
radius: Float radius: Float
@ -2258,6 +2344,9 @@ type RootQueryType {
"Filter users by email" "Filter users by email"
email: String email: String
"Filter users by current signed-in IP address"
currentSignInIp: String
"The page in the paginated users list" "The page in the paginated users list"
page: Int page: Int
@ -2349,6 +2438,12 @@ type RootQueryType {
preferredUsername: String! preferredUsername: String!
): Group ): Group
"Get a group by its preferred username"
groupById(
"The group local ID"
id: ID!
): Group
"Get all events" "Get all events"
events( events(
"The page in the paginated event list" "The page in the paginated event list"
@ -2439,6 +2534,9 @@ type RootQueryType {
"Filter reports by status" "Filter reports by status"
status: ReportStatus status: ReportStatus
"Filter reports by domain name"
domain: String
): PaginatedReportList ): PaginatedReportList
"Get a report by id" "Get a report by id"
@ -2486,6 +2584,30 @@ type RootQueryType {
direction: String direction: String
): PaginatedFollowerList ): PaginatedFollowerList
"List instances"
instances(
"The page in the paginated relay followings list"
page: Int
"The limit of relay followings per page"
limit: Int
"The field to order by the list"
orderBy: InstancesSortFields
"Filter by domain"
filterDomain: String
"Whether or not to filter instances by the follow status"
filterFollowStatus: InstanceFilterFollowStatus
"Whether or not to filter instances by the suspended status"
filterSuspendStatus: InstanceFilterSuspendStatus
"The sorting direction"
direction: String
): PaginatedInstanceList
"Get an instance's details" "Get an instance's details"
instance( instance(
"The instance domain" "The instance domain"
@ -2569,15 +2691,15 @@ type Instance {
"The domain name of the instance" "The domain name of the instance"
domain: ID domain: ID
"Whether this instance has a Mobilizon relay actor"
hasRelay: Boolean
"Do we follow this instance" "Do we follow this instance"
followerStatus: InstanceFollowStatus followerStatus: InstanceFollowStatus
"Does this instance follow us?" "Does this instance follow us?"
followedStatus: InstanceFollowStatus followedStatus: InstanceFollowStatus
"The number of events on this instance we know of"
eventCount: Int
"The number of profiles on this instance we know of" "The number of profiles on this instance we know of"
personCount: Int personCount: Int
@ -2595,6 +2717,9 @@ type Instance {
"The size of all the media files sent by actors from this instance" "The size of all the media files sent by actors from this instance"
mediaSize: Int mediaSize: Int
"Whether this instance has a relay, meaning that it's a Mobilizon instance that we can follow"
hasRelay: Boolean
} }
""" """
@ -2865,7 +2990,7 @@ type Event implements ActivityObject & Interactable & ActionLogObject {
tags: [Tag] tags: [Tag]
"The event's category" "The event's category"
category: String category: EventCategory
"Whether or not the event is a draft" "Whether or not the event is a draft"
draft: Boolean draft: Boolean
@ -3296,6 +3421,39 @@ type Activity {
group: Group group: Group
} }
enum EventCategory {
ARTS
BOOK_CLUBS
BUSINESS
CAUSES
COMEDY
CRAFTS
FOOD_DRINK
HEALTH
MUSIC
AUTO_BOAT_AIR
COMMUNITY
FAMILY_EDUCATION
FASHION_BEAUTY
FILM_MEDIA
GAMES
LANGUAGE_CULTURE
LEARNING
LGBTQ
MOVEMENTS_POLITICS
NETWORKING
PARTY
PERFORMING_VISUAL_ARTS
PETS
PHOTOGRAPHY
OUTDOORS_ADVENTURE
SPIRITUALITY_RELIGION_BELIEFS
SCIENCE_TECH
SPORTS
THEATRE
MEETING
}
"The list of visibility options for a post" "The list of visibility options for a post"
enum PostVisibility { enum PostVisibility {
"Publicly listed and federated. Can be shared." "Publicly listed and federated. Can be shared."
@ -3355,6 +3513,20 @@ type ReportNote implements ActionLogObject {
insertedAt: DateTime insertedAt: DateTime
} }
enum AnalyticsConfigurationType {
"A string"
STRING
"An integer"
INTEGER
"A boolean"
BOOLEAN
"A float"
FLOAT
}
"The types of Group that exist" "The types of Group that exist"
enum GroupType { enum GroupType {
"A private group of persons" "A private group of persons"
@ -3494,6 +3666,12 @@ type PaginatedFollowedGroupEvents {
total: Int total: Int
} }
enum InstanceFilterFollowStatus {
ALL
FOLLOWING
FOLLOWED
}
"Instance map tiles configuration" "Instance map tiles configuration"
type Tiles { type Tiles {
"The instance's tiles endpoint" "The instance's tiles endpoint"
@ -3661,6 +3839,11 @@ type AdminSettings {
instanceLanguages: [String] instanceLanguages: [String]
} }
enum InstanceFilterSuspendStatus {
ALL
SUSPENDED
}
"The instance's features" "The instance's features"
type Features { type Features {
"Whether groups are activated on this instance" "Whether groups are activated on this instance"
@ -3668,9 +3851,6 @@ type Features {
"Whether event creation is allowed on this instance" "Whether event creation is allowed on this instance"
eventCreation: Boolean eventCreation: Boolean
"Activate link to Koena Connect"
koenaConnect: Boolean
} }
"A set of user settings" "A set of user settings"

View File

@ -195,7 +195,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
actor = insert(:actor) actor = insert(:actor)
actor_data = %{summary: @updated_actor_summary} actor_data = %{summary: @updated_actor_summary}
{:ok, update, _} = Actions.Update.update(actor, actor_data, false) {:ok, update, _} = Actions.Update.update(actor, actor_data, true)
assert update.data["actor"] == actor.url assert update.data["actor"] == actor.url
assert update.data["to"] == [@activity_pub_public_audience] assert update.data["to"] == [@activity_pub_public_audience]

View File

@ -3,12 +3,13 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
use Oban.Testing, repo: Mobilizon.Storage.Repo use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory import Mobilizon.Factory
import Mox import Mox
import ExUnit.CaptureLog
alias Mobilizon.{Actors, Events, Posts} alias Mobilizon.{Actors, Events, Posts}
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Posts.Post alias Mobilizon.Posts.Post
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.HTTP.ActivityPub.Mock alias Mobilizon.Service.HTTP.ActivityPub.Mock
@ -50,6 +51,29 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
assert actor.summary == "<p>Some bio</p>" assert actor.summary == "<p>Some bio</p>"
end end
test "it fails for incoming update activies on local actors" do
%Actor{url: relay_actor_url} = Relay.get_actor()
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
object =
update_data["object"]
|> Map.put("actor", relay_actor_url)
|> Map.put("id", relay_actor_url)
update_data =
update_data
|> Map.put("actor", relay_actor_url)
|> Map.put("object", object)
assert capture_log([level: :warn], fn ->
:error = Transmogrifier.handle_incoming(update_data)
end) =~ "[warning] Activity tried to update an actor that's local or not a group"
{:ok, %Actor{keys: keys}} = Actors.get_actor_by_url(relay_actor_url)
assert Regex.match?(~r/BEGIN RSA PRIVATE KEY/, keys)
end
test "it works for incoming update activities on events" do test "it works for incoming update activities on events" do
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!() data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()