Merge branch 'fixes' into 'master'

Fix overflow on group membership cards

See merge request framasoft/mobilizon!940
This commit is contained in:
Thomas Citharel 2021-06-16 15:40:14 +00:00
commit 5eea5e2c81
44 changed files with 2245 additions and 1826 deletions

View File

@ -2,8 +2,9 @@
FROM node:16-alpine as assets FROM node:16-alpine as assets
RUN apk add --no-cache python3 build-base libwebp-tools bash imagemagick ncurses RUN apk add --no-cache python3 build-base libwebp-tools bash imagemagick ncurses
WORKDIR /build
COPY js . COPY js .
RUN ls -a
RUN yarn install \ RUN yarn install \
&& yarn run build && yarn run build
@ -24,7 +25,7 @@ COPY config/config.exs config/prod.exs ./config/
COPY config/docker.exs ./config/runtime.exs COPY config/docker.exs ./config/runtime.exs
COPY rel ./rel COPY rel ./rel
COPY support ./support COPY support ./support
COPY --from=assets ./priv/static ./priv/static COPY --from=assets ./build/priv/static ./priv/static
RUN mix phx.digest \ RUN mix phx.digest \
&& mix release && mix release

View File

@ -73,14 +73,14 @@
"@types/vuedraggable": "^2.23.0", "@types/vuedraggable": "^2.23.0",
"@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0", "@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~5.0.0-beta.0", "@vue/cli-plugin-babel": "~5.0.0-beta.2",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-beta.0", "@vue/cli-plugin-e2e-cypress": "~5.0.0-beta.2",
"@vue/cli-plugin-eslint": "~5.0.0-beta.0", "@vue/cli-plugin-eslint": "~5.0.0-beta.2",
"@vue/cli-plugin-pwa": "~5.0.0-beta.0", "@vue/cli-plugin-pwa": "~5.0.0-beta.2",
"@vue/cli-plugin-router": "~5.0.0-beta.0", "@vue/cli-plugin-router": "~5.0.0-beta.2",
"@vue/cli-plugin-typescript": "~5.0.0-beta.0", "@vue/cli-plugin-typescript": "~5.0.0-beta.2",
"@vue/cli-plugin-unit-jest": "~5.0.0-beta.0", "@vue/cli-plugin-unit-jest": "~5.0.0-beta.2",
"@vue/cli-service": "~5.0.0-beta.0", "@vue/cli-service": "~5.0.0-beta.2",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0", "@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^1.1.0", "@vue/test-utils": "^1.1.0",

View File

@ -0,0 +1,48 @@
<template>
<div class="actor-inline">
<div class="actor-avatar">
<figure class="image is-24x24" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="actor-name">
<p>
{{ actor.name || `@${usernameWithDomain(actor)}` }}
</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { IActor, usernameWithDomain } from "../../types/actor";
@Component
export default class ActorInline extends Vue {
@Prop({ required: true, type: Object }) actor!: IActor;
usernameWithDomain = usernameWithDomain;
}
</script>
<style lang="scss" scoped>
div.actor-inline {
align-items: flex-start;
display: inline-flex;
text-align: inherit;
align-items: top;
div.actor-avatar {
flex-basis: auto;
flex-grow: 0;
flex-shrink: 0;
margin-right: 0.5rem;
}
div.actor-name {
flex-basis: auto;
flex-grow: 1;
flex-shrink: 1;
text-align: inherit;
}
}
</style>

View File

@ -41,7 +41,7 @@
></popover-actor-card ></popover-actor-card
></i18n ></i18n
> >
<small class="has-text-grey activity-date">{{ <small class="has-text-grey-dark activity-date">{{
activity.insertedAt | formatTimeString activity.insertedAt | formatTimeString
}}</small> }}</small>
</div> </div>

View File

@ -27,7 +27,7 @@
></popover-actor-card ></popover-actor-card
></i18n ></i18n
> >
<small class="has-text-grey activity-date">{{ <small class="has-text-grey-dark activity-date">{{
activity.insertedAt | formatTimeString activity.insertedAt | formatTimeString
}}</small> }}</small>
</div> </div>

View File

@ -34,7 +34,7 @@
v-for="detail in details" v-for="detail in details"
:key="detail" :key="detail"
tag="p" tag="p"
class="has-text-grey" class="has-text-grey-dark"
> >
<popover-actor-card <popover-actor-card
:actor="activity.author" :actor="activity.author"
@ -63,7 +63,7 @@
subjectParams.old_group_name subjectParams.old_group_name
}}</b> }}</b>
</i18n> </i18n>
<small class="has-text-grey activity-date">{{ <small class="has-text-grey-dark activity-date">{{
activity.insertedAt | formatTimeString activity.insertedAt | formatTimeString
}}</small> }}</small>
</div> </div>

View File

@ -34,7 +34,7 @@
></popover-actor-card ></popover-actor-card
></i18n ></i18n
> >
<small class="has-text-grey activity-date">{{ <small class="has-text-grey-dark activity-date">{{
activity.insertedAt | formatTimeString activity.insertedAt | formatTimeString
}}</small> }}</small>
</div> </div>

View File

@ -27,7 +27,7 @@
></popover-actor-card ></popover-actor-card
></i18n ></i18n
> >
<small class="has-text-grey activity-date">{{ <small class="has-text-grey-dark activity-date">{{
activity.insertedAt | formatTimeString activity.insertedAt | formatTimeString
}}</small> }}</small>
</div> </div>

View File

@ -37,7 +37,7 @@
></popover-actor-card ></popover-actor-card
></i18n ></i18n
> >
<small class="has-text-grey activity-date">{{ <small class="has-text-grey-dark activity-date">{{
activity.insertedAt | formatTimeString activity.insertedAt | formatTimeString
}}</small> }}</small>
</div> </div>

View File

@ -32,10 +32,10 @@
}}</span }}</span
> >
</div> </div>
<div class="has-text-grey" v-if="!discussion.lastComment.deletedAt"> <div class="has-text-grey-dark" v-if="!discussion.lastComment.deletedAt">
{{ htmlTextEllipsis }} {{ htmlTextEllipsis }}
</div> </div>
<div v-else class="has-text-grey"> <div v-else class="has-text-grey-dark">
{{ $t("[This comment has been deleted]") }} {{ $t("[This comment has been deleted]") }}
</div> </div>
</div> </div>
@ -98,10 +98,9 @@ export default class DiscussionListItem extends Vue {
.discussion-minimalist-title { .discussion-minimalist-title {
color: #3c376e; color: #3c376e;
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, font-family: Roboto, Helvetica, Arial, serif;
Arial, serif; font-size: 16px;
font-size: 1.25rem; font-weight: 500;
font-weight: 700;
flex: 1; flex: 1;
} }
} }

View File

@ -99,7 +99,9 @@ export default class GroupMemberCard extends Vue {
} }
.media-content { .media-content {
overflow: hidden; ::v-deep .tags {
margin-bottom: 0;
}
} }
} }

View File

@ -55,20 +55,21 @@ section {
} }
div.group-section-title { div.group-section-title {
--title-color: $violet-2;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
background: $secondary; background: $secondary;
color: #3a384c; color: var(--title-color);
&.privateSection { &.privateSection {
color: $violet-2; color: $purple-3;
background: $purple-2; background: $violet-2;
} }
::v-deep & > a { ::v-deep & > a {
align-self: center; align-self: center;
margin-right: 5px; margin-right: 5px;
color: $orange-3; color: var(--title-color);
} }
h2 { h2 {

View File

@ -30,6 +30,17 @@ import { IGroup } from "@/types/actor";
}, },
}, },
}, },
metaInfo() {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
title: this.$t("Join group {group}", {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
group: this.groupTitle,
}) as string,
};
},
}) })
export default class JoinGroupWithAccount extends Vue { export default class JoinGroupWithAccount extends Vue {
@Prop({ type: String, required: true }) preferredUsername!: string; @Prop({ type: String, required: true }) preferredUsername!: string;
@ -40,6 +51,10 @@ export default class JoinGroupWithAccount extends Vue {
return this.group?.url; return this.group?.url;
} }
get groupTitle(): undefined | string {
return this.group?.name || this.group?.preferredUsername;
}
sentence = this.$t( sentence = this.$t(
"We will redirect you to your instance in order to interact with this group" "We will redirect you to your instance in order to interact with this group"
); );

View File

@ -0,0 +1,138 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ $t("Share this group") }}</p>
</header>
<section class="modal-card-body is-flex" v-if="group">
<div class="container has-text-centered">
<b-notification
type="is-warning"
v-if="group.visibility !== GroupVisibility.PUBLIC"
:closable="false"
>
{{
$t(
"This group is accessible only through it's link. Be careful where you post this link."
)
}}
</b-notification>
<b-field>
<b-input ref="groupURLInput" :value="group.url" expanded />
<p class="control">
<b-tooltip
:label="$t('URL copied to clipboard')"
:active="showCopiedTooltip"
always
type="is-success"
position="is-left"
>
<b-button
type="is-primary"
icon-right="content-paste"
native-type="button"
@click="copyURL"
@keyup.enter="copyURL"
/>
</b-tooltip>
</p>
</b-field>
<div>
<!-- <b-icon icon="mastodon" size="is-large" type="is-primary" />-->
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"
><b-icon icon="twitter" size="is-large" type="is-primary"
/></a>
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"
><b-icon icon="facebook" size="is-large" type="is-primary"
/></a>
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"
><b-icon icon="linkedin" size="is-large" type="is-primary"
/></a>
<a
:href="diasporaShareUrl"
class="diaspora"
target="_blank"
rel="nofollow noopener"
>
<span data-v-5e15e80a="" class="icon has-text-primary is-large">
<DiasporaLogo alt="diaspora-logo" />
</span>
</a>
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"
><b-icon icon="email" size="is-large" type="is-primary"
/></a>
</div>
</div>
</section>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { GroupVisibility } from "@/types/enums";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import DiasporaLogo from "../../assets/diaspora-icon.svg?inline";
import { displayName, IGroup } from "@/types/actor";
@Component({
components: {
DiasporaLogo,
},
})
export default class ShareGroupModal extends Vue {
@Prop({ type: Object, required: true }) group!: IGroup;
@Ref("groupURLInput") readonly groupURLInput!: any;
GroupVisibility = GroupVisibility;
showCopiedTooltip = false;
get twitterShareUrl(): string {
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(
this.group.url
)}&text=${displayName(this.group)}`;
}
get facebookShareUrl(): string {
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
this.group.url
)}`;
}
get linkedInShareUrl(): string {
return `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(
this.group.url
)}&title=${displayName(this.group)}`;
}
get emailShareUrl(): string {
return `mailto:?to=&body=${this.group.url}&subject=${displayName(
this.group
)}`;
}
get diasporaShareUrl(): string {
return `https://share.diasporafoundation.org/?title=${encodeURIComponent(
displayName(this.group)
)}&url=${encodeURIComponent(this.group.url)}`;
}
copyURL(): void {
this.groupURLInput.$refs.input.select();
document.execCommand("copy");
this.showCopiedTooltip = true;
setTimeout(() => {
this.showCopiedTooltip = false;
}, 2000);
}
}
</script>
<style lang="scss" scoped>
.diaspora span svg {
height: 2rem;
width: 2rem;
}
</style>

