Merge branch 'fixes' into 'main'

Update schema.graphql file

Closes #1088

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

View File

@ -240,8 +240,6 @@ build-docker-tag:
package-app:
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

View File

@ -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>

View File

@ -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
)}`;
}

View File

@ -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(

View File

@ -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
)}`;
}

View File

@ -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>

View File

@ -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
)}`;
}

View File

@ -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">

View File

@ -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;
}

View File

@ -1321,5 +1321,14 @@
"You may now close this page or {return_to_the_homepage}.": "You may now close this page or {return_to_the_homepage}.",
"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"
}

View File

@ -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"
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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),

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>
`;

View File

@ -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>

View File

@ -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: