Merge branch 'fixes' into 'main'
Update schema.graphql file Closes #1088 See merge request framasoft/mobilizon!1212
This commit is contained in:
commit
04ec8fe2d3
|
@ -240,8 +240,6 @@ build-docker-tag:
|
|||
package-app:
|
||||
image: mobilizon/buildpack:1.13.4-erlang-24.3.3-${OS}
|
||||
stage: package
|
||||
before_script:
|
||||
- apt-get update && apt-get install -yq build-essential git curl cmake
|
||||
variables: &release-variables
|
||||
MIX_ENV: "prod"
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
<template>
|
||||
<div class="actor-inline">
|
||||
<div class="actor-avatar">
|
||||
<figure class="image is-24x24" v-if="actor.avatar">
|
||||
<div class="inline-flex items-start">
|
||||
<div class="flex-none mr-2">
|
||||
<figure class="image is-48x48" v-if="actor.avatar">
|
||||
<img class="is-rounded" :src="actor.avatar.url" alt="" />
|
||||
</figure>
|
||||
<b-icon v-else size="is-medium" icon="account-circle" />
|
||||
<b-icon v-else size="is-large" icon="account-circle" />
|
||||
</div>
|
||||
|
||||
<div class="actor-name">
|
||||
<p>
|
||||
<div class="flex-auto">
|
||||
<p class="text-base line-clamp-3 md:line-clamp-2 max-w-xl">
|
||||
{{ displayName(actor) }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 truncate">
|
||||
@{{ usernameWithDomain(actor) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -190,7 +190,7 @@ export default class ShareEventModal extends Vue {
|
|||
}
|
||||
|
||||
get mastodonShareUrl(): string {
|
||||
return `https://toot.karamoff.dev/?text=${encodeURIComponent(
|
||||
return `https://toot.kytta.dev/?text=${encodeURIComponent(
|
||||
this.basicTextToEncode
|
||||
)}`;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
|
||||
import { FETCH_GROUP } from "@/graphql/group";
|
||||
import { IGroup } from "@/types/actor";
|
||||
import { displayName, IGroup } from "@/types/actor";
|
||||
|
||||
@Component({
|
||||
components: { RedirectWithAccount },
|
||||
|
@ -52,7 +52,7 @@ export default class JoinGroupWithAccount extends Vue {
|
|||
}
|
||||
|
||||
get groupTitle(): undefined | string {
|
||||
return this.group?.name || this.group?.preferredUsername;
|
||||
return this.group && displayName(this.group);
|
||||
}
|
||||
|
||||
sentence = this.$t(
|
||||
|
|
|
@ -117,7 +117,7 @@ import { Component, Prop, Vue, Ref } from "vue-property-decorator";
|
|||
import { GroupVisibility } from "@/types/enums";
|
||||
import DiasporaLogo from "../Share/DiasporaLogo.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";
|
||||
|
||||
@Component({
|
||||
|
@ -177,7 +177,7 @@ export default class ShareGroupModal extends Vue {
|
|||
}
|
||||
|
||||
get mastodonShareUrl(): string {
|
||||
return `https://toot.karamoff.dev/?text=${encodeURIComponent(
|
||||
return `https://toot.kytta.dev/?text=${encodeURIComponent(
|
||||
this.basicTextToEncode
|
||||
)}`;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:rounded="true"
|
||||
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">
|
||||
{{ post.title }}
|
||||
</h3>
|
||||
|
|
|
@ -179,7 +179,7 @@ export default class SharePostModal extends Vue {
|
|||
}
|
||||
|
||||
get mastodonShareUrl(): string {
|
||||
return `https://toot.karamoff.dev/?text=${encodeURIComponent(
|
||||
return `https://toot.kytta.dev/?text=${encodeURIComponent(
|
||||
this.basicTextToEncode
|
||||
)}`;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:class="{ 'is-titleless': !title }"
|
||||
>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<div class="media-left hidden md:block">
|
||||
<b-icon icon="alert" type="is-warning" size="is-large" />
|
||||
</div>
|
||||
<div class="media-content">
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<!-- @slot Mandatory title -->
|
||||
<slot />
|
||||
</h2>
|
||||
<p v-show="$slots.desc">
|
||||
<p v-show="$slots.desc" :class="descriptionClasses">
|
||||
<!-- @slot Optional description -->
|
||||
<slot name="desc" />
|
||||
</p>
|
||||
|
@ -21,6 +21,8 @@ import { Component, Prop, Vue } from "vue-property-decorator";
|
|||
@Component
|
||||
export default class EmptyContent extends Vue {
|
||||
@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 }) center!: boolean;
|
||||
}
|
||||
|
|
|
@ -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}.",
|
||||
"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 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"
|
||||
}
|
|
@ -1312,5 +1312,14 @@
|
|||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export function usernameWithDomain(actor: IActor, force = false): string {
|
|||
}
|
||||
|
||||
export function displayName(actor: IActor): string {
|
||||
return actor.name != null && actor.name !== ""
|
||||
return actor && actor.name != null && actor.name !== ""
|
||||
? actor.name
|
||||
: usernameWithDomain(actor);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<p class="modal-card-title">{{ $t("Pick an identity") }}</p>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="list is-hoverable">
|
||||
<div class="list is-hoverable list-none">
|
||||
<a
|
||||
class="list-item"
|
||||
v-for="identity in identities"
|
||||
|
@ -12,7 +12,7 @@
|
|||
:class="{
|
||||
'is-active': currentIdentity && identity.id === currentIdentity.id,
|
||||
}"
|
||||
@click="changeCurrentIdentity(identity)"
|
||||
@click="currentIdentity = identity"
|
||||
>
|
||||
<div class="media">
|
||||
<img
|
||||
|
@ -60,10 +60,11 @@ export default class IdentityPicker extends Vue {
|
|||
|
||||
identities: IActor[] = [];
|
||||
|
||||
currentIdentity: IActor = this.value;
|
||||
get currentIdentity(): IActor {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
changeCurrentIdentity(identity: IActor): void {
|
||||
this.currentIdentity = identity;
|
||||
set currentIdentity(identity: IActor) {
|
||||
this.$emit("input", identity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
]"
|
||||
/>
|
||||
<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">
|
||||
<router-link
|
||||
:to="{
|
||||
|
@ -64,7 +66,7 @@
|
|||
<span class="text-sm block">{{ $t("Uploaded media size") }}</span>
|
||||
</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">
|
||||
<button
|
||||
@click="removeInstanceFollow"
|
||||
|
@ -88,7 +90,7 @@
|
|||
{{ $t("Follow instance") }}
|
||||
</button>
|
||||
</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
|
||||
@click="acceptInstance"
|
||||
v-if="instance.followerStatus == InstanceFollowStatus.PENDING"
|
||||
|
@ -98,12 +100,12 @@
|
|||
</button>
|
||||
<button
|
||||
@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"
|
||||
>
|
||||
{{ $t("Reject follow") }}
|
||||
</button>
|
||||
<p v-else>
|
||||
<p v-if="instance.followerStatus == InstanceFollowStatus.NONE">
|
||||
{{ $t("This instance doesn't follow yours.") }}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -124,7 +126,6 @@ import {
|
|||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { formatBytes } from "@/utils/datetime";
|
||||
import RouteName from "@/router/name";
|
||||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
import { IInstance } from "@/types/instance.model";
|
||||
import { ApolloCache, gql, Reference } from "@apollo/client/core";
|
||||
import { InstanceFollowStatus } from "@/types/enums";
|
||||
|
@ -154,38 +155,61 @@ export default class Instance extends Vue {
|
|||
|
||||
async acceptInstance(): Promise<void> {
|
||||
try {
|
||||
const { instance } = this;
|
||||
await this.$apollo.mutate({
|
||||
mutation: ACCEPT_RELAY,
|
||||
variables: {
|
||||
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> {
|
||||
try {
|
||||
const { instance } = this;
|
||||
await this.$apollo.mutate({
|
||||
mutation: REJECT_RELAY,
|
||||
variables: {
|
||||
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,
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
Snackbar.open({
|
||||
message: err.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
this.$notifier.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop following instance
|
||||
*/
|
||||
async removeInstanceFollow(): Promise<void> {
|
||||
const { instance } = this;
|
||||
try {
|
||||
|
@ -232,13 +255,9 @@ export default class Instance extends Vue {
|
|||
});
|
||||
},
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,6 +124,17 @@
|
|||
</p>
|
||||
</div>
|
||||
</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 v-else-if="instances && instances.elements.length == 0">
|
||||
<empty-content icon="lan-disconnect" :inline="true">
|
||||
|
@ -163,6 +174,8 @@ import {
|
|||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||
|
||||
const INSTANCES_PAGE_LIMIT = 10;
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
instances: {
|
||||
|
@ -171,7 +184,7 @@ const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
|||
variables() {
|
||||
return {
|
||||
page: this.instancePage,
|
||||
limit: 10,
|
||||
limit: INSTANCES_PAGE_LIMIT,
|
||||
filterDomain: this.filterDomain,
|
||||
filterFollowStatus: this.followStatus,
|
||||
};
|
||||
|
@ -204,6 +217,8 @@ export default class Follows extends Vue {
|
|||
|
||||
InstanceFollowStatus = InstanceFollowStatus;
|
||||
|
||||
INSTANCES_PAGE_LIMIT = INSTANCES_PAGE_LIMIT;
|
||||
|
||||
data(): Record<string, unknown> {
|
||||
return {
|
||||
debouncedUpdateDomainFilter: debounce(this.updateDomainFilter, 500),
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<h1 class="title" v-if="group">
|
||||
{{
|
||||
$t("{group}'s events", {
|
||||
group: group.name || group.preferredUsername,
|
||||
group: displayName(group),
|
||||
})
|
||||
}}
|
||||
</h1>
|
||||
|
@ -78,7 +78,7 @@
|
|||
<b-pagination
|
||||
class="mt-4"
|
||||
:total="group.organizedEvents.total"
|
||||
v-model="eventsPage"
|
||||
v-model="page"
|
||||
:per-page="EVENTS_PAGE_LIMIT"
|
||||
:aria-next-label="$t('Next page')"
|
||||
:aria-previous-label="$t('Previous page')"
|
||||
|
@ -91,17 +91,15 @@
|
|||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component } from "vue-property-decorator";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import RouteName from "@/router/name";
|
||||
import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||
import GroupedMultiEventMinimalistCard from "@/components/Event/GroupedMultiEventMinimalistCard.vue";
|
||||
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
||||
import GroupMixin from "@/mixins/group";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { FETCH_GROUP_EVENTS } from "@/graphql/event";
|
||||
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;
|
||||
|
||||
|
@ -127,10 +125,11 @@ const EVENTS_PAGE_LIMIT = 10;
|
|||
name: this.$route.params.preferredUsername,
|
||||
beforeDateTime: this.showPassedEvents ? new Date() : null,
|
||||
afterDateTime: this.showPassedEvents ? null : new Date(),
|
||||
organisedEventsPage: this.eventsPage,
|
||||
organisedEventsPage: this.page,
|
||||
organisedEventsLimit: EVENTS_PAGE_LIMIT,
|
||||
};
|
||||
},
|
||||
update: (data) => data.group,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -144,15 +143,27 @@ const EVENTS_PAGE_LIMIT = 10;
|
|||
const { group } = this;
|
||||
return {
|
||||
title: this.$t("{group} events", {
|
||||
group: group?.name || usernameWithDomain(group),
|
||||
group: displayName(group),
|
||||
}) as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class GroupEvents extends mixins(GroupMixin) {
|
||||
export default class GroupEvents extends Vue {
|
||||
group!: IGroup;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -170,14 +181,11 @@ export default class GroupEvents extends mixins(GroupMixin) {
|
|||
}
|
||||
|
||||
get showPassedEvents(): boolean {
|
||||
return (
|
||||
this.$route.query.future !== undefined &&
|
||||
this.$route.query.future.toString() === "false"
|
||||
);
|
||||
return this.$route.query.future === "false";
|
||||
}
|
||||
|
||||
set showPassedEvents(value: boolean) {
|
||||
this.$router.push({ query: { future: this.showPassedEvents.toString() } });
|
||||
this.$router.replace({ query: { future: (!value).toString() } });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -27,11 +27,11 @@
|
|||
<div class="title-container">
|
||||
<h1 v-if="group.name">{{ group.name }}</h1>
|
||||
<b-skeleton v-else :animated="true" />
|
||||
<small
|
||||
<span
|
||||
dir="ltr"
|
||||
class="has-text-grey-dark"
|
||||
v-if="group.preferredUsername"
|
||||
>@{{ usernameWithDomain(group) }}</small
|
||||
>@{{ usernameWithDomain(group) }}</span
|
||||
>
|
||||
<b-skeleton v-else :animated="true" />
|
||||
<br />
|
||||
|
@ -78,7 +78,7 @@
|
|||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="flex gap-2">
|
||||
<b-button
|
||||
outlined
|
||||
icon-left="timeline-text"
|
||||
|
@ -101,78 +101,123 @@
|
|||
}"
|
||||
>{{ $t("Group settings") }}</b-button
|
||||
>
|
||||
<b-tooltip
|
||||
v-if="
|
||||
(!isCurrentActorAGroupMember || previewPublic) &&
|
||||
group.openness === Openness.INVITE_ONLY
|
||||
"
|
||||
:label="$t('This group is invite-only')"
|
||||
position="is-bottom"
|
||||
>
|
||||
<b-button disabled type="is-primary">{{
|
||||
$t("Join group")
|
||||
}}</b-button></b-tooltip
|
||||
>
|
||||
<b-button
|
||||
v-else-if="
|
||||
((!isCurrentActorAGroupMember &&
|
||||
!isCurrentActorAPendingGroupMember) ||
|
||||
previewPublic) &&
|
||||
currentActor.id
|
||||
"
|
||||
@click="joinGroup"
|
||||
@keyup.enter="joinGroup"
|
||||
type="is-primary"
|
||||
:disabled="previewPublic"
|
||||
>{{ $t("Join group") }}</b-button
|
||||
<b-dropdown
|
||||
aria-role="list"
|
||||
trap-focus
|
||||
v-show="showJoinButton && showFollowButton"
|
||||
>
|
||||
<template #trigger>
|
||||
<b-button
|
||||
:label="$t('Follow')"
|
||||
type="is-primary"
|
||||
icon-left="rss"
|
||||
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
|
||||
outlined
|
||||
v-else-if="isCurrentActorAPendingGroupMember"
|
||||
v-if="isCurrentActorAPendingGroupMember"
|
||||
@click="leaveGroup"
|
||||
@keyup.enter="leaveGroup"
|
||||
type="is-primary"
|
||||
>{{ $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
|
||||
outlined
|
||||
v-if="isCurrentActorPendingFollow && currentActor.id"
|
||||
|
@ -192,12 +237,20 @@
|
|||
v-if="isCurrentActorFollowing"
|
||||
@click="toggleFollowNotify"
|
||||
@keyup.enter="toggleFollowNotify"
|
||||
class="notification-button p-1.5"
|
||||
outlined
|
||||
:icon-left="
|
||||
isCurrentActorFollowingNotify
|
||||
? 'bell-outline'
|
||||
: 'bell-off-outline'
|
||||
"
|
||||
></b-button>
|
||||
>
|
||||
<span class="sr-only">{{
|
||||
isCurrentActorFollowingNotify
|
||||
? $t("Activate notifications")
|
||||
: $t("Deactivate notifications")
|
||||
}}</span>
|
||||
</b-button>
|
||||
<b-button
|
||||
outlined
|
||||
icon-left="share"
|
||||
|
@ -308,28 +361,6 @@
|
|||
)
|
||||
}}
|
||||
</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>
|
||||
</header>
|
||||
</div>
|
||||
|
@ -506,6 +537,12 @@
|
|||
$t("View full profile")
|
||||
}}</a>
|
||||
</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">
|
||||
{{
|
||||
$tc("{count} members", group.members.total, {
|
||||
|
@ -553,17 +590,6 @@
|
|||
</div>
|
||||
</aside>
|
||||
<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>
|
||||
<subtitle>{{ $t("Upcoming events") }}</subtitle>
|
||||
<div
|
||||
|
@ -577,7 +603,12 @@
|
|||
class="organized-event"
|
||||
/>
|
||||
</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") }}
|
||||
<template #desc>
|
||||
<template v-if="isCurrentActorFollowing">
|
||||
|
@ -594,7 +625,7 @@
|
|||
</template>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
class="my-2"
|
||||
class="my-2 self-center"
|
||||
type="is-text"
|
||||
:to="{
|
||||
name: RouteName.GROUP_EVENTS,
|
||||
|
@ -621,8 +652,8 @@
|
|||
>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<subtitle>{{ $t("Latest posts") }}</subtitle>
|
||||
<section class="flex flex-col items-stretch">
|
||||
<subtitle class="ml-0">{{ $t("Latest posts") }}</subtitle>
|
||||
|
||||
<multi-post-list-item
|
||||
v-if="
|
||||
|
@ -642,13 +673,16 @@
|
|||
{{ $t("No posts yet") }}
|
||||
</empty-content>
|
||||
<b-skeleton animated v-else-if="$apollo.loading"></b-skeleton>
|
||||
<router-link
|
||||
<b-button
|
||||
class="self-center my-2"
|
||||
v-if="posts.total > 0"
|
||||
tag="router-link"
|
||||
type="is-text"
|
||||
:to="{
|
||||
name: RouteName.POSTS,
|
||||
params: { preferredUsername: usernameWithDomain(group) },
|
||||
}"
|
||||
>{{ $t("View all posts") }}</router-link
|
||||
>{{ $t("View all posts") }}</b-button
|
||||
>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -806,25 +840,38 @@ export default class Group extends mixins(GroupMixin) {
|
|||
}
|
||||
|
||||
async joinGroup(): Promise<void> {
|
||||
const [group, currentActorId] = [
|
||||
usernameWithDomain(this.group),
|
||||
this.currentActor.id,
|
||||
];
|
||||
this.$apollo.mutate({
|
||||
mutation: JOIN_GROUP,
|
||||
variables: {
|
||||
groupId: this.group.id,
|
||||
},
|
||||
refetchQueries: [
|
||||
{
|
||||
query: PERSON_STATUS_GROUP,
|
||||
variables: {
|
||||
id: currentActorId,
|
||||
group,
|
||||
},
|
||||
if (!this.currentActor?.id) {
|
||||
this.$router.push({
|
||||
name: RouteName.GROUP_JOIN,
|
||||
params: { preferredUsername: usernameWithDomain(this.group) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const [group, currentActorId] = [
|
||||
usernameWithDomain(this.group),
|
||||
this.currentActor.id,
|
||||
];
|
||||
await this.$apollo.mutate({
|
||||
mutation: JOIN_GROUP,
|
||||
variables: {
|
||||
groupId: this.group.id,
|
||||
},
|
||||
],
|
||||
});
|
||||
refetchQueries: [
|
||||
{
|
||||
query: PERSON_STATUS_GROUP,
|
||||
variables: {
|
||||
id: currentActorId,
|
||||
group,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
this.$notifier.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async openLeaveGroupModal(): Promise<void> {
|
||||
|
@ -870,6 +917,13 @@ export default class Group extends mixins(GroupMixin) {
|
|||
}
|
||||
|
||||
async followGroup(): Promise<void> {
|
||||
if (!this.currentActor?.id) {
|
||||
this.$router.push({
|
||||
name: RouteName.GROUP_FOLLOW,
|
||||
params: { preferredUsername: usernameWithDomain(this.group) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const [group, currentActorId] = [
|
||||
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>
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1380,4 +1469,7 @@ div.container {
|
|||
height: 60vh;
|
||||
width: 100%;
|
||||
}
|
||||
button.button.notification-button ::v-deep span.icon.is-small {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
v-if="post.draft"
|
||||
>{{ $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>
|
||||
<p class="metadata">
|
||||
<router-link
|
||||
|
@ -441,7 +443,6 @@ article.post {
|
|||
h1.title {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
font-size: 38px;
|
||||
font-family: "Roboto", "Helvetica", "Arial", serif;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ exports[`CommentTree renders an empty comment tree 1`] = `
|
|||
</article>
|
||||
</form>
|
||||
<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>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
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">
|
||||
<!---->
|
||||
<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">
|
||||
My Blog Post
|
||||
</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`] = `
|
||||
<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">
|
||||
My Blog Post
|
||||
</h3>
|
||||
|
@ -31,7 +31,7 @@ exports[`PostListItem renders post list item with publisher name 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">
|
||||
<!---->
|
||||
<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">
|
||||
My Blog Post
|
||||
</h3>
|
||||
|
|
202
js/yarn.lock
202
js/yarn.lock
|
@ -1297,9 +1297,9 @@
|
|||
integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.0":
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3"
|
||||
integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==
|
||||
version "0.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.7.tgz#941982134e9b7fad031c857ccfc4a0634fc6a471"
|
||||
integrity sha512-8XC0l0PwCbdg2Uc8zIIf6djNX3lYiz9GqQlC1LJ9WQvTYvcfP8IA9K2IKRnPm5tAX6X/+orF+WwKZ0doGcgJlg==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
@ -1929,9 +1929,9 @@
|
|||
"@types/geojson" "*"
|
||||
|
||||
"@types/lodash@^4.14.141":
|
||||
version "4.14.181"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d"
|
||||
integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==
|
||||
version "4.14.182"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
|
||||
|
||||
"@types/mime@^1":
|
||||
version "1.3.2"
|
||||
|
@ -1949,9 +1949,9 @@
|
|||
integrity sha512-rr20mmx41OkWx4q5du2dv2sESR/6xH2tzScUQXwO8SiaQWa6PYTuan1nqBtA76FR9qkVfZY7nwQwZNC9StX/Ww==
|
||||
|
||||
"@types/node@*":
|
||||
version "17.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.24.tgz#20ba1bf69c1b4ab405c7a01e950c4f446b05029f"
|
||||
integrity sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==
|
||||
version "17.0.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
|
||||
integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
|
@ -2165,13 +2165,13 @@
|
|||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.3.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz#9608a4b6d0427104bccf132f058cba629a6553c0"
|
||||
integrity sha512-w59GpFqDYGnWFim9p6TGJz7a3qWeENJuAKCqjGSx+Hq/bwq3RZwXYqy98KIfN85yDqz9mq6QXiY5h0FjGQLyEg==
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
|
||||
integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.19.0"
|
||||
"@typescript-eslint/type-utils" "5.19.0"
|
||||
"@typescript-eslint/utils" "5.19.0"
|
||||
"@typescript-eslint/scope-manager" "5.20.0"
|
||||
"@typescript-eslint/type-utils" "5.20.0"
|
||||
"@typescript-eslint/utils" "5.20.0"
|
||||
debug "^4.3.2"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
ignore "^5.1.8"
|
||||
|
@ -2202,29 +2202,29 @@
|
|||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.3.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.19.0.tgz#05e587c1492868929b931afa0cb5579b0f728e75"
|
||||
integrity sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ==
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
|
||||
integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.19.0"
|
||||
"@typescript-eslint/types" "5.19.0"
|
||||
"@typescript-eslint/typescript-estree" "5.19.0"
|
||||
"@typescript-eslint/scope-manager" "5.20.0"
|
||||
"@typescript-eslint/types" "5.20.0"
|
||||
"@typescript-eslint/typescript-estree" "5.20.0"
|
||||
debug "^4.3.2"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz#97e59b0bcbcb54dbcdfba96fc103b9020bbe9cb4"
|
||||
integrity sha512-Fz+VrjLmwq5fbQn5W7cIJZ066HxLMKvDEmf4eu1tZ8O956aoX45jAuBB76miAECMTODyUxH61AQM7q4/GOMQ5g==
|
||||
"@typescript-eslint/scope-manager@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
|
||||
integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.19.0"
|
||||
"@typescript-eslint/visitor-keys" "5.19.0"
|
||||
"@typescript-eslint/types" "5.20.0"
|
||||
"@typescript-eslint/visitor-keys" "5.20.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.19.0.tgz#80f2125b0dfe82494bbae1ea99f1c0186d420282"
|
||||
integrity sha512-O6XQ4RI4rQcBGshTQAYBUIGsKqrKeuIOz9v8bckXZnSeXjn/1+BDZndHLe10UplQeJLXDNbaZYrAytKNQO2T4Q==
|
||||
"@typescript-eslint/type-utils@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
|
||||
integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "5.19.0"
|
||||
"@typescript-eslint/utils" "5.20.0"
|
||||
debug "^4.3.2"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
|
@ -2233,10 +2233,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
|
||||
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
|
||||
|
||||
"@typescript-eslint/types@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.19.0.tgz#12d3d600d754259da771806ee8b2c842d3be8d12"
|
||||
integrity sha512-zR1ithF4Iyq1wLwkDcT+qFnhs8L5VUtjgac212ftiOP/ZZUOCuuF2DeGiZZGQXGoHA50OreZqLH5NjDcDqn34w==
|
||||
"@typescript-eslint/types@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
|
||||
integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
|
||||
|
||||
"@typescript-eslint/typescript-estree@3.10.1":
|
||||
version "3.10.1"
|
||||
|
@ -2252,28 +2252,28 @@
|
|||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.19.0.tgz#fc987b8f62883f9ea6a5b488bdbcd20d33c0025f"
|
||||
integrity sha512-dRPuD4ocXdaE1BM/dNR21elSEUPKaWgowCA0bqJ6YbYkvtrPVEvZ+zqcX5a8ECYn3q5iBSSUcBBD42ubaOp0Hw==
|
||||
"@typescript-eslint/typescript-estree@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
|
||||
integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.19.0"
|
||||
"@typescript-eslint/visitor-keys" "5.19.0"
|
||||
"@typescript-eslint/types" "5.20.0"
|
||||
"@typescript-eslint/visitor-keys" "5.20.0"
|
||||
debug "^4.3.2"
|
||||
globby "^11.0.4"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.19.0.tgz#fe87f1e3003d9973ec361ed10d36b4342f1ded1e"
|
||||
integrity sha512-ZuEckdupXpXamKvFz/Ql8YnePh2ZWcwz7APICzJL985Rp5C2AYcHO62oJzIqNhAMtMK6XvrlBTZeNG8n7gS3lQ==
|
||||
"@typescript-eslint/utils@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
|
||||
integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@typescript-eslint/scope-manager" "5.19.0"
|
||||
"@typescript-eslint/types" "5.19.0"
|
||||
"@typescript-eslint/typescript-estree" "5.19.0"
|
||||
"@typescript-eslint/scope-manager" "5.20.0"
|
||||
"@typescript-eslint/types" "5.20.0"
|
||||
"@typescript-eslint/typescript-estree" "5.20.0"
|
||||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
|
@ -2284,12 +2284,12 @@
|
|||
dependencies:
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.19.0.tgz#c84ebc7f6c744707a361ca5ec7f7f64cd85b8af6"
|
||||
integrity sha512-Ym7zZoMDZcAKWsULi2s7UMLREdVQdScPQ/fKWMYefarCztWlHPFVJo8racf8R0Gc8FAEJ2eD4of8As1oFtnQlQ==
|
||||
"@typescript-eslint/visitor-keys@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
|
||||
integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.19.0"
|
||||
"@typescript-eslint/types" "5.20.0"
|
||||
eslint-visitor-keys "^3.0.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"
|
||||
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:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
|
||||
|
@ -3138,6 +3133,11 @@ async@^2.6.2:
|
|||
dependencies:
|
||||
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:
|
||||
version "0.4.0"
|
||||
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"
|
||||
|
||||
babel-loader@^8.2.2:
|
||||
version "8.2.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b"
|
||||
integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==
|
||||
version "8.2.5"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
|
||||
integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
|
||||
dependencies:
|
||||
find-cache-dir "^3.3.1"
|
||||
loader-utils "^2.0.0"
|
||||
|
@ -3372,6 +3372,13 @@ brace-expansion@^1.1.7:
|
|||
balanced-match "^1.0.0"
|
||||
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:
|
||||
version "3.0.2"
|
||||
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"
|
||||
|
||||
core-js-compat@^3.20.2, core-js-compat@^3.21.0, core-js-compat@^3.8.3:
|
||||
version "3.22.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.0.tgz#7ce17ab57c378be2c717c7c8ed8f82a50a25b3e4"
|
||||
integrity sha512-WwA7xbfRGrk8BGaaHlakauVXrlYmAIkk8PNGb1FDQS+Rbrewc3pgFfwJFRw6psmJVAll7Px9UHRYE16oRQnwAQ==
|
||||
version "3.22.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.1.tgz#47b9c5e79efbf13935f637449fa1cdec8cd9515f"
|
||||
integrity sha512-CWbNqTluLMvZg1cjsQUbGiCM91dobSHKfDIyCoxuqxthdjGuUlaMbCsSehP3CBiVvG0C7P6UIrC1v0hgFE75jw==
|
||||
dependencies:
|
||||
browserslist "^4.20.2"
|
||||
semver "7.0.0"
|
||||
|
@ -3866,9 +3873,9 @@ core-js@^2.4.0, core-js@^2.5.0:
|
|||
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||
|
||||
core-js@^3.6.4, core-js@^3.8.3:
|
||||
version "3.22.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.0.tgz#b52007870c5e091517352e833b77f0b2d2b259f3"
|
||||
integrity sha512-8h9jBweRjMiY+ORO7bdWSeWfHhLPO7whobj7Z2Bl0IDo00C228EdGgH7FE4jGumbEjzcFfkfW8bXgdkEDhnwHQ==
|
||||
version "3.22.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.1.tgz#1936e4f1da82675fe22ae10ee60ef638cd9752fd"
|
||||
integrity sha512-l6CwCLq7XgITOQGhv1dIUmwCFoqFjyQ6zQHUCQlS0xKmb9d6OHIg8jDiEoswhaettT21BSF5qKr6kbvE+aKwxw==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.3"
|
||||
|
@ -4371,9 +4378,9 @@ ejs@^3.1.6:
|
|||
jake "^10.6.1"
|
||||
|
||||
electron-to-chromium@^1.4.84:
|
||||
version "1.4.111"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz#897613f6504f3f17c9381c7499a635b413e4df4e"
|
||||
integrity sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw==
|
||||
version "1.4.114"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.114.tgz#d85ec0808dd50b0cf6e6b262480ffd385f71c873"
|
||||
integrity sha512-gRwLpVYWHGbERPU6o8pKfR168V6enWEXzZc6zQNNXbgJ7UJna+9qzAIHY94+9KOv71D/CH+QebLA9pChD2q8zA==
|
||||
|
||||
emittery@^0.8.1:
|
||||
version "0.8.1"
|
||||
|
@ -4994,11 +5001,11 @@ file-entry-cache@^6.0.1:
|
|||
flat-cache "^3.0.4"
|
||||
|
||||
filelist@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
|
||||
integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83"
|
||||
integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
minimatch "^5.0.1"
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
|
@ -5156,9 +5163,9 @@ functional-red-black-tree@^1.0.1:
|
|||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
functions-have-names@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21"
|
||||
integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
gensync@^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"
|
||||
|
||||
has-bigints@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
|
||||
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
||||
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
@ -5502,9 +5509,9 @@ http-proxy-agent@^4.0.1:
|
|||
debug "4"
|
||||
|
||||
http-proxy-middleware@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a"
|
||||
integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.5.tgz#2d12fb41a414889372643a1f54279a2f6470aa93"
|
||||
integrity sha512-ORErEaxkjyrhifofwCuQttHPUSestLtiPDwV0qQOFB0ww6695H953wIGRnkakw1K+GAP+t8/RPbfDB75RFL4Fg==
|
||||
dependencies:
|
||||
"@types/http-proxy" "^1.17.8"
|
||||
http-proxy "^1.18.1"
|
||||
|
@ -5694,9 +5701,9 @@ is-ci@^1.0.10:
|
|||
ci-info "^1.5.0"
|
||||
|
||||
is-core-module@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
|
||||
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
|
@ -5946,11 +5953,11 @@ iterall@^1.2.2:
|
|||
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
|
||||
|
||||
jake@^10.6.1:
|
||||
version "10.8.4"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.4.tgz#f6a8b7bf90c6306f768aa82bb7b98bf4ca15e84a"
|
||||
integrity sha512-MtWeTkl1qGsWUtbl/Jsca/8xSoK3x0UmS82sNbjqxxG/de/M/3b1DntdjHgPMC50enlTNwXOCRqPXLLt5cCfZA==
|
||||
version "10.8.5"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
|
||||
integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
|
||||
dependencies:
|
||||
async "0.9.x"
|
||||
async "^3.2.3"
|
||||
chalk "^4.0.2"
|
||||
filelist "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
@ -6913,6 +6920,13 @@ minimatch@^3.0.4, minimatch@^3.1.2:
|
|||
dependencies:
|
||||
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:
|
||||
version "1.2.6"
|
||||
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"
|
||||
|
||||
sass@^1.34.1:
|
||||
version "1.50.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.0.tgz#3e407e2ebc53b12f1e35ce45efb226ea6063c7c8"
|
||||
integrity sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==
|
||||
version "1.50.1"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.1.tgz#e9b078a1748863013c4712d2466ce8ca4e4ed292"
|
||||
integrity sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
|
|
|
@ -28,7 +28,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Update do
|
|||
Logger.debug("updating an activity")
|
||||
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, activity} = create_activity(update_data, local)
|
||||
maybe_federate(activity)
|
||||
|
|
|
@ -8,7 +8,9 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
|
|||
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay, Transmogrifier, Visibility}
|
||||
alias Mobilizon.Federation.HTTPSignatures.Signature
|
||||
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 """
|
||||
Publish an activity to all appropriated audiences inboxes
|
||||
|
@ -77,7 +79,7 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
|
|||
Tesla.Env.result()
|
||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||
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)
|
||||
date = Signature.generate_date_header()
|
||||
|
@ -87,7 +89,7 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
|
|||
signature =
|
||||
Signature.sign(actor, %{
|
||||
"(request-target)": "post #{path}",
|
||||
host: host,
|
||||
host: create_full_domain_string(uri),
|
||||
"content-length": byte_size(json),
|
||||
digest: digest,
|
||||
date: date
|
||||
|
|
|
@ -10,6 +10,8 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
|
|||
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils}
|
||||
require Logger
|
||||
|
||||
@collection_element_task_processing_time 60_000
|
||||
|
||||
@doc """
|
||||
Refresh a remote profile
|
||||
"""
|
||||
|
@ -158,7 +160,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
|
|||
|
||||
items
|
||||
|> 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")
|
||||
:ok
|
||||
|
|
|
@ -14,9 +14,9 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier}
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.WebFinger
|
||||
alias Mobilizon.Service.Workers.Background
|
||||
|
||||
alias Mobilizon.GraphQL.API.Follows
|
||||
alias Mobilizon.Service.Workers.Background
|
||||
import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1]
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -172,14 +172,14 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
defp fetch_actor("http://" <> address), do: fetch_actor(address)
|
||||
|
||||
defp fetch_actor(address) do
|
||||
%URI{host: host} = URI.parse("http://" <> address)
|
||||
%URI{host: host} = uri = URI.parse("http://" <> address)
|
||||
|
||||
cond do
|
||||
String.contains?(address, "@") ->
|
||||
check_actor(address)
|
||||
|
||||
!is_nil(host) ->
|
||||
check_actor("relay@#{host}")
|
||||
uri |> create_full_domain_string() |> check_actor()
|
||||
|
||||
true ->
|
||||
{:error, :bad_url}
|
||||
|
|
|
@ -41,7 +41,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
params = %{
|
||||
reporter_id: params["reporter"].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"] || "",
|
||||
additional: %{
|
||||
"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
|
||||
{:ok, activity, new_actor}
|
||||
else
|
||||
{:error, :update_not_allowed} ->
|
||||
Logger.warn("Activity tried to update an actor that's local or not a group",
|
||||
activity: params
|
||||
)
|
||||
|
||||
:error
|
||||
|
||||
e ->
|
||||
Sentry.capture_message("Error while handling an Update activity",
|
||||
extra: %{params: params}
|
||||
|
@ -614,19 +622,25 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:error, :unknown_actor}
|
||||
|
||||
{:ok, %Actor{} = actor} ->
|
||||
case is_group_object_gone(object_id) do
|
||||
{:ok, object} ->
|
||||
if Utils.origin_check_from_id?(actor_url, object_id) ||
|
||||
Permission.can_delete_group_object?(actor, object) do
|
||||
Actions.Delete.delete(object, actor, false)
|
||||
else
|
||||
Logger.warn("Object origin check failed")
|
||||
:error
|
||||
end
|
||||
# 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
|
||||
# The group object is no longer there, we can remove the element
|
||||
{:ok, entity} ->
|
||||
if Utils.origin_check_from_id?(actor_url, object_id) ||
|
||||
Permission.can_delete_group_object?(actor, entity) do
|
||||
Actions.Delete.delete(entity, actor, false)
|
||||
else
|
||||
Logger.warn("Object origin check failed")
|
||||
:error
|
||||
end
|
||||
|
||||
{:error, err} ->
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
{:error, err} ->
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1207,4 +1221,9 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
moderator.domain == group.domain
|
||||
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
|
||||
|
|
|
@ -45,25 +45,30 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||
def update(%Actor{} = old_actor, args, additional) do
|
||||
updater_actor = Map.get(args, :updater_actor) || Map.get(additional, :updater_actor)
|
||||
|
||||
case Actors.update_actor(old_actor, args) do
|
||||
{:ok, %Actor{} = new_actor} ->
|
||||
GroupActivity.insert_activity(new_actor,
|
||||
subject: "group_updated",
|
||||
old_group: old_actor,
|
||||
updater_actor: 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
|
||||
{:ok, %Actor{} = new_actor} ->
|
||||
GroupActivity.insert_activity(new_actor,
|
||||
subject: "group_updated",
|
||||
old_group: old_actor,
|
||||
updater_actor: updater_actor
|
||||
)
|
||||
|
||||
actor_as_data = Convertible.model_to_as(new_actor)
|
||||
Cachex.del(:activity_pub, "actor_#{new_actor.preferred_username}")
|
||||
audience = Audience.get_audience(new_actor)
|
||||
actor_as_data = Convertible.model_to_as(new_actor)
|
||||
Cachex.del(:activity_pub, "actor_#{new_actor.preferred_username}")
|
||||
audience = Audience.get_audience(new_actor)
|
||||
|
||||
additional = Map.merge(additional, %{"actor" => (updater_actor || old_actor).url})
|
||||
additional = Map.merge(additional, %{"actor" => (updater_actor || old_actor).url})
|
||||
|
||||
update_data = make_update_data(actor_as_data, Map.merge(audience, additional))
|
||||
{:ok, new_actor, update_data}
|
||||
update_data = make_update_data(actor_as_data, Map.merge(audience, additional))
|
||||
{:ok, new_actor, update_data}
|
||||
|
||||
{:error, %Ecto.Changeset{} = err} ->
|
||||
{:error, err}
|
||||
{:error, %Ecto.Changeset{} = err} ->
|
||||
{:error, err}
|
||||
end
|
||||
else
|
||||
{:error, :update_not_allowed}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -672,8 +672,15 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
@doc """
|
||||
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()}
|
||||
def pem_to_public_key(pem) do
|
||||
defp pem_to_public_key(pem) do
|
||||
[key_code] = :public_key.pem_decode(pem)
|
||||
key = :public_key.pem_entry_decode(key_code)
|
||||
|
||||
|
@ -686,14 +693,8 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
@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
|
||||
|
||||
def make_signature(actor, id, date) do
|
||||
@spec make_signature(Actor.t(), String.t(), DateTime.t()) :: list({atom(), String.t()})
|
||||
defp make_signature(actor, id, date) do
|
||||
uri = URI.parse(id)
|
||||
|
||||
signature =
|
||||
|
@ -780,4 +781,16 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
params
|
||||
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
|
||||
|
|
|
@ -17,6 +17,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
alias Mobilizon.Service.RichMedia.Parser
|
||||
alias Mobilizon.Web.Upload
|
||||
import Mobilizon.Federation.ActivityStream.Converter.Utils, only: [get_address: 1]
|
||||
import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1]
|
||||
|
||||
@behaviour Converter
|
||||
|
||||
|
@ -54,7 +55,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
outbox_url: data["outbox"],
|
||||
following_url: data["following"],
|
||||
followers_url: data["followers"],
|
||||
domain: URI.parse(data["id"]).host,
|
||||
domain: data["id"] |> URI.new!() |> create_full_domain_string(),
|
||||
manually_approves_followers: data["manuallyApprovesFollowers"],
|
||||
type: data["type"],
|
||||
visibility: if(Map.get(data, "discoverable", false) == true, do: :public, else: :unlisted),
|
||||
|
|
|
@ -67,33 +67,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
def as_to_model(%{"object" => objects} = object) do
|
||||
with {:ok, %Actor{} = reporter} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
|
||||
%Actor{} = reported <-
|
||||
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),
|
||||
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
|
||||
%Actor{} = reported <- find_reported(objects),
|
||||
event <- find_event(objects),
|
||||
comments <- find_comments(objects, reported, event) do
|
||||
%{
|
||||
"reporter" => reporter,
|
||||
"uri" => object["id"],
|
||||
|
@ -104,4 +80,41 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
}
|
||||
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
|
||||
|
|
|
@ -34,6 +34,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
|||
@spec fetch_mentions([map()]) :: [map()]
|
||||
def fetch_mentions(mentions) when is_list(mentions) do
|
||||
Logger.debug("fetching mentions")
|
||||
Logger.debug(inspect(mentions))
|
||||
|
||||
Enum.reduce(mentions, [], fn mention, acc -> create_mention(mention, acc) end)
|
||||
end
|
||||
|
|
|
@ -95,6 +95,9 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
|
|||
actor_url = key_id_to_actor_url(kid)
|
||||
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} <-
|
||||
ActivityPubActor.make_actor_from_url(actor_url, ignore_sign_object_fetches: true) do
|
||||
get_actor_public_key(actor)
|
||||
|
|
|
@ -129,6 +129,7 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
| :address_invalid
|
||||
| :http_error
|
||||
| :webfinger_information_not_json
|
||||
| :webfinger_information_not_valid
|
||||
| :no_url_in_webfinger_data
|
||||
|
||||
@doc """
|
||||
|
@ -164,7 +165,9 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
end
|
||||
|
||||
@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
|
||||
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()}
|
||||
defp find_webfinger_endpoint(domain) when is_binary(domain) do
|
||||
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}} <-
|
||||
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
|
||||
{:ok, link_template}
|
||||
else
|
||||
|
@ -225,7 +229,8 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
|
||||
_ ->
|
||||
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
|
||||
|
@ -243,28 +248,36 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
end
|
||||
|
||||
@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
|
||||
data =
|
||||
Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
|
||||
case {link["type"], link["rel"]} do
|
||||
{"application/activity+json", "self"} ->
|
||||
Map.put(data, "url", link["href"])
|
||||
links = Map.get(doc, "links")
|
||||
subject = Map.get(doc, "subject")
|
||||
|
||||
{nil, _rel} ->
|
||||
Logger.debug("No type declared for the following link #{inspect(link)}")
|
||||
data
|
||||
if !is_nil(links) && !is_nil(subject) do
|
||||
data =
|
||||
Enum.reduce(links, %{"subject" => subject}, fn link, data ->
|
||||
case {link["type"], link["rel"]} do
|
||||
{"application/activity+json", "self"} ->
|
||||
Map.put(data, "url", link["href"])
|
||||
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"Unhandled type to finger: #{inspect(link["type"])}"
|
||||
end)
|
||||
{nil, _rel} ->
|
||||
Logger.debug("No type declared for the following link #{inspect(link)}")
|
||||
data
|
||||
|
||||
data
|
||||
end
|
||||
end)
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"Unhandled type to finger: #{inspect(link)}"
|
||||
end)
|
||||
|
||||
{:ok, data}
|
||||
data
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, data}
|
||||
else
|
||||
{:error, :webfinger_information_not_valid}
|
||||
end
|
||||
end
|
||||
|
||||
defp webfinger_from_json(_doc), do: {:error, :webfinger_information_not_json}
|
||||
|
|
|
@ -69,10 +69,6 @@ defmodule Mobilizon.GraphQL.API.Follows do
|
|||
)
|
||||
|
||||
case Actors.check_follow(follower, followed) do
|
||||
%Follower{approved: false} = follow ->
|
||||
Actors.delete_follower(follow)
|
||||
{:error, "Follow already rejected"}
|
||||
|
||||
%Follower{} = follow ->
|
||||
Actions.Reject.reject(
|
||||
:follow,
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule Mobilizon.Service.DateTime do
|
|||
|
||||
@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()
|
||||
def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do
|
||||
Mobilizon.Cldr.DateTime.to_string!(datetime,
|
||||
|
@ -75,7 +77,7 @@ defmodule Mobilizon.Service.DateTime do
|
|||
def calculate_next_day_notification(%Date{} = day, options \\ []) do
|
||||
compare_to = Keyword.get(options, :compare_to, DateTime.utc_now())
|
||||
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)
|
||||
|
||||
|
@ -145,7 +147,7 @@ defmodule Mobilizon.Service.DateTime do
|
|||
@spec appropriate_first_day_of_week(DateTime.t(), keyword) :: DateTime.t() | nil
|
||||
defp appropriate_first_day_of_week(%DateTime{} = datetime, options) do
|
||||
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)
|
||||
|
||||
|
@ -170,7 +172,7 @@ defmodule Mobilizon.Service.DateTime do
|
|||
options
|
||||
) do
|
||||
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)
|
||||
end
|
||||
|
||||
|
@ -182,7 +184,7 @@ defmodule Mobilizon.Service.DateTime do
|
|||
compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today())
|
||||
compare_to = Keyword.get(options, :compare_to_datetime, DateTime.utc_now())
|
||||
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)
|
||||
|
||||
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
|
||||
DateTime.to_date(one) == DateTime.to_date(two)
|
||||
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
|
||||
|
|
|
@ -8,8 +8,8 @@ defmodule Mobilizon.Service.Formatter do
|
|||
Formats input text to structured data, extracts mentions and hashtags.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Service.Formatter.HTML
|
||||
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()
|
||||
defp escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
|
||||
case Actors.get_actor_by_name(nickname) do
|
||||
%Actor{} ->
|
||||
case ActivityPubActor.find_or_make_actor_from_nickname(nickname) do
|
||||
{:ok, %Actor{}} ->
|
||||
# escape markdown characters with `\\`
|
||||
# (we don't want something like @user__name to be parsed by markdown)
|
||||
String.replace(mention, @markdown_characters_regex, "\\\\\\1")
|
||||
|
||||
nil ->
|
||||
{:error, _err} ->
|
||||
buffer
|
||||
end
|
||||
end
|
||||
|
||||
@spec mention_handler(String.t(), String.t(), any(), map()) :: {String.t(), map()}
|
||||
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 ->
|
||||
# link = "<span class='h-card mention'>@<span>#{preferred_username}</span></span>"
|
||||
#
|
||||
# {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 =
|
||||
# "<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})}}
|
||||
|
||||
nil ->
|
||||
{:error, _} ->
|
||||
{buffer, acc}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Mobilizon.Service.Workers.CleanSuspendedActors do
|
||||
@moduledoc """
|
||||
Worker to clean unattached media
|
||||
Worker to clean suspended actors
|
||||
"""
|
||||
|
||||
use Oban.Worker, queue: "background"
|
||||
|
|
|
@ -24,6 +24,9 @@ defmodule Mobilizon.Service.Workers.RefreshInstances do
|
|||
| {:error,
|
||||
Mobilizon.Federation.ActivityPub.Actor.make_actor_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
|
||||
ActivityPubActor.find_or_make_actor_from_nickname("relay@#{domain}")
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -167,7 +167,7 @@ defmodule Mobilizon.Mixfile do
|
|||
{:mogrify, "~> 0.9"},
|
||||
{:linkify, "~> 0.3"},
|
||||
{:http_signatures, "~> 0.1.0"},
|
||||
{:ex_cldr, "2.27.1"},
|
||||
{:ex_cldr, "~> 2.28.0"},
|
||||
{:ex_cldr_dates_times, "~> 2.2"},
|
||||
{:ex_optimizer, "~> 0.1"},
|
||||
{:progress_bar, "~> 2.0"},
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -34,12 +34,12 @@
|
|||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"erlport": {:hex, :erlport, "0.10.1", "c96ffa51bbcab0298232fcdfe8c3e110f1598011de71ae6b9082b80c9e2e476a", [:rebar3], [], "hexpm", "34931e8cb62a131d1bc8a2bd04d4007c73c03e4f10e22ee4a218e7172227a918"},
|
||||
"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_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_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_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"},
|
||||
|
|
File diff suppressed because it is too large
Load diff
200
schema.graphql
200
schema.graphql
|
@ -175,6 +175,9 @@ type Config {
|
|||
"The instance's admins languages"
|
||||
languages: [String]
|
||||
|
||||
"The instance list of event categories possibilities"
|
||||
eventCategories: [EventCategoryOption]
|
||||
|
||||
"Whether the registrations are opened"
|
||||
registrationsOpen: Boolean
|
||||
|
||||
|
@ -246,6 +249,9 @@ type Config {
|
|||
|
||||
"The instance list of export formats"
|
||||
exportFormats: ExportFormats
|
||||
|
||||
"Configuration for diverse analytics services"
|
||||
analytics: [Analytics]
|
||||
}
|
||||
|
||||
"A tag"
|
||||
|
@ -368,6 +374,15 @@ human-readable ID strings.
|
|||
"""
|
||||
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"
|
||||
type PaginatedDiscussionList {
|
||||
"A list of discussion"
|
||||
|
@ -667,6 +682,26 @@ type RootSubscriptionType {
|
|||
): 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"
|
||||
type DeletedFeedToken {
|
||||
"The user that owned the deleted feed token"
|
||||
|
@ -930,6 +965,27 @@ type PaginatedFollowerList {
|
|||
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"
|
||||
enum EventStatus {
|
||||
"The event is tentative"
|
||||
|
@ -1154,11 +1210,14 @@ type Person implements ActionLogObject & Actor {
|
|||
limit: Int
|
||||
): PaginatedParticipantList
|
||||
|
||||
"The list of group this person is member of"
|
||||
"The list of groups this person is member of"
|
||||
memberships(
|
||||
"Filter by group federated username"
|
||||
group: String
|
||||
|
||||
"Filter by group ID"
|
||||
groupId: ID
|
||||
|
||||
"The page in the paginated memberships list"
|
||||
page: Int
|
||||
|
||||
|
@ -1514,7 +1573,7 @@ type RootMutationType {
|
|||
attributedToId: ID
|
||||
|
||||
"The event's category"
|
||||
category: String
|
||||
category: EventCategory
|
||||
|
||||
"The event's physical address"
|
||||
physicalAddress: AddressInput
|
||||
|
@ -1580,7 +1639,7 @@ type RootMutationType {
|
|||
attributedToId: ID
|
||||
|
||||
"The event's category"
|
||||
category: String
|
||||
category: EventCategory
|
||||
|
||||
"The event's physical address"
|
||||
physicalAddress: AddressInput
|
||||
|
@ -1841,6 +1900,12 @@ type RootMutationType {
|
|||
noteId: ID!
|
||||
): DeletedObject
|
||||
|
||||
"Add an instance subscription"
|
||||
addInstance(
|
||||
"The instance domain to add"
|
||||
domain: String!
|
||||
): Instance
|
||||
|
||||
"Add a relay subscription"
|
||||
addRelay(
|
||||
"The relay hostname to add"
|
||||
|
@ -1910,6 +1975,24 @@ type RootMutationType {
|
|||
instanceLanguages: [String]
|
||||
): 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"
|
||||
createTodoList(
|
||||
"The todo list title"
|
||||
|
@ -2225,6 +2308,9 @@ type RootQueryType {
|
|||
"Whether the event is online or in person"
|
||||
type: EventType
|
||||
|
||||
"The category for the event"
|
||||
category: String
|
||||
|
||||
"Radius around the location to search in"
|
||||
radius: Float
|
||||
|
||||
|
@ -2258,6 +2344,9 @@ type RootQueryType {
|
|||
"Filter users by email"
|
||||
email: String
|
||||
|
||||
"Filter users by current signed-in IP address"
|
||||
currentSignInIp: String
|
||||
|
||||
"The page in the paginated users list"
|
||||
page: Int
|
||||
|
||||
|
@ -2349,6 +2438,12 @@ type RootQueryType {
|
|||
preferredUsername: String!
|
||||
): Group
|
||||
|
||||
"Get a group by its preferred username"
|
||||
groupById(
|
||||
"The group local ID"
|
||||
id: ID!
|
||||
): Group
|
||||
|
||||
"Get all events"
|
||||
events(
|
||||
"The page in the paginated event list"
|
||||
|
@ -2439,6 +2534,9 @@ type RootQueryType {
|
|||
|
||||
"Filter reports by status"
|
||||
status: ReportStatus
|
||||
|
||||
"Filter reports by domain name"
|
||||
domain: String
|
||||
): PaginatedReportList
|
||||
|
||||
"Get a report by id"
|
||||
|
@ -2486,6 +2584,30 @@ type RootQueryType {
|
|||
direction: String
|
||||
): 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"
|
||||
instance(
|
||||
"The instance domain"
|
||||
|
@ -2569,15 +2691,15 @@ type Instance {
|
|||
"The domain name of the instance"
|
||||
domain: ID
|
||||
|
||||
"Whether this instance has a Mobilizon relay actor"
|
||||
hasRelay: Boolean
|
||||
|
||||
"Do we follow this instance"
|
||||
followerStatus: InstanceFollowStatus
|
||||
|
||||
"Does this instance follow us?"
|
||||
followedStatus: InstanceFollowStatus
|
||||
|
||||
"The number of events on this instance we know of"
|
||||
eventCount: Int
|
||||
|
||||
"The number of profiles on this instance we know of"
|
||||
personCount: Int
|
||||
|
||||
|
@ -2595,6 +2717,9 @@ type Instance {
|
|||
|
||||
"The size of all the media files sent by actors from this instance"
|
||||
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]
|
||||
|
||||
"The event's category"
|
||||
category: String
|
||||
category: EventCategory
|
||||
|
||||
"Whether or not the event is a draft"
|
||||
draft: Boolean
|
||||
|
@ -3296,6 +3421,39 @@ type Activity {
|
|||
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"
|
||||
enum PostVisibility {
|
||||
"Publicly listed and federated. Can be shared."
|
||||
|
@ -3355,6 +3513,20 @@ type ReportNote implements ActionLogObject {
|
|||
insertedAt: DateTime
|
||||
}
|
||||
|
||||
enum AnalyticsConfigurationType {
|
||||
"A string"
|
||||
STRING
|
||||
|
||||
"An integer"
|
||||
INTEGER
|
||||
|
||||
"A boolean"
|
||||
BOOLEAN
|
||||
|
||||
"A float"
|
||||
FLOAT
|
||||
}
|
||||
|
||||
"The types of Group that exist"
|
||||
enum GroupType {
|
||||
"A private group of persons"
|
||||
|
@ -3494,6 +3666,12 @@ type PaginatedFollowedGroupEvents {
|
|||
total: Int
|
||||
}
|
||||
|
||||
enum InstanceFilterFollowStatus {
|
||||
ALL
|
||||
FOLLOWING
|
||||
FOLLOWED
|
||||
}
|
||||
|
||||
"Instance map tiles configuration"
|
||||
type Tiles {
|
||||
"The instance's tiles endpoint"
|
||||
|
@ -3661,6 +3839,11 @@ type AdminSettings {
|
|||
instanceLanguages: [String]
|
||||
}
|
||||
|
||||
enum InstanceFilterSuspendStatus {
|
||||
ALL
|
||||
SUSPENDED
|
||||
}
|
||||
|
||||
"The instance's features"
|
||||
type Features {
|
||||
"Whether groups are activated on this instance"
|
||||
|
@ -3668,9 +3851,6 @@ type Features {
|
|||
|
||||
"Whether event creation is allowed on this instance"
|
||||
eventCreation: Boolean
|
||||
|
||||
"Activate link to Koena Connect"
|
||||
koenaConnect: Boolean
|
||||
}
|
||||
|
||||
"A set of user settings"
|
||||
|
|
|
@ -195,7 +195,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
|
|||
actor = insert(:actor)
|
||||
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["to"] == [@activity_pub_public_audience]
|
||||
|
|
|
@ -3,12 +3,13 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
|
|||
use Oban.Testing, repo: Mobilizon.Storage.Repo
|
||||
import Mobilizon.Factory
|
||||
import Mox
|
||||
import ExUnit.CaptureLog
|
||||
|
||||
alias Mobilizon.{Actors, Events, Posts}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Transmogrifier}
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Service.HTTP.ActivityPub.Mock
|
||||
|
||||
|
@ -50,6 +51,29 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
|
|||
assert actor.summary == "<p>Some bio</p>"
|
||||
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
|
||||
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
|
|
Loading…
Reference in a new issue