View File

@ -12,27 +12,38 @@ import { IMedia } from "@/types/media.model";
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import LazyImage from "../Image/LazyImage.vue"; import LazyImage from "../Image/LazyImage.vue";
const DEFAULT_CARD_URL = "/img/mobilizon_default_card.png";
const DEFAULT_BLURHASH = "MCHKI4El-P-U}+={R-WWoes,Iu-P=?R,xD";
const DEFAULT_WIDTH = 630;
const DEFAULT_HEIGHT = 350;
const DEFAULT_PICTURE = {
url: DEFAULT_CARD_URL,
metadata: {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
blurhash: DEFAULT_BLURHASH,
},
};
@Component({ @Component({
components: { components: {
LazyImage, LazyImage,
}, },
}) })
export default class LazyImageWrapper extends Vue { export default class LazyImageWrapper extends Vue {
@Prop({ required: true, default: null }) @Prop({ required: true })
picture!: IMedia | null; picture!: IMedia;
get pictureOrDefault(): Partial<IMedia> { get pictureOrDefault(): Partial<IMedia> {
if (this.picture === null) {
return DEFAULT_PICTURE;
}
return { return {
url: url: this?.picture?.url,
this?.picture === null
? "/img/mobilizon_default_card.png"
: this?.picture?.url,
metadata: { metadata: {
width: this?.picture?.metadata?.width || 630, width: this?.picture?.metadata?.width,
height: this?.picture?.metadata?.height || 350, height: this?.picture?.metadata?.height,
blurhash: blurhash: this?.picture?.metadata?.blurhash,
this?.picture?.metadata?.blurhash ||
"MCHKI4El-P-U}+={R-WWoes,Iu-P=?R,xD",
}, },
}; };
} }

View File

@ -112,7 +112,12 @@
<span @click="setIdentity(identity)"> <span @click="setIdentity(identity)">
<div class="media-left"> <div class="media-left">
<figure class="image is-32x32" v-if="identity.avatar"> <figure class="image is-32x32" v-if="identity.avatar">
<img class="is-rounded" :src="identity.avatar.url" alt /> <img
class="is-rounded"
loading="lazy"
:src="identity.avatar.url"
alt
/>
</figure> </figure>
<b-icon v-else size="is-medium" icon="account-circle" /> <b-icon v-else size="is-medium" icon="account-circle" />
</div> </div>
@ -133,11 +138,6 @@
:to="{ name: RouteName.UPDATE_IDENTITY }" :to="{ name: RouteName.UPDATE_IDENTITY }"
>{{ $t("My account") }}</b-navbar-item >{{ $t("My account") }}</b-navbar-item
> >
<!-- <b-navbar-item tag="router-link" :to="{ name: RouteName.CREATE_GROUP }">-->
<!-- {{ $t('Create group') }}-->
<!-- </b-navbar-item>-->
<b-navbar-item <b-navbar-item
v-if="currentUser.role === ICurrentUserRole.ADMINISTRATOR" v-if="currentUser.role === ICurrentUserRole.ADMINISTRATOR"
tag="router-link" tag="router-link"

View File

@ -5,7 +5,7 @@
> >
<div class="title-info-wrapper"> <div class="title-info-wrapper">
<p class="post-minimalist-title">{{ post.title }}</p> <p class="post-minimalist-title">{{ post.title }}</p>
<small class="has-text-grey">{{ <small class="has-text-grey-dark">{{
formatDistanceToNow(new Date(post.publishAt || post.insertedAt), { formatDistanceToNow(new Date(post.publishAt || post.insertedAt), {
locale: $dateFnsLocale, locale: $dateFnsLocale,
addSuffix: true, addSuffix: true,
@ -43,10 +43,9 @@ export default class PostListItem extends Vue {
.post-minimalist-title { .post-minimalist-title {
color: #3c376e; color: #3c376e;
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, font-family: Roboto, Helvetica, Arial, serif;
serif; font-size: 16px;
font-size: 1rem; font-weight: 500;
font-weight: 700;
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;

View File

@ -23,6 +23,7 @@
<div class="title-wrapper"> <div class="title-wrapper">
<img <img
class="favicon" class="favicon"
alt=""
v-if="resource.metadata && resource.metadata.faviconUrl" v-if="resource.metadata && resource.metadata.faviconUrl"
:src="resource.metadata.faviconUrl" :src="resource.metadata.faviconUrl"
/> />
@ -31,7 +32,8 @@
<div class="metadata-wrapper"> <div class="metadata-wrapper">
<span class="host" v-if="!inline || preview">{{ urlHostname }}</span> <span class="host" v-if="!inline || preview">{{ urlHostname }}</span>
<span <span
class="published-at is-hidden-mobile" class="published-at"
:class="{ 'is-hidden-mobile': !inline }"
v-if="resource.updatedAt || resource.publishedAt" v-if="resource.updatedAt || resource.publishedAt"
>{{ >{{
(resource.updatedAt || resource.publishedAt) (resource.updatedAt || resource.publishedAt)

View File

@ -369,10 +369,10 @@ export const JOIN_EVENT = gql`
message: $message message: $message
locale: $locale locale: $locale
) { ) {
...ParticipantsQuery ...ParticipantQuery
} }
} }
${PARTICIPANTS_QUERY_FRAGMENT} ${PARTICIPANT_QUERY_FRAGMENT}
`; `;
export const LEAVE_EVENT = gql` export const LEAVE_EVENT = gql`

View File

@ -80,10 +80,22 @@ export const GROUP_FIELDS_FRAGMENTS = gql`
avatar { avatar {
id id
url url
name
metadata {
width
height
blurhash
}
} }
banner { banner {
id id
url url
name
metadata {
width
height
blurhash
}
} }
organizedEvents( organizedEvents(
afterDatetime: $afterDateTime afterDatetime: $afterDateTime

View File

@ -41,6 +41,11 @@ export const POST_FRAGMENT = gql`
id id
url url
name name
metadata {
height
width
blurhash
}
} }
} }
${TAG_FRAGMENT} ${TAG_FRAGMENT}

View File

@ -1043,5 +1043,9 @@
"User settings": "User settings", "User settings": "User settings",
"You changed your email or password": "You changed your email or password", "You changed your email or password": "You changed your email or password",
"Organized by you": "Organized by you", "Organized by you": "Organized by you",
"Move resource to the root folder": "Move resource to the root folder" "Move resource to the root folder": "Move resource to the root folder",
"Share this group": "Share this group",
"This group is accessible only through it's link. Be careful where you post this link.": "This group is accessible only through it's link. Be careful where you post this link.",
"{count} members": "No members|One member|{count} members",
"Share": "Share"
} }

View File

@ -1134,5 +1134,9 @@
"{username} was invited to {group}": "{username} a été invité à {group}", "{username} was invited to {group}": "{username} a été invité à {group}",
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap", "© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
"Organized by you": "Organisé par vous", "Organized by you": "Organisé par vous",
"Move resource to the root folder": "Déplacer la resource dans le dossier racine" "Move resource to the root folder": "Déplacer la resource dans le dossier racine",
"Share this group": "Partager ce groupe",
"This group is accessible only through it's link. Be careful where you post this link.": "Ce groupe est accessible uniquement à travers son lien. Faites attention où vous le diffusez.",
"{count} members": "Aucun membre|Un⋅e membre|{count} membres",
"Share": "Partager"
} }

View File

@ -5,13 +5,7 @@ import {
} from "@/graphql/actor"; } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group"; import { FETCH_GROUP } from "@/graphql/group";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import { import { IActor, IGroup, IPerson, usernameWithDomain } from "@/types/actor";
Group,
IActor,
IGroup,
IPerson,
usernameWithDomain,
} from "@/types/actor";
import { MemberRole } from "@/types/enums"; import { MemberRole } from "@/types/enums";
import { Component, Vue } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
@ -50,14 +44,14 @@ const now = new Date();
variables() { variables() {
return { return {
actorId: this.currentActor.id, actorId: this.currentActor.id,
group: this.group.preferredUsername, group: this.group?.preferredUsername,
}; };
}, },
skip() { skip() {
return ( return (
!this.currentActor || !this.currentActor ||
!this.currentActor.id || !this.currentActor.id ||
!this.group.preferredUsername !this.group?.preferredUsername
); );
}, },
}, },
@ -65,7 +59,7 @@ const now = new Date();
return ( return (
!this.currentActor || !this.currentActor ||
!this.currentActor.id || !this.currentActor.id ||
!this.group.preferredUsername !this.group?.preferredUsername
); );
}, },
}, },
@ -73,7 +67,7 @@ const now = new Date();
}, },
}) })
export default class GroupMixin extends Vue { export default class GroupMixin extends Vue {
group: IGroup = new Group(); group!: IGroup;
currentActor!: IActor; currentActor!: IActor;

View File

@ -1,5 +1,5 @@
import { RouteConfig } from "vue-router"; import { RouteConfig } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
export enum ActorRouteName { export enum ActorRouteName {
GROUP = "Group", GROUP = "Group",
@ -12,14 +12,14 @@ export const actorRoutes: RouteConfig[] = [
{ {
path: "/groups/create", path: "/groups/create",
name: ActorRouteName.CREATE_GROUP, name: ActorRouteName.CREATE_GROUP,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "CreateGroup" */ "@/views/Group/Create.vue"), import(/* webpackChunkName: "CreateGroup" */ "@/views/Group/Create.vue"),
meta: { requiredAuth: true }, meta: { requiredAuth: true },
}, },
{ {
path: "/@:preferredUsername", path: "/@:preferredUsername",
name: ActorRouteName.GROUP, name: ActorRouteName.GROUP,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Group" */ "@/views/Group/Group.vue"), import(/* webpackChunkName: "Group" */ "@/views/Group/Group.vue"),
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
@ -27,7 +27,7 @@ export const actorRoutes: RouteConfig[] = [
{ {
path: "/groups/me", path: "/groups/me",
name: ActorRouteName.MY_GROUPS, name: ActorRouteName.MY_GROUPS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "MyGroups" */ "@/views/Group/MyGroups.vue"), import(/* webpackChunkName: "MyGroups" */ "@/views/Group/MyGroups.vue"),
meta: { requiredAuth: true }, meta: { requiredAuth: true },
}, },

View File

@ -1,5 +1,5 @@
import { RouteConfig } from "vue-router"; import { RouteConfig } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
export enum DiscussionRouteName { export enum DiscussionRouteName {
DISCUSSION_LIST = "DISCUSSION_LIST", DISCUSSION_LIST = "DISCUSSION_LIST",
@ -11,7 +11,7 @@ export const discussionRoutes: RouteConfig[] = [
{ {
path: "/@:preferredUsername/discussions", path: "/@:preferredUsername/discussions",
name: DiscussionRouteName.DISCUSSION_LIST, name: DiscussionRouteName.DISCUSSION_LIST,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "DiscussionsList" */ "@/views/Discussions/DiscussionsList.vue" /* webpackChunkName: "DiscussionsList" */ "@/views/Discussions/DiscussionsList.vue"
), ),
@ -21,7 +21,7 @@ export const discussionRoutes: RouteConfig[] = [
{ {
path: "/@:preferredUsername/discussions/new", path: "/@:preferredUsername/discussions/new",
name: DiscussionRouteName.CREATE_DISCUSSION, name: DiscussionRouteName.CREATE_DISCUSSION,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "CreateDiscussion" */ "@/views/Discussions/Create.vue" /* webpackChunkName: "CreateDiscussion" */ "@/views/Discussions/Create.vue"
), ),
@ -31,7 +31,7 @@ export const discussionRoutes: RouteConfig[] = [
{ {
path: "/@:preferredUsername/c/:slug/:comment_id?", path: "/@:preferredUsername/c/:slug/:comment_id?",
name: DiscussionRouteName.DISCUSSION, name: DiscussionRouteName.DISCUSSION,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Discussion" */ "@/views/Discussions/Discussion.vue" /* webpackChunkName: "Discussion" */ "@/views/Discussions/Discussion.vue"
), ),

View File

@ -1,5 +1,5 @@
import { RouteConfig } from "vue-router"; import { RouteConfig } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
export enum ErrorRouteName { export enum ErrorRouteName {
ERROR = "Error", ERROR = "Error",
@ -9,7 +9,7 @@ export const errorRoutes: RouteConfig[] = [
{ {
path: "/error", path: "/error",
name: ErrorRouteName.ERROR, name: ErrorRouteName.ERROR,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Error" */ "../views/Error.vue"), import(/* webpackChunkName: "Error" */ "../views/Error.vue"),
}, },
]; ];

View File

@ -1,15 +1,15 @@
import { RouteConfig, Route } from "vue-router"; import { RouteConfig, Route } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
const participations = (): Promise<EsModuleComponent> => const participations = (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "participations" */ "@/views/Event/Participants.vue" /* webpackChunkName: "participations" */ "@/views/Event/Participants.vue"
); );
const editEvent = (): Promise<EsModuleComponent> => const editEvent = (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "edit-event" */ "@/views/Event/Edit.vue"); import(/* webpackChunkName: "edit-event" */ "@/views/Event/Edit.vue");
const event = (): Promise<EsModuleComponent> => const event = (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "event" */ "@/views/Event/Event.vue"); import(/* webpackChunkName: "event" */ "@/views/Event/Event.vue");
const myEvents = (): Promise<EsModuleComponent> => const myEvents = (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "my-events" */ "@/views/Event/MyEvents.vue"); import(/* webpackChunkName: "my-events" */ "@/views/Event/MyEvents.vue");
export enum EventRouteName { export enum EventRouteName {
@ -31,7 +31,7 @@ export const eventRoutes: RouteConfig[] = [
{ {
path: "/events/list/:location?", path: "/events/list/:location?",
name: EventRouteName.EVENT_LIST, name: EventRouteName.EVENT_LIST,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "EventList" */ "@/views/Event/EventList.vue"), import(/* webpackChunkName: "EventList" */ "@/views/Event/EventList.vue"),
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
@ -83,35 +83,35 @@ export const eventRoutes: RouteConfig[] = [
{ {
path: "/events/:uuid/participate", path: "/events/:uuid/participate",
name: EventRouteName.EVENT_PARTICIPATE_LOGGED_OUT, name: EventRouteName.EVENT_PARTICIPATE_LOGGED_OUT,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../components/Participation/UnloggedParticipation.vue"), import("../components/Participation/UnloggedParticipation.vue"),
props: true, props: true,
}, },
{ {
path: "/events/:uuid/participate/with-account", path: "/events/:uuid/participate/with-account",
name: EventRouteName.EVENT_PARTICIPATE_WITH_ACCOUNT, name: EventRouteName.EVENT_PARTICIPATE_WITH_ACCOUNT,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../components/Participation/ParticipationWithAccount.vue"), import("../components/Participation/ParticipationWithAccount.vue"),
props: true, props: true,
}, },
{ {
path: "/events/:uuid/participate/without-account", path: "/events/:uuid/participate/without-account",
name: EventRouteName.EVENT_PARTICIPATE_WITHOUT_ACCOUNT, name: EventRouteName.EVENT_PARTICIPATE_WITHOUT_ACCOUNT,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../components/Participation/ParticipationWithoutAccount.vue"), import("../components/Participation/ParticipationWithoutAccount.vue"),
props: true, props: true,
}, },
{ {
path: "/participation/email/confirm/:token", path: "/participation/email/confirm/:token",
name: EventRouteName.EVENT_PARTICIPATE_CONFIRM, name: EventRouteName.EVENT_PARTICIPATE_CONFIRM,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../components/Participation/ConfirmParticipation.vue"), import("../components/Participation/ConfirmParticipation.vue"),
props: true, props: true,
}, },
{ {
path: "/tag/:tag", path: "/tag/:tag",
name: EventRouteName.TAG, name: EventRouteName.TAG,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Search" */ "@/views/Search.vue"), import(/* webpackChunkName: "Search" */ "@/views/Search.vue"),
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },

View File

@ -1,5 +1,5 @@
import { RouteConfig, Route } from "vue-router"; import { RouteConfig, Route } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
export enum GroupsRouteName { export enum GroupsRouteName {
TODO_LISTS = "TODO_LISTS", TODO_LISTS = "TODO_LISTS",
@ -21,16 +21,16 @@ export enum GroupsRouteName {
TIMELINE = "TIMELINE", TIMELINE = "TIMELINE",
} }
const resourceFolder = (): Promise<EsModuleComponent> => const resourceFolder = (): Promise<ImportedComponent> =>
import("@/views/Resources/ResourceFolder.vue"); import("@/views/Resources/ResourceFolder.vue");
const groupEvents = (): Promise<EsModuleComponent> => const groupEvents = (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "groupEvents" */ "@/views/Event/GroupEvents.vue"); import(/* webpackChunkName: "groupEvents" */ "@/views/Event/GroupEvents.vue");
export const groupsRoutes: RouteConfig[] = [ export const groupsRoutes: RouteConfig[] = [
{ {
path: "/@:preferredUsername/todo-lists", path: "/@:preferredUsername/todo-lists",
name: GroupsRouteName.TODO_LISTS, name: GroupsRouteName.TODO_LISTS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Todos/TodoLists.vue"), import("@/views/Todos/TodoLists.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },
@ -38,7 +38,7 @@ export const groupsRoutes: RouteConfig[] = [
{ {
path: "/todo-lists/:id", path: "/todo-lists/:id",
name: GroupsRouteName.TODO_LIST, name: GroupsRouteName.TODO_LIST,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Todos/TodoList.vue"), import("@/views/Todos/TodoList.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },
@ -46,7 +46,7 @@ export const groupsRoutes: RouteConfig[] = [
{ {
path: "/todo/:todoId", path: "/todo/:todoId",
name: GroupsRouteName.TODO, name: GroupsRouteName.TODO,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Todos/Todo.vue"), import("@/views/Todos/Todo.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },
@ -67,7 +67,7 @@ export const groupsRoutes: RouteConfig[] = [
}, },
{ {
path: "/@:preferredUsername/settings", path: "/@:preferredUsername/settings",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Group/Settings.vue"), import("@/views/Group/Settings.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },
@ -77,20 +77,20 @@ export const groupsRoutes: RouteConfig[] = [
{ {
path: "public", path: "public",
name: GroupsRouteName.GROUP_PUBLIC_SETTINGS, name: GroupsRouteName.GROUP_PUBLIC_SETTINGS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../views/Group/GroupSettings.vue"), import("../views/Group/GroupSettings.vue"),
}, },
{ {
path: "members", path: "members",
name: GroupsRouteName.GROUP_MEMBERS_SETTINGS, name: GroupsRouteName.GROUP_MEMBERS_SETTINGS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../views/Group/GroupMembers.vue"), import("../views/Group/GroupMembers.vue"),
props: true, props: true,
}, },
{ {
path: "followers", path: "followers",
name: GroupsRouteName.GROUP_FOLLOWERS_SETTINGS, name: GroupsRouteName.GROUP_FOLLOWERS_SETTINGS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("../views/Group/GroupFollowers.vue"), import("../views/Group/GroupFollowers.vue"),
props: true, props: true,
}, },
@ -98,7 +98,7 @@ export const groupsRoutes: RouteConfig[] = [
}, },
{ {
path: "/@:preferredUsername/p/new", path: "/@:preferredUsername/p/new",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Posts/Edit.vue"), import("@/views/Posts/Edit.vue"),
props: true, props: true,
name: GroupsRouteName.POST_CREATE, name: GroupsRouteName.POST_CREATE,
@ -106,7 +106,7 @@ export const groupsRoutes: RouteConfig[] = [
}, },
{ {
path: "/p/:slug/edit", path: "/p/:slug/edit",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Posts/Edit.vue"), import("@/views/Posts/Edit.vue"),
props: (route: Route): Record<string, unknown> => ({ props: (route: Route): Record<string, unknown> => ({
...route.params, ...route.params,
@ -117,7 +117,7 @@ export const groupsRoutes: RouteConfig[] = [
}, },
{ {
path: "/p/:slug", path: "/p/:slug",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Posts/Post.vue"), import("@/views/Posts/Post.vue"),
props: true, props: true,
name: GroupsRouteName.POST, name: GroupsRouteName.POST,
@ -125,7 +125,7 @@ export const groupsRoutes: RouteConfig[] = [
}, },
{ {
path: "/@:preferredUsername/p", path: "/@:preferredUsername/p",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Posts/List.vue"), import("@/views/Posts/List.vue"),
props: true, props: true,
name: GroupsRouteName.POSTS, name: GroupsRouteName.POSTS,
@ -140,7 +140,7 @@ export const groupsRoutes: RouteConfig[] = [
}, },
{ {
path: "/@:preferredUsername/join", path: "/@:preferredUsername/join",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/components/Group/JoinGroupWithAccount.vue"), import("@/components/Group/JoinGroupWithAccount.vue"),
props: true, props: true,
name: GroupsRouteName.GROUP_JOIN, name: GroupsRouteName.GROUP_JOIN,
@ -149,7 +149,7 @@ export const groupsRoutes: RouteConfig[] = [
{ {
path: "/@:preferredUsername/timeline", path: "/@:preferredUsername/timeline",
name: GroupsRouteName.TIMELINE, name: GroupsRouteName.TIMELINE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import("@/views/Group/Timeline.vue"), import("@/views/Group/Timeline.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },

View File

@ -2,7 +2,7 @@ import Vue from "vue";
import Router, { Route } from "vue-router"; import Router, { Route } from "vue-router";
import VueScrollTo from "vue-scrollto"; import VueScrollTo from "vue-scrollto";
import { PositionResult } from "vue-router/types/router.d"; import { PositionResult } from "vue-router/types/router.d";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
import Home from "../views/Home.vue"; import Home from "../views/Home.vue";
import { eventRoutes } from "./event"; import { eventRoutes } from "./event";
import { actorRoutes } from "./actor"; import { actorRoutes } from "./actor";
@ -46,7 +46,7 @@ export const routes = [
{ {
path: "/search", path: "/search",
name: RouteName.SEARCH, name: RouteName.SEARCH,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Search" */ "@/views/Search.vue"), import(/* webpackChunkName: "Search" */ "@/views/Search.vue"),
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
@ -60,7 +60,7 @@ export const routes = [
{ {
path: "/about", path: "/about",
name: RouteName.ABOUT, name: RouteName.ABOUT,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "about" */ "@/views/About.vue"), import(/* webpackChunkName: "about" */ "@/views/About.vue"),
meta: { requiredAuth: false }, meta: { requiredAuth: false },
redirect: { name: RouteName.ABOUT_INSTANCE }, redirect: { name: RouteName.ABOUT_INSTANCE },
@ -68,7 +68,7 @@ export const routes = [
{ {
path: "instance", path: "instance",
name: RouteName.ABOUT_INSTANCE, name: RouteName.ABOUT_INSTANCE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "about" */ "@/views/About/AboutInstance.vue" /* webpackChunkName: "about" */ "@/views/About/AboutInstance.vue"
), ),
@ -76,28 +76,28 @@ export const routes = [
{ {
path: "/terms", path: "/terms",
name: RouteName.TERMS, name: RouteName.TERMS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "cookies" */ "@/views/About/Terms.vue"), import(/* webpackChunkName: "cookies" */ "@/views/About/Terms.vue"),
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{ {
path: "/privacy", path: "/privacy",
name: RouteName.PRIVACY, name: RouteName.PRIVACY,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "cookies" */ "@/views/About/Privacy.vue"), import(/* webpackChunkName: "cookies" */ "@/views/About/Privacy.vue"),
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{ {
path: "/rules", path: "/rules",
name: RouteName.RULES, name: RouteName.RULES,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "cookies" */ "@/views/About/Rules.vue"), import(/* webpackChunkName: "cookies" */ "@/views/About/Rules.vue"),
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{ {
path: "/glossary", path: "/glossary",
name: RouteName.GLOSSARY, name: RouteName.GLOSSARY,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "cookies" */ "@/views/About/Glossary.vue" /* webpackChunkName: "cookies" */ "@/views/About/Glossary.vue"
), ),
@ -108,14 +108,14 @@ export const routes = [
{ {
path: "/interact", path: "/interact",
name: RouteName.INTERACT, name: RouteName.INTERACT,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "interact" */ "@/views/Interact.vue"), import(/* webpackChunkName: "interact" */ "@/views/Interact.vue"),
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{ {
path: "/auth/:provider/callback", path: "/auth/:provider/callback",
name: "auth-callback", name: "auth-callback",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "ProviderValidation" */ "@/views/User/ProviderValidation.vue" /* webpackChunkName: "ProviderValidation" */ "@/views/User/ProviderValidation.vue"
), ),
@ -123,7 +123,7 @@ export const routes = [
{ {
path: "/welcome/:step?", path: "/welcome/:step?",
name: RouteName.WELCOME_SCREEN, name: RouteName.WELCOME_SCREEN,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "WelcomeScreen" */ "@/views/User/SettingsOnboard.vue" /* webpackChunkName: "WelcomeScreen" */ "@/views/User/SettingsOnboard.vue"
), ),
@ -139,7 +139,7 @@ export const routes = [
{ {
path: "/404", path: "/404",
name: RouteName.PAGE_NOT_FOUND, name: RouteName.PAGE_NOT_FOUND,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "PageNotFound" */ "../views/PageNotFound.vue" /* webpackChunkName: "PageNotFound" */ "../views/PageNotFound.vue"
), ),

View File

@ -1,5 +1,5 @@
import { Route, RouteConfig } from "vue-router"; import { Route, RouteConfig } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
export enum SettingsRouteName { export enum SettingsRouteName {
SETTINGS = "SETTINGS", SETTINGS = "SETTINGS",
@ -31,7 +31,7 @@ export enum SettingsRouteName {
export const settingsRoutes: RouteConfig[] = [ export const settingsRoutes: RouteConfig[] = [
{ {
path: "/settings", path: "/settings",
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Settings" */ "@/views/Settings.vue"), import(/* webpackChunkName: "Settings" */ "@/views/Settings.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },
@ -47,7 +47,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "account/general", path: "account/general",
name: SettingsRouteName.ACCOUNT_SETTINGS_GENERAL, name: SettingsRouteName.ACCOUNT_SETTINGS_GENERAL,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "AccountSettings" */ "@/views/Settings/AccountSettings.vue" /* webpackChunkName: "AccountSettings" */ "@/views/Settings/AccountSettings.vue"
), ),
@ -57,7 +57,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "preferences", path: "preferences",
name: SettingsRouteName.PREFERENCES, name: SettingsRouteName.PREFERENCES,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Preferences" */ "@/views/Settings/Preferences.vue" /* webpackChunkName: "Preferences" */ "@/views/Settings/Preferences.vue"
), ),
@ -67,7 +67,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "notifications", path: "notifications",
name: SettingsRouteName.NOTIFICATIONS, name: SettingsRouteName.NOTIFICATIONS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Notifications" */ "@/views/Settings/Notifications.vue" /* webpackChunkName: "Notifications" */ "@/views/Settings/Notifications.vue"
), ),
@ -83,7 +83,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/dashboard", path: "admin/dashboard",
name: SettingsRouteName.ADMIN_DASHBOARD, name: SettingsRouteName.ADMIN_DASHBOARD,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Dashboard" */ "@/views/Admin/Dashboard.vue" /* webpackChunkName: "Dashboard" */ "@/views/Admin/Dashboard.vue"
), ),
@ -92,7 +92,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/settings", path: "admin/settings",
name: SettingsRouteName.ADMIN_SETTINGS, name: SettingsRouteName.ADMIN_SETTINGS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "AdminSettings" */ "@/views/Admin/Settings.vue" /* webpackChunkName: "AdminSettings" */ "@/views/Admin/Settings.vue"
), ),
@ -102,7 +102,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/users", path: "admin/users",
name: SettingsRouteName.USERS, name: SettingsRouteName.USERS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Users" */ "@/views/Admin/Users.vue"), import(/* webpackChunkName: "Users" */ "@/views/Admin/Users.vue"),
props: true, props: true,
meta: { requiredAuth: true }, meta: { requiredAuth: true },
@ -110,7 +110,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/users/:id", path: "admin/users/:id",
name: SettingsRouteName.ADMIN_USER_PROFILE, name: SettingsRouteName.ADMIN_USER_PROFILE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "AdminUserProfile" */ "@/views/Admin/AdminUserProfile.vue" /* webpackChunkName: "AdminUserProfile" */ "@/views/Admin/AdminUserProfile.vue"
), ),
@ -120,7 +120,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/profiles", path: "admin/profiles",
name: SettingsRouteName.PROFILES, name: SettingsRouteName.PROFILES,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "AdminProfiles" */ "@/views/Admin/Profiles.vue" /* webpackChunkName: "AdminProfiles" */ "@/views/Admin/Profiles.vue"
), ),
@ -130,7 +130,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/profiles/:id", path: "admin/profiles/:id",
name: SettingsRouteName.ADMIN_PROFILE, name: SettingsRouteName.ADMIN_PROFILE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "AdminProfile" */ "@/views/Admin/AdminProfile.vue" /* webpackChunkName: "AdminProfile" */ "@/views/Admin/AdminProfile.vue"
), ),
@ -140,7 +140,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/groups", path: "admin/groups",
name: SettingsRouteName.ADMIN_GROUPS, name: SettingsRouteName.ADMIN_GROUPS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "GroupProfiles" */ "@/views/Admin/GroupProfiles.vue" /* webpackChunkName: "GroupProfiles" */ "@/views/Admin/GroupProfiles.vue"
), ),
@ -150,7 +150,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "admin/groups/:id", path: "admin/groups/:id",
name: SettingsRouteName.ADMIN_GROUP_PROFILE, name: SettingsRouteName.ADMIN_GROUP_PROFILE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "AdminGroupProfile" */ "@/views/Admin/AdminGroupProfile.vue" /* webpackChunkName: "AdminGroupProfile" */ "@/views/Admin/AdminGroupProfile.vue"
), ),
@ -161,14 +161,14 @@ export const settingsRoutes: RouteConfig[] = [
path: "admin/relays", path: "admin/relays",
name: SettingsRouteName.RELAYS, name: SettingsRouteName.RELAYS,
redirect: { name: SettingsRouteName.RELAY_FOLLOWINGS }, redirect: { name: SettingsRouteName.RELAY_FOLLOWINGS },
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Follows" */ "@/views/Admin/Follows.vue"), import(/* webpackChunkName: "Follows" */ "@/views/Admin/Follows.vue"),
meta: { requiredAuth: true }, meta: { requiredAuth: true },
children: [ children: [
{ {
path: "followings", path: "followings",
name: SettingsRouteName.RELAY_FOLLOWINGS, name: SettingsRouteName.RELAY_FOLLOWINGS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Followings" */ "@/components/Admin/Followings.vue" /* webpackChunkName: "Followings" */ "@/components/Admin/Followings.vue"
), ),
@ -177,7 +177,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "followers", path: "followers",
name: SettingsRouteName.RELAY_FOLLOWERS, name: SettingsRouteName.RELAY_FOLLOWERS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Followers" */ "@/components/Admin/Followers.vue" /* webpackChunkName: "Followers" */ "@/components/Admin/Followers.vue"
), ),
@ -195,7 +195,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "/moderation/reports/:filter?", path: "/moderation/reports/:filter?",
name: SettingsRouteName.REPORTS, name: SettingsRouteName.REPORTS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "ReportList" */ "@/views/Moderation/ReportList.vue" /* webpackChunkName: "ReportList" */ "@/views/Moderation/ReportList.vue"
), ),
@ -205,7 +205,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "/moderation/report/:reportId", path: "/moderation/report/:reportId",
name: SettingsRouteName.REPORT, name: SettingsRouteName.REPORT,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "Report" */ "@/views/Moderation/Report.vue" /* webpackChunkName: "Report" */ "@/views/Moderation/Report.vue"
), ),
@ -215,7 +215,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "/moderation/logs", path: "/moderation/logs",
name: SettingsRouteName.REPORT_LOGS, name: SettingsRouteName.REPORT_LOGS,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "ModerationLogs" */ "@/views/Moderation/Logs.vue" /* webpackChunkName: "ModerationLogs" */ "@/views/Moderation/Logs.vue"
), ),
@ -231,7 +231,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "/identity/create", path: "/identity/create",
name: SettingsRouteName.CREATE_IDENTITY, name: SettingsRouteName.CREATE_IDENTITY,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "EditIdentity" */ "@/views/Account/children/EditIdentity.vue" /* webpackChunkName: "EditIdentity" */ "@/views/Account/children/EditIdentity.vue"
), ),
@ -244,7 +244,7 @@ export const settingsRoutes: RouteConfig[] = [
{ {
path: "/identity/update/:identityName?", path: "/identity/update/:identityName?",
name: SettingsRouteName.UPDATE_IDENTITY, name: SettingsRouteName.UPDATE_IDENTITY,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "EditIdentity" */ "@/views/Account/children/EditIdentity.vue" /* webpackChunkName: "EditIdentity" */ "@/views/Account/children/EditIdentity.vue"
), ),

View File

@ -1,6 +1,6 @@
import { beforeRegisterGuard } from "@/router/guards/register-guard"; import { beforeRegisterGuard } from "@/router/guards/register-guard";
import { Route, RouteConfig } from "vue-router"; import { Route, RouteConfig } from "vue-router";
import { EsModuleComponent } from "vue/types/options"; import { ImportedComponent } from "vue/types/options";
export enum UserRouteName { export enum UserRouteName {
REGISTER = "Register", REGISTER = "Register",
@ -17,7 +17,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/register/user", path: "/register/user",
name: UserRouteName.REGISTER, name: UserRouteName.REGISTER,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "RegisterUser" */ "@/views/User/Register.vue" /* webpackChunkName: "RegisterUser" */ "@/views/User/Register.vue"
), ),
@ -28,7 +28,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/register/profile", path: "/register/profile",
name: UserRouteName.REGISTER_PROFILE, name: UserRouteName.REGISTER_PROFILE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "RegisterProfile" */ "@/views/Account/Register.vue" /* webpackChunkName: "RegisterProfile" */ "@/views/Account/Register.vue"
), ),
@ -42,7 +42,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/resend-instructions", path: "/resend-instructions",
name: UserRouteName.RESEND_CONFIRMATION, name: UserRouteName.RESEND_CONFIRMATION,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "ResendConfirmation" */ "@/views/User/ResendConfirmation.vue" /* webpackChunkName: "ResendConfirmation" */ "@/views/User/ResendConfirmation.vue"
), ),
@ -52,7 +52,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/password-reset/send", path: "/password-reset/send",
name: UserRouteName.SEND_PASSWORD_RESET, name: UserRouteName.SEND_PASSWORD_RESET,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "SendPasswordReset" */ "@/views/User/SendPasswordReset.vue" /* webpackChunkName: "SendPasswordReset" */ "@/views/User/SendPasswordReset.vue"
), ),
@ -62,7 +62,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/password-reset/:token", path: "/password-reset/:token",
name: UserRouteName.PASSWORD_RESET, name: UserRouteName.PASSWORD_RESET,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "PasswordReset" */ "@/views/User/PasswordReset.vue" /* webpackChunkName: "PasswordReset" */ "@/views/User/PasswordReset.vue"
), ),
@ -72,7 +72,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/validate/email/:token", path: "/validate/email/:token",
name: UserRouteName.EMAIL_VALIDATE, name: UserRouteName.EMAIL_VALIDATE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import( import(
/* webpackChunkName: "EmailValidate" */ "@/views/User/EmailValidate.vue" /* webpackChunkName: "EmailValidate" */ "@/views/User/EmailValidate.vue"
), ),
@ -82,7 +82,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/validate/:token", path: "/validate/:token",
name: UserRouteName.VALIDATE, name: UserRouteName.VALIDATE,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Validate" */ "@/views/User/Validate.vue"), import(/* webpackChunkName: "Validate" */ "@/views/User/Validate.vue"),
props: true, props: true,
meta: { requiresAuth: false }, meta: { requiresAuth: false },
@ -90,7 +90,7 @@ export const userRoutes: RouteConfig[] = [
{ {
path: "/login", path: "/login",
name: UserRouteName.LOGIN, name: UserRouteName.LOGIN,
component: (): Promise<EsModuleComponent> => component: (): Promise<ImportedComponent> =>
import(/* webpackChunkName: "Login" */ "@/views/User/Login.vue"), import(/* webpackChunkName: "Login" */ "@/views/User/Login.vue"),
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },

View File

@ -57,7 +57,8 @@ export class Actor implements IActor {
} }
export function usernameWithDomain(actor: IActor, force = false): string { export function usernameWithDomain(actor: IActor, force = false): string {
if (actor.domain) { if (!actor) return "";
if (actor?.domain) {
return `${actor.preferredUsername}@${actor.domain}`; return `${actor.preferredUsername}@${actor.domain}`;
} }
if (force) { if (force) {

View File

@ -7,7 +7,7 @@ import type { IDiscussion } from "../discussions";
import type { IPost } from "../post.model"; import type { IPost } from "../post.model";
import type { IAddress } from "../address.model"; import type { IAddress } from "../address.model";
import { Address } from "../address.model"; import { Address } from "../address.model";
import { ActorType, Openness } from "../enums"; import { ActorType, GroupVisibility, Openness } from "../enums";
import type { IMember } from "./member.model"; import type { IMember } from "./member.model";
import type { ITodoList } from "../todolist"; import type { ITodoList } from "../todolist";
import { IActivity } from "../activity.model"; import { IActivity } from "../activity.model";
@ -20,6 +20,7 @@ export interface IGroup extends IActor {
organizedEvents: Paginate<IEvent>; organizedEvents: Paginate<IEvent>;
physicalAddress: IAddress; physicalAddress: IAddress;
openness: Openness; openness: Openness;
visibility: GroupVisibility;
manuallyApprovesFollowers: boolean; manuallyApprovesFollowers: boolean;
activity: Paginate<IActivity>; activity: Paginate<IActivity>;
} }
@ -43,6 +44,7 @@ export class Group extends Actor implements IGroup {
this.patch(hash); this.patch(hash);
} }
visibility: GroupVisibility = GroupVisibility.PUBLIC;
activity: Paginate<IActivity> = { elements: [], total: 0 }; activity: Paginate<IActivity> = { elements: [], total: 0 };
openness: Openness = Openness.INVITE_ONLY; openness: Openness = Openness.INVITE_ONLY;

View File

@ -1443,8 +1443,9 @@ div.sidebar {
width: 100%; width: 100%;
.media-content { .media-content {
width: calc(100% - 32px - 1rem); width: calc(100% - 32px - 1rem);
max-width: 80vw;
p.has-text-grey { p.has-text-grey-dark {
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }

View File

@ -135,7 +135,7 @@ const EVENTS_PAGE_LIMIT = 10;
const { group } = this; const { group } = this;
return { return {
title: this.$t("{group} events", { title: this.$t("{group} events", {
group: group.name || usernameWithDomain(group), group: group?.name || usernameWithDomain(group),
}) as string, }) as string,
}; };
}, },

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,12 @@
<ul> <ul>
<li> <li>
<router-link <router-link
v-if="group"
:to="{ :to="{
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
>{{ group.name }}</router-link >{{ group.name || usernameWithDomain(group) }}</router-link
> >
</li> </li>
<li> <li>
@ -37,10 +38,10 @@
> >
<form @submit.prevent="updateGroup"> <form @submit.prevent="updateGroup">
<b-field :label="$t('Group name')"> <b-field :label="$t('Group name')">
<b-input v-model="group.name" /> <b-input v-model="editableGroup.name" />
</b-field> </b-field>
<b-field :label="$t('Group short description')"> <b-field :label="$t('Group short description')">
<editor mode="basic" v-model="group.summary" :maxSize="500" <editor mode="basic" v-model="editableGroup.summary" :maxSize="500"
/></b-field> /></b-field>
<b-field :label="$t('Avatar')"> <b-field :label="$t('Avatar')">
<picture-upload <picture-upload
@ -62,7 +63,7 @@
<p class="label">{{ $t("Group visibility") }}</p> <p class="label">{{ $t("Group visibility") }}</p>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="group.visibility" v-model="editableGroup.visibility"
name="groupVisibility" name="groupVisibility"
:native-value="GroupVisibility.PUBLIC" :native-value="GroupVisibility.PUBLIC"
> >
@ -76,9 +77,9 @@
</div> </div>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="group.visibility" v-model="editableGroup.visibility"
name="groupVisibility" name="groupVisibility"
:native-value="GroupVisibility.PRIVATE" :native-value="GroupVisibility.UNLISTED"
>{{ $t("Only accessible through link") }}<br /> >{{ $t("Only accessible through link") }}<br />
<small>{{ <small>{{
$t( $t(
@ -110,7 +111,7 @@
<p class="label">{{ $t("New members") }}</p> <p class="label">{{ $t("New members") }}</p>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="group.openness" v-model="editableGroup.openness"
name="groupOpenness" name="groupOpenness"
:native-value="Openness.OPEN" :native-value="Openness.OPEN"
> >
@ -124,7 +125,7 @@
</div> </div>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="group.openness" v-model="editableGroup.openness"
name="groupOpenness" name="groupOpenness"
:native-value="Openness.INVITE_ONLY" :native-value="Openness.INVITE_ONLY"
>{{ $t("Manually invite new members") }}<br /> >{{ $t("Manually invite new members") }}<br />
@ -140,14 +141,14 @@
:label="$t('Followers')" :label="$t('Followers')"
:message="$t('Followers will receive new public events and posts.')" :message="$t('Followers will receive new public events and posts.')"
> >
<b-checkbox v-model="group.manuallyApprovesFollowers"> <b-checkbox v-model="editableGroup.manuallyApprovesFollowers">
{{ $t("Manually approve new followers") }} {{ $t("Manually approve new followers") }}
</b-checkbox> </b-checkbox>
</b-field> </b-field>
<full-address-auto-complete <full-address-auto-complete
:label="$t('Group address')" :label="$t('Group address')"
v-model="group.physicalAddress" v-model="editableGroup.physicalAddress"
:value="currentAddress" :value="currentAddress"
/> />
@ -171,14 +172,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from "vue-property-decorator"; import { Component, Watch } from "vue-property-decorator";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue"; import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
import { Route } from "vue-router"; import { Route } from "vue-router";
import PictureUpload from "@/components/PictureUpload.vue"; import PictureUpload from "@/components/PictureUpload.vue";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import GroupMixin from "@/mixins/group"; import GroupMixin from "@/mixins/group";
import { GroupVisibility, Openness } from "@/types/enums"; import { GroupVisibility, Openness } from "@/types/enums";
import RouteName from "../../router/name";
import { UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group"; import { UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
import { IGroup, usernameWithDomain } from "../../types/actor"; import { IGroup, usernameWithDomain } from "../../types/actor";
import { Address, IAddress } from "../../types/address.model"; import { Address, IAddress } from "../../types/address.model";
@ -186,6 +186,8 @@ import { CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model"; import { IConfig } from "@/types/config.model";
import { ServerParseError } from "@apollo/client/link/http"; import { ServerParseError } from "@apollo/client/link/http";
import { ErrorResponse } from "@apollo/client/link/error"; import { ErrorResponse } from "@apollo/client/link/error";
import RouteName from "@/router/name";
import { buildFileFromIMedia } from "@/utils/image";
@Component({ @Component({
components: { components: {
@ -225,9 +227,12 @@ export default class GroupSettings extends mixins(GroupMixin) {
showCopiedTooltip = false; showCopiedTooltip = false;
editableGroup!: IGroup;
async updateGroup(): Promise<void> { async updateGroup(): Promise<void> {
try { try {
const variables = this.buildVariables(); const variables = this.buildVariables();
console.log(variables);
await this.$apollo.mutate<{ updateGroup: IGroup }>({ await this.$apollo.mutate<{ updateGroup: IGroup }>({
mutation: UPDATE_GROUP, mutation: UPDATE_GROUP,
variables, variables,
@ -270,18 +275,38 @@ export default class GroupSettings extends mixins(GroupMixin) {
}, 2000); }, 2000);
} }
@Watch("group")
async watchUpdateGroup(oldGroup: IGroup, newGroup: IGroup): Promise<void> {
if (
oldGroup?.avatar !== undefined &&
oldGroup?.avatar !== newGroup?.avatar
) {
this.avatarFile = await buildFileFromIMedia(this.group.avatar);
}
if (
oldGroup?.banner !== undefined &&
oldGroup?.banner !== newGroup?.banner
) {
this.bannerFile = await buildFileFromIMedia(this.group.banner);
}
this.editableGroup = { ...this.group };
}
private buildVariables() { private buildVariables() {
let avatarObj = {}; let avatarObj = {};
let bannerObj = {}; let bannerObj = {};
const variables = { ...this.group }; const variables = { ...this.editableGroup };
const physicalAddress = {
...variables.physicalAddress,
};
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
delete variables.__typename; delete variables.__typename;
if (variables.physicalAddress) { if (physicalAddress) {
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
delete variables.physicalAddress.__typename; delete physicalAddress.__typename;
} }
delete variables.avatar; delete variables.avatar;
delete variables.banner; delete variables.banner;
@ -291,7 +316,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
avatar: { avatar: {
media: { media: {
name: this.avatarFile.name, name: this.avatarFile.name,
alt: `${this.group.preferredUsername}'s avatar`, alt: `${this.editableGroup.preferredUsername}'s avatar`,
file: this.avatarFile, file: this.avatarFile,
}, },
}, },
@ -303,14 +328,20 @@ export default class GroupSettings extends mixins(GroupMixin) {
banner: { banner: {
media: { media: {
name: this.bannerFile.name, name: this.bannerFile.name,
alt: `${this.group.preferredUsername}'s banner`, alt: `${this.editableGroup.preferredUsername}'s banner`,
file: this.bannerFile, file: this.bannerFile,
}, },
}, },
}; };
} }
return { return {
...variables, id: this.group.id,
name: this.editableGroup.name,
summary: this.editableGroup.summary,
visibility: this.editableGroup.visibility,
openness: this.editableGroup.openness,
manuallyApprovesFollowers: this.editableGroup.manuallyApprovesFollowers,
physicalAddress,
...avatarObj, ...avatarObj,
...bannerObj, ...bannerObj,
}; };
@ -322,7 +353,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
} }
get currentAddress(): IAddress { get currentAddress(): IAddress {
return new Address(this.group.physicalAddress); return new Address(this.editableGroup.physicalAddress);
} }
get avatarMaxSize(): number | undefined { get avatarMaxSize(): number | undefined {

View File

@ -49,7 +49,7 @@
<picture-upload <picture-upload
v-model="pictureFile" v-model="pictureFile"
:textFallback="$t('Headline picture')" :textFallback="$t('Headline picture')"
:defaultImage="post.picture" :defaultImage="editablePost.picture"
/> />
<b-field <b-field
@ -61,21 +61,21 @@
size="is-large" size="is-large"
aria-required="true" aria-required="true"
required required
v-model="post.title" v-model="editablePost.title"
/> />
</b-field> </b-field>
<tag-input v-model="post.tags" :data="tags" path="title" /> <tag-input v-model="editablePost.tags" :data="tags" path="title" />
<div class="field"> <div class="field">
<label class="label">{{ $t("Post") }}</label> <label class="label">{{ $t("Post") }}</label>
<p v-if="errors.body" class="help is-danger">{{ errors.body }}</p> <p v-if="errors.body" class="help is-danger">{{ errors.body }}</p>
<editor v-model="post.body" /> <editor v-model="editablePost.body" />
</div> </div>
<subtitle>{{ $t("Who can view this post") }}</subtitle> <subtitle>{{ $t("Who can view this post") }}</subtitle>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="post.visibility" v-model="editablePost.visibility"
name="postVisibility" name="postVisibility"
:native-value="PostVisibility.PUBLIC" :native-value="PostVisibility.PUBLIC"
>{{ $t("Visible everywhere on the web") }}</b-radio >{{ $t("Visible everywhere on the web") }}</b-radio
@ -83,7 +83,7 @@
</div> </div>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="post.visibility" v-model="editablePost.visibility"
name="postVisibility" name="postVisibility"
:native-value="PostVisibility.UNLISTED" :native-value="PostVisibility.UNLISTED"
>{{ $t("Only accessible through link") }}</b-radio >{{ $t("Only accessible through link") }}</b-radio
@ -91,7 +91,7 @@
</div> </div>
<div class="field"> <div class="field">
<b-radio <b-radio
v-model="post.visibility" v-model="editablePost.visibility"
name="postVisibility" name="postVisibility"
:native-value="PostVisibility.PRIVATE" :native-value="PostVisibility.PRIVATE"
>{{ $t("Only accessible to members of the group") }}</b-radio >{{ $t("Only accessible to members of the group") }}</b-radio
@ -166,7 +166,7 @@ import {
import { IPost } from "../../types/post.model"; import { IPost } from "../../types/post.model";
import Editor from "../../components/Editor.vue"; import Editor from "../../components/Editor.vue";
import { IActor, IGroup, usernameWithDomain } from "../../types/actor"; import { IActor, usernameWithDomain } from "../../types/actor";
import TagInput from "../../components/Event/TagInput.vue"; import TagInput from "../../components/Event/TagInput.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import Subtitle from "../../components/Utils/Subtitle.vue"; import Subtitle from "../../components/Utils/Subtitle.vue";
@ -249,8 +249,6 @@ export default class EditPost extends mixins(GroupMixin) {
tags: [], tags: [],
}; };
group!: IGroup;
PostVisibility = PostVisibility; PostVisibility = PostVisibility;
pictureFile: File | null = null; pictureFile: File | null = null;
@ -259,6 +257,8 @@ export default class EditPost extends mixins(GroupMixin) {
RouteName = RouteName; RouteName = RouteName;
editablePost!: IPost;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
async mounted(): Promise<void> { async mounted(): Promise<void> {
@ -270,6 +270,7 @@ export default class EditPost extends mixins(GroupMixin) {
if (oldPost.picture !== newPost.picture) { if (oldPost.picture !== newPost.picture) {
this.pictureFile = await buildFileFromIMedia(this.post.picture); this.pictureFile = await buildFileFromIMedia(this.post.picture);
} }
this.editablePost = { ...this.post };
} }
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
@ -280,11 +281,11 @@ export default class EditPost extends mixins(GroupMixin) {
const { data } = await this.$apollo.mutate({ const { data } = await this.$apollo.mutate({
mutation: UPDATE_POST, mutation: UPDATE_POST,
variables: { variables: {
id: this.post.id, id: this.editablePost.id,
title: this.post.title, title: this.editablePost.title,
body: this.post.body, body: this.editablePost.body,
tags: (this.post.tags || []).map(({ title }) => title), tags: (this.editablePost.tags || []).map(({ title }) => title),
visibility: this.post.visibility, visibility: this.editablePost.visibility,
draft, draft,
...(await this.buildPicture()), ...(await this.buildPicture()),
}, },
@ -300,9 +301,9 @@ export default class EditPost extends mixins(GroupMixin) {
const { data } = await this.$apollo.mutate({ const { data } = await this.$apollo.mutate({
mutation: CREATE_POST, mutation: CREATE_POST,
variables: { variables: {
...this.post, ...this.editablePost,
...(await this.buildPicture()), ...(await this.buildPicture()),
tags: (this.post.tags || []).map(({ title }) => title), tags: (this.editablePost.tags || []).map(({ title }) => title),
attributedToId: this.actualGroup.id, attributedToId: this.actualGroup.id,
draft, draft,
}, },
@ -362,16 +363,16 @@ export default class EditPost extends mixins(GroupMixin) {
obj = { ...obj, ...pictureObj }; obj = { ...obj, ...pictureObj };
} }
try { try {
if (this.post.picture && this.pictureFile) { if (this.editablePost.picture && this.pictureFile) {
const oldPictureFile = (await buildFileFromIMedia( const oldPictureFile = (await buildFileFromIMedia(
this.post.picture this.editablePost.picture
)) as File; )) as File;
const oldPictureFileContent = await readFileAsync(oldPictureFile); const oldPictureFileContent = await readFileAsync(oldPictureFile);
const newPictureFileContent = await readFileAsync( const newPictureFileContent = await readFileAsync(
this.pictureFile as File this.pictureFile as File
); );
if (oldPictureFileContent === newPictureFileContent) { if (oldPictureFileContent === newPictureFileContent) {
obj.picture = { mediaId: this.post.picture.id }; obj.picture = { mediaId: this.editablePost.picture.id };
} }
} }
} catch (e) { } catch (e) {
@ -381,7 +382,7 @@ export default class EditPost extends mixins(GroupMixin) {
} }
get actualGroup(): IActor { get actualGroup(): IActor {
if (!this.group.id) { if (!this.group?.id) {
return this.post.attributedTo as IActor; return this.post.attributedTo as IActor;
} }
return this.group; return this.group;

View File

@ -86,7 +86,7 @@ import { IMember } from "@/types/actor/member.model";
import { FETCH_GROUP_POSTS } from "../../graphql/post"; import { FETCH_GROUP_POSTS } from "../../graphql/post";
import { Paginate } from "../../types/paginate"; import { Paginate } from "../../types/paginate";
import { IPost } from "../../types/post.model"; import { IPost } from "../../types/post.model";
import { IGroup, IPerson, usernameWithDomain } from "../../types/actor"; import { usernameWithDomain } from "../../types/actor";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import PostElementItem from "../../components/Post/PostElementItem.vue"; import PostElementItem from "../../components/Post/PostElementItem.vue";
@ -138,14 +138,10 @@ const POSTS_PAGE_LIMIT = 10;
export default class PostList extends mixins(GroupMixin) { export default class PostList extends mixins(GroupMixin) {
@Prop({ required: true, type: String }) preferredUsername!: string; @Prop({ required: true, type: String }) preferredUsername!: string;
group!: IGroup;
posts!: Paginate<IPost>; posts!: Paginate<IPost>;
memberships!: IMember[]; memberships!: IMember[];
currentActor!: IPerson;
postsPage = 1; postsPage = 1;
RouteName = RouteName; RouteName = RouteName;

View File

@ -1,61 +1,78 @@
<template> <template>
<div> <article class="container" v-if="post">
<article class="container" v-if="post"> <header>
<section class="heading-section"> <div class="banner-container">
<h1 class="title">{{ post.title }}</h1> <lazy-image
<i18n tag="span" path="By {author}" class="authors"> v-if="post.picture"
<router-link :src="post.picture.url"
slot="author" :width="post.picture.metadata.width"
:to="{ :height="post.picture.metadata.height"
name: RouteName.GROUP, :blurhash="post.picture.metadata.blurhash"
params: { />
preferredUsername: usernameWithDomain(post.attributedTo), </div>
}, <div class="heading-section">
}" <div class="heading-wrapper">
>{{ post.attributedTo.name }}</router-link <div class="title-metadata">
> <h1 class="title">{{ post.title }}</h1>
</i18n> <p class="metadata">
<p class="published has-text-grey-dark" v-if="!post.draft"> <router-link
{{ post.publishAt | formatDateTimeString }} slot="author"
</p> :to="{
<small name: RouteName.GROUP,
v-if="post.visibility === PostVisibility.PRIVATE" params: {
class="has-text-grey-dark" preferredUsername: usernameWithDomain(post.attributedTo),
> },
<b-icon icon="lock" size="is-small" /> }"
{{ >
$t("Accessible only to members", { group: post.attributedTo.name }) <actor-inline :actor="post.attributedTo" />
}} </router-link>
</small> <span class="published has-text-grey-dark" v-if="!post.draft">
<p class="buttons" v-if="isCurrentActorMember"> <b-icon icon="clock" size="is-small" />
<b-tag type="is-warning" size="is-medium" v-if="post.draft">{{ {{ post.publishAt | formatDateTimeString }}
$t("Draft") </span>
}}</b-tag> <span
<router-link v-if="post.visibility === PostVisibility.PRIVATE"
v-if=" class="has-text-grey-dark"
currentActor.id === post.author.id || >
isCurrentActorAGroupModerator <b-icon icon="lock" size="is-small" />
" {{
:to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }" $t("Accessible only to members", {
tag="button" group: post.attributedTo.name,
class="button is-text" })
>{{ $t("Edit") }}</router-link }}
> </span>
</p> </p>
<img v-if="post.picture" :src="post.picture.url" alt="" /> </div>
</section> <p class="buttons" v-if="isCurrentActorMember">
<section v-html="post.body" class="content" /> <b-tag type="is-warning" size="is-medium" v-if="post.draft">{{
<section class="tags"> $t("Draft")
<router-link }}</b-tag>
v-for="tag in post.tags" <router-link
:key="tag.title" v-if="
:to="{ name: RouteName.TAG, params: { tag: tag.title } }" currentActor.id === post.author.id ||
> isCurrentActorAGroupModerator
<tag>{{ tag.title }}</tag> "
</router-link> :to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }"
</section> tag="button"
</article> class="button is-text"
</div> >{{ $t("Edit") }}</router-link
>
</p>
</div>
</div>
</header>
<section v-html="post.body" class="content" />
<section class="tags">
<router-link
v-for="tag in post.tags"
:key="tag.title"
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
>
<tag>{{ tag.title }}</tag>
</router-link>
</section>
</article>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -66,11 +83,12 @@ import { PostVisibility } from "@/types/enums";
import { IMember } from "@/types/actor/member.model"; import { IMember } from "@/types/actor/member.model";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor"; import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { FETCH_POST } from "../../graphql/post"; import { FETCH_POST } from "../../graphql/post";
import { IPost } from "../../types/post.model"; import { IPost } from "../../types/post.model";
import { usernameWithDomain } from "../../types/actor"; import { usernameWithDomain } from "../../types/actor";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import Tag from "../../components/Tag.vue"; import Tag from "../../components/Tag.vue";
import LazyImage from "../../components/Image/LazyImage.vue";
import ActorInline from "../../components/Account/ActorInline.vue";
@Component({ @Component({
apollo: { apollo: {
@ -106,6 +124,8 @@ import Tag from "../../components/Tag.vue";
}, },
components: { components: {
Tag, Tag,
LazyImage,
ActorInline,
}, },
metaInfo() { metaInfo() {
return { return {
@ -148,78 +168,93 @@ export default class Post extends mixins(GroupMixin) {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
article { article {
section.heading-section { background: $white !important;
text-align: center; header {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: auto -3rem 2rem; .banner-container {
display: flex;
h1.title {
margin: 0 auto;
padding-top: 3rem;
font-size: 3rem;
font-weight: 700;
}
.authors {
margin-top: 2rem;
display: inline-block;
}
.published {
margin-top: 1rem;
}
&::after {
height: 0.2rem;
content: " ";
display: block;
width: 100%;
background-color: $purple-1;
margin-top: 1rem;
}
.buttons {
justify-content: center; justify-content: center;
} height: 30vh;
& > * {
z-index: 10;
} }
& > img { .heading-section {
position: absolute; position: relative;
left: 0; display: flex;
top: 0; flex-direction: column;
width: 100%; margin-bottom: 2rem;
height: 100%;
opacity: 0.3;
object-fit: cover;
object-position: 50% 50%;
z-index: 0;
}
}
section.content { .heading-wrapper {
font-size: 1.1rem; padding: 15px 10px;
} display: flex;
flex-wrap: wrap;
justify-content: center;
section.tags { .title-metadata {
padding-bottom: 5rem; min-width: 300px;
flex: 20;
a { p.metadata {
text-decoration: none; margin-top: 16px;
} display: flex;
span { justify-content: flex-start;
&.tag { flex-wrap: wrap;
margin: 0 2px;
*:not(:first-child) {
padding-left: 5px;
}
}
}
p.buttons {
flex: 1;
}
}
h1.title {
margin: 0;
font-weight: 500;
font-size: 38px;
font-family: "Roboto", "Helvetica", "Arial", serif;
}
.authors {
display: inline-block;
}
&::after {
height: 0.2rem;
content: " ";
display: block;
background-color: $purple-1;
}
.buttons {
justify-content: center;
}
}
}
& > section {
margin: 0 2rem;
&.content {
font-size: 1.1rem;
}
&.tags {
padding-bottom: 5rem;
a {
text-decoration: none;
}
span {
&.tag {
margin: 0 2px;
}
} }
} }
} }
background: $white;
max-width: 700px;
margin: 0 auto; margin: 0 auto;
padding: 0 3rem;
} }
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@ -76,14 +76,19 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
@doc """ @doc """
Find an actor in our local database or call WebFinger to find what's its AP ID is and then fetch it Find an actor in our local database or call WebFinger to find what's its AP ID is and then fetch it
""" """
@spec find_or_make_actor_from_nickname(String.t(), atom() | nil) :: tuple() @spec find_or_make_actor_from_nickname(String.t(), atom() | nil) ::
{:ok, Actor.t()} | {:error, any()}
def find_or_make_actor_from_nickname(nickname, type \\ nil) do def find_or_make_actor_from_nickname(nickname, type \\ nil) do
case Actors.get_actor_by_name(nickname, type) do case Actors.get_actor_by_name_with_preload(nickname, type) do
%Actor{} = actor -> %Actor{url: actor_url} = actor ->
{:ok, actor} if Actors.needs_update?(actor) do
make_actor_from_url(actor_url, true)
else
{:ok, actor}
end
nil -> nil ->
make_actor_from_nickname(nickname) make_actor_from_nickname(nickname, true)
end end
end end
@ -94,10 +99,10 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
Create an actor inside our database from username, using WebFinger to find out its AP ID and then fetch it Create an actor inside our database from username, using WebFinger to find out its AP ID and then fetch it
""" """
@spec make_actor_from_nickname(String.t()) :: {:ok, %Actor{}} | {:error, any()} @spec make_actor_from_nickname(String.t()) :: {:ok, %Actor{}} | {:error, any()}
def make_actor_from_nickname(nickname) do def make_actor_from_nickname(nickname, preload \\ false) do
case WebFinger.finger(nickname) do case WebFinger.finger(nickname) do
{:ok, url} when is_binary(url) -> {:ok, url} when is_binary(url) ->
make_actor_from_url(url) make_actor_from_url(url, preload)
_e -> _e ->
{:error, "No ActivityPub URL found in WebFinger"} {:error, "No ActivityPub URL found in WebFinger"}

View File

@ -4,10 +4,11 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
""" """
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources, Todos, Tombstone} alias Mobilizon.{Actors, Discussions, Events, Posts, Resources, Todos, Tombstone}
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.Actor, as: ActorModel
alias Mobilizon.Actors.Member
alias Mobilizon.Discussions.{Comment, Discussion} alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Relay alias Mobilizon.Federation.ActivityPub.{Actor, Relay}
alias Mobilizon.Posts.Post alias Mobilizon.Posts.Post
alias Mobilizon.Resources.Resource alias Mobilizon.Resources.Resource
alias Mobilizon.Todos.{Todo, TodoList} alias Mobilizon.Todos.{Todo, TodoList}
@ -23,8 +24,8 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
{:commit, Actor.t()} | {:ignore, nil} {:commit, Actor.t()} | {:ignore, nil}
def get_actor_by_name(name) do def get_actor_by_name(name) do
Cachex.fetch(@cache, "actor_" <> name, fn "actor_" <> name -> Cachex.fetch(@cache, "actor_" <> name, fn "actor_" <> name ->
case Actors.get_actor_by_name_with_preload(name) do case Actor.find_or_make_actor_from_nickname(name) do
%Actor{} = actor -> {:ok, %ActorModel{} = actor} ->
{:commit, actor} {:commit, actor}
nil -> nil ->
@ -41,7 +42,7 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
def get_local_actor_by_name(name) do def get_local_actor_by_name(name) do
Cachex.fetch(@cache, "local_actor_" <> name, fn "local_actor_" <> name -> Cachex.fetch(@cache, "local_actor_" <> name, fn "local_actor_" <> name ->
case Actors.get_local_actor_by_name(name) do case Actors.get_local_actor_by_name(name) do
%Actor{} = actor -> %ActorModel{} = actor ->
{:commit, actor} {:commit, actor}
nil -> nil ->