Improve and activate groups

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-09-29 09:53:48 +02:00
parent 1ca46a6863
commit 49a5725da3
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
131 changed files with 16440 additions and 1929 deletions

View file

@ -17,7 +17,7 @@ config :mobilizon, :instance,
description: "Change this to a proper description of your instance", description: "Change this to a proper description of your instance",
hostname: "localhost", hostname: "localhost",
registrations_open: false, registrations_open: false,
registration_email_whitelist: [], registration_email_allowlist: [],
demo: false, demo: false,
repository: Mix.Project.config()[:source_url], repository: Mix.Project.config()[:source_url],
allow_relay: true, allow_relay: true,
@ -29,8 +29,7 @@ config :mobilizon, :instance,
email_from: "noreply@localhost", email_from: "noreply@localhost",
email_reply_to: "noreply@localhost" email_reply_to: "noreply@localhost"
# Groups are to be activated with Mobilizon 1.0.0 config :mobilizon, :groups, enabled: true
config :mobilizon, :groups, enabled: false
config :mobilizon, :events, creation: true config :mobilizon, :events, creation: true

View file

@ -76,7 +76,7 @@ export default class App extends Vue {
currentUser!: ICurrentUser; currentUser!: ICurrentUser;
async created() { async created(): Promise<void> {
if (await this.initializeCurrentUser()) { if (await this.initializeCurrentUser()) {
await initializeCurrentActor(this.$apollo.provider.defaultClient); await initializeCurrentActor(this.$apollo.provider.defaultClient);
} }

View file

@ -1,5 +1,4 @@
import { IntrospectionFragmentMatcher, NormalizedCacheObject } from "apollo-cache-inmemory"; import { IntrospectionFragmentMatcher, NormalizedCacheObject } from "apollo-cache-inmemory";
import { IError, errors, defaultError, refreshSuggestion } from "@/utils/errors";
import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from "@/constants"; import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from "@/constants";
import { REFRESH_TOKEN } from "@/graphql/auth"; import { REFRESH_TOKEN } from "@/graphql/auth";
import { saveTokenData } from "@/utils/auth"; import { saveTokenData } from "@/utils/auth";
@ -24,18 +23,6 @@ export const fragmentMatcher = new IntrospectionFragmentMatcher({
}, },
}); });
export const computeErrorMessage = (message: any) => {
const error: IError = errors.reduce((acc, errorLocal) => {
if (RegExp(errorLocal.match).test(message)) {
return errorLocal;
}
return acc;
}, defaultError);
if (error.value === null) return null;
return error.suggestRefresh === false ? error.value : `${error.value}<br>${refreshSuggestion}`;
};
export async function refreshAccessToken( export async function refreshAccessToken(
apolloClient: ApolloClient<NormalizedCacheObject> apolloClient: ApolloClient<NormalizedCacheObject>
): Promise<boolean> { ): Promise<boolean> {

View file

@ -101,6 +101,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Mixins } from "vue-property-decorator"; import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ACCEPT_RELAY, REJECT_RELAY, RELAY_FOLLOWERS } from "../../graphql/admin"; import { ACCEPT_RELAY, REJECT_RELAY, RELAY_FOLLOWERS } from "../../graphql/admin";
import { Paginate } from "../../types/paginate"; import { Paginate } from "../../types/paginate";
import { IFollower } from "../../types/actor/follower.model"; import { IFollower } from "../../types/actor/follower.model";
@ -125,19 +126,20 @@ export default class Followers extends Mixins(RelayMixin) {
RelayMixin = RelayMixin; RelayMixin = RelayMixin;
async acceptRelays() { async acceptRelays(): Promise<void> {
await this.checkedRows.forEach((row: IFollower) => { await this.checkedRows.forEach((row: IFollower) => {
this.acceptRelay(`${row.actor.preferredUsername}@${row.actor.domain}`); this.acceptRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
}); });
} }
async rejectRelays() { async rejectRelays(): Promise<void> {
await this.checkedRows.forEach((row: IFollower) => { await this.checkedRows.forEach((row: IFollower) => {
this.rejectRelay(`${row.actor.preferredUsername}@${row.actor.domain}`); this.rejectRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
}); });
} }
async acceptRelay(address: string) { async acceptRelay(address: string): Promise<void> {
try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: ACCEPT_RELAY, mutation: ACCEPT_RELAY,
variables: { variables: {
@ -146,9 +148,13 @@ export default class Followers extends Mixins(RelayMixin) {
}); });
await this.$apollo.queries.relayFollowers.refetch(); await this.$apollo.queries.relayFollowers.refetch();
this.checkedRows = []; this.checkedRows = [];
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
async rejectRelay(address: string) { async rejectRelay(address: string): Promise<void> {
try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: REJECT_RELAY, mutation: REJECT_RELAY,
variables: { variables: {
@ -157,6 +163,9 @@ export default class Followers extends Mixins(RelayMixin) {
}); });
await this.$apollo.queries.relayFollowers.refetch(); await this.$apollo.queries.relayFollowers.refetch();
this.checkedRows = []; this.checkedRows = [];
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
get checkedRowsHaveAtLeastOneToApprove(): boolean { get checkedRowsHaveAtLeastOneToApprove(): boolean {

View file

@ -99,6 +99,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Mixins } from "vue-property-decorator"; import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ADD_RELAY, RELAY_FOLLOWINGS, REMOVE_RELAY } from "../../graphql/admin"; import { ADD_RELAY, RELAY_FOLLOWINGS, REMOVE_RELAY } from "../../graphql/admin";
import { IFollower } from "../../types/actor/follower.model"; import { IFollower } from "../../types/actor/follower.model";
import { Paginate } from "../../types/paginate"; import { Paginate } from "../../types/paginate";
@ -125,8 +126,9 @@ export default class Followings extends Mixins(RelayMixin) {
RelayMixin = RelayMixin; RelayMixin = RelayMixin;
async followRelay(e: Event) { async followRelay(e: Event): Promise<void> {
e.preventDefault(); e.preventDefault();
try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: ADD_RELAY, mutation: ADD_RELAY,
variables: { variables: {
@ -136,15 +138,19 @@ export default class Followings extends Mixins(RelayMixin) {
}); });
await this.$apollo.queries.relayFollowings.refetch(); await this.$apollo.queries.relayFollowings.refetch();
this.newRelayAddress = ""; this.newRelayAddress = "";
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
async removeRelays() { async removeRelays(): Promise<void> {
await this.checkedRows.forEach((row: IFollower) => { await this.checkedRows.forEach((row: IFollower) => {
this.removeRelay(`${row.targetActor.preferredUsername}@${row.targetActor.domain}`); this.removeRelay(`${row.targetActor.preferredUsername}@${row.targetActor.domain}`);
}); });
} }
async removeRelay(address: string) { async removeRelay(address: string): Promise<void> {
try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: REMOVE_RELAY, mutation: REMOVE_RELAY,
variables: { variables: {
@ -153,6 +159,9 @@ export default class Followings extends Mixins(RelayMixin) {
}); });
await this.$apollo.queries.relayFollowings.refetch(); await this.$apollo.queries.relayFollowings.refetch();
this.checkedRows = []; this.checkedRows = [];
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
} }
</script> </script>

View file

@ -126,6 +126,7 @@
import { Component, Prop, Vue, Ref } from "vue-property-decorator"; import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import EditorComponent from "@/components/Editor.vue"; import EditorComponent from "@/components/Editor.vue";
import TimeAgo from "javascript-time-ago"; import TimeAgo from "javascript-time-ago";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { CommentModel, IComment } from "../../types/comment.model"; import { CommentModel, IComment } from "../../types/comment.model";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor"; import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { IPerson, usernameWithDomain } from "../../types/actor"; import { IPerson, usernameWithDomain } from "../../types/actor";
@ -171,7 +172,7 @@ export default class Comment extends Vue {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
async mounted() { async mounted(): Promise<void> {
const localeName = this.$i18n.locale; const localeName = this.$i18n.locale;
const locale = await import(`javascript-time-ago/locale/${localeName}`); const locale = await import(`javascript-time-ago/locale/${localeName}`);
TimeAgo.addLocale(locale); TimeAgo.addLocale(locale);
@ -183,7 +184,7 @@ export default class Comment extends Vue {
} }
} }
async createReplyToComment(comment: IComment) { async createReplyToComment(comment: IComment): Promise<void> {
if (this.replyTo) { if (this.replyTo) {
this.replyTo = false; this.replyTo = false;
this.newComment = new CommentModel(); this.newComment = new CommentModel();
@ -196,7 +197,7 @@ export default class Comment extends Vue {
this.commentEditor.replyToComment(comment); this.commentEditor.replyToComment(comment);
} }
replyToComment() { replyToComment(): void {
this.newComment.inReplyToComment = this.comment; this.newComment.inReplyToComment = this.comment;
this.newComment.originComment = this.comment.originComment || this.comment; this.newComment.originComment = this.comment.originComment || this.comment;
this.newComment.actor = this.currentActor; this.newComment.actor = this.currentActor;
@ -205,7 +206,7 @@ export default class Comment extends Vue {
this.replyTo = false; this.replyTo = false;
} }
async fetchReplies() { async fetchReplies(): Promise<void> {
const parentId = this.comment.id; const parentId = this.comment.id;
const { data } = await this.$apollo.query<{ thread: IComment[] }>({ const { data } = await this.$apollo.query<{ thread: IComment[] }>({
query: FETCH_THREAD_REPLIES, query: FETCH_THREAD_REPLIES,
@ -267,7 +268,7 @@ export default class Comment extends Vue {
return this.commentId; return this.commentId;
} }
reportModal() { reportModal(): void {
if (!this.comment.actor) return; if (!this.comment.actor) return;
this.$buefy.modal.open({ this.$buefy.modal.open({
parent: this, parent: this,
@ -281,7 +282,7 @@ export default class Comment extends Vue {
}); });
} }
async reportComment(content: string, forward: boolean) { async reportComment(content: string, forward: boolean): Promise<void> {
try { try {
if (!this.comment.actor) return; if (!this.comment.actor) return;
await this.$apollo.mutate<IReport>({ await this.$apollo.mutate<IReport>({
@ -303,8 +304,8 @@ export default class Comment extends Vue {
position: "is-bottom-right", position: "is-bottom-right",
duration: 5000, duration: 5000,
}); });
} catch (error) { } catch (e) {
console.error(error); Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
} }
} }
} }

View file

@ -51,6 +51,7 @@
import { Prop, Vue, Component, Watch } from "vue-property-decorator"; import { Prop, Vue, Component, Watch } from "vue-property-decorator";
import Comment from "@/components/Comment/Comment.vue"; import Comment from "@/components/Comment/Comment.vue";
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue"; import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { CommentModel, IComment } from "../../types/comment.model"; import { CommentModel, IComment } from "../../types/comment.model";
import { import {
CREATE_COMMENT_FROM_EVENT, CREATE_COMMENT_FROM_EVENT,
@ -100,11 +101,11 @@ export default class CommentTree extends Vue {
CommentModeration = CommentModeration; CommentModeration = CommentModeration;
@Watch("currentActor") @Watch("currentActor")
watchCurrentActor(currentActor: IPerson) { watchCurrentActor(currentActor: IPerson): void {
this.newComment.actor = currentActor; this.newComment.actor = currentActor;
} }
async createCommentForEvent(comment: IComment) { async createCommentForEvent(comment: IComment): Promise<void> {
try { try {
if (!comment.actor) return; if (!comment.actor) return;
await this.$apollo.mutate({ await this.$apollo.mutate({
@ -190,11 +191,12 @@ export default class CommentTree extends Vue {
// and reset the new comment field // and reset the new comment field
this.newComment = new CommentModel(); this.newComment = new CommentModel();
} catch (e) { } catch (e) {
console.error(e); Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
} }
} }
async deleteComment(comment: IComment) { async deleteComment(comment: IComment): Promise<void> {
try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: DELETE_COMMENT, mutation: DELETE_COMMENT,
variables: { variables: {
@ -258,6 +260,9 @@ export default class CommentTree extends Vue {
}, },
}); });
// this.comments = this.comments.filter(commentItem => commentItem.id !== comment.id); // this.comments = this.comments.filter(commentItem => commentItem.id !== comment.id);
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
get orderedComments(): IComment[] { get orderedComments(): IComment[] {

View file

@ -2,7 +2,7 @@
<div v-if="editor"> <div v-if="editor">
<div <div
class="editor" class="editor"
:class="{ mode_description: isDescriptionMode }" :class="{ short_mode: isShortMode, comment_mode: isCommentMode }"
id="tiptab-editor" id="tiptab-editor"
:data-actor-id="currentActor && currentActor.id" :data-actor-id="currentActor && currentActor.id"
> >
@ -211,6 +211,7 @@ import tippy, { Instance, sticky } from "tippy.js";
import { SEARCH_PERSONS } from "../graphql/search"; import { SEARCH_PERSONS } from "../graphql/search";
import { Actor, IActor, IPerson } from "../types/actor"; import { Actor, IActor, IPerson } from "../types/actor";
import Image from "./Editor/Image"; import Image from "./Editor/Image";
import MaxSize from "./Editor/MaxSize";
import { UPLOAD_PICTURE } from "../graphql/upload"; import { UPLOAD_PICTURE } from "../graphql/upload";
import { listenFileUpload } from "../utils/upload"; import { listenFileUpload } from "../utils/upload";
import { CURRENT_ACTOR_CLIENT } from "../graphql/actor"; import { CURRENT_ACTOR_CLIENT } from "../graphql/actor";
@ -229,6 +230,8 @@ export default class EditorComponent extends Vue {
@Prop({ required: false, default: "description" }) mode!: string; @Prop({ required: false, default: "description" }) mode!: string;
@Prop({ required: false, default: 100_000_000 }) maxSize!: number;
currentActor!: IPerson; currentActor!: IPerson;
editor: Editor | null = null; editor: Editor | null = null;
@ -254,6 +257,10 @@ export default class EditorComponent extends Vue {
return this.mode === "comment"; return this.mode === "comment";
} }
get isShortMode(): boolean {
return this.isBasicMode;
}
get hasResults(): boolean { get hasResults(): boolean {
return this.filteredActors.length > 0; return this.filteredActors.length > 0;
} }
@ -386,6 +393,7 @@ export default class EditorComponent extends Vue {
showOnlyWhenEditable: false, showOnlyWhenEditable: false,
}), }),
new Image(), new Image(),
new MaxSize({ maxSize: this.maxSize }),
], ],
onUpdate: ({ getHTML }: { getHTML: Function }) => { onUpdate: ({ getHTML }: { getHTML: Function }) => {
this.$emit("input", getHTML()); this.$emit("input", getHTML());
@ -395,7 +403,7 @@ export default class EditorComponent extends Vue {
} }
@Watch("value") @Watch("value")
onValueChanged(val: string) { onValueChanged(val: string): void {
if (!this.editor) return; if (!this.editor) return;
if (val !== this.editor.getHTML()) { if (val !== this.editor.getHTML()) {
this.editor.setContent(val); this.editor.setContent(val);
@ -420,7 +428,7 @@ export default class EditorComponent extends Vue {
return undefined; return undefined;
} }
upHandler() { upHandler(): void {
this.navigatedActorIndex = this.navigatedActorIndex =
(this.navigatedActorIndex + this.filteredActors.length - 1) % this.filteredActors.length; (this.navigatedActorIndex + this.filteredActors.length - 1) % this.filteredActors.length;
} }
@ -429,11 +437,11 @@ export default class EditorComponent extends Vue {
* navigate to the next item * navigate to the next item
* if it's the last item, navigate to the first one * if it's the last item, navigate to the first one
*/ */
downHandler() { downHandler(): void {
this.navigatedActorIndex = (this.navigatedActorIndex + 1) % this.filteredActors.length; this.navigatedActorIndex = (this.navigatedActorIndex + 1) % this.filteredActors.length;
} }
enterHandler() { enterHandler(): void {
const actor = this.filteredActors[this.navigatedActorIndex]; const actor = this.filteredActors[this.navigatedActorIndex];
if (actor) { if (actor) {
this.selectActor(actor); this.selectActor(actor);
@ -445,7 +453,7 @@ export default class EditorComponent extends Vue {
* so it's important to pass also the position of your suggestion text * so it's important to pass also the position of your suggestion text
* @param actor IActor * @param actor IActor
*/ */
selectActor(actor: IActor) { selectActor(actor: IActor): void {
const actorModel = new Actor(actor); const actorModel = new Actor(actor);
this.insertMention({ this.insertMention({
range: this.suggestionRange, range: this.suggestionRange,
@ -460,7 +468,7 @@ export default class EditorComponent extends Vue {
} }
/** We use this to programatically insert an actor mention when creating a reply to comment */ /** We use this to programatically insert an actor mention when creating a reply to comment */
replyToComment(comment: IComment) { replyToComment(comment: IComment): void {
if (!comment.actor) return; if (!comment.actor) return;
const actorModel = new Actor(comment.actor); const actorModel = new Actor(comment.actor);
if (!this.editor) return; if (!this.editor) return;
@ -476,11 +484,12 @@ export default class EditorComponent extends Vue {
* tiptap provides a virtualNode object for using popper.js (or tippy.js) for popups * tiptap provides a virtualNode object for using popper.js (or tippy.js) for popups
* @param node * @param node
*/ */
renderPopup(node: Element) { renderPopup(node: Element): void {
if (this.popup) { if (this.popup) {
return; return;
} }
this.popup = tippy("#mobilizon", { this.popup = tippy("#mobilizon", {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
getReferenceClientRect: node.getBoundingClientRect, getReferenceClientRect: node.getBoundingClientRect,
appendTo: () => document.body, appendTo: () => document.body,
@ -497,8 +506,9 @@ export default class EditorComponent extends Vue {
}) as Instance[]; }) as Instance[];
} }
destroyPopup() { destroyPopup(): void {
if (this.popup) { if (this.popup) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
this.popup[0].destroy(); this.popup[0].destroy();
this.popup = null; this.popup = null;
@ -512,7 +522,7 @@ export default class EditorComponent extends Vue {
* Show a file prompt, upload picture and insert it into editor * Show a file prompt, upload picture and insert it into editor
* @param command * @param command
*/ */
async showImagePrompt(command: Function) { async showImagePrompt(command: Function): Promise<void> {
const image = await listenFileUpload(); const image = await listenFileUpload();
const { data } = await this.$apollo.mutate({ const { data } = await this.$apollo.mutate({
mutation: UPLOAD_PICTURE, mutation: UPLOAD_PICTURE,
@ -527,7 +537,7 @@ export default class EditorComponent extends Vue {
} }
} }
beforeDestroy() { beforeDestroy(): void {
if (!this.editor) return; if (!this.editor) return;
this.destroyPopup(); this.destroyPopup();
this.editor.destroy(); this.editor.destroy();
@ -577,10 +587,20 @@ $color-white: #eee;
font-style: italic; font-style: italic;
} }
&.mode_description { .editor__content div.ProseMirror {
div.ProseMirror {
min-height: 10rem; min-height: 10rem;
} }
&.short_mode {
div.ProseMirror {
min-height: 5rem;
}
}
&.comment_mode {
div.ProseMirror {
min-height: 2rem;
}
} }
&__content { &__content {

View file

@ -0,0 +1,33 @@
// @ts-nocheck
import { Extension, Plugin } from "tiptap";
export default class MaxSize extends Extension {
get name() {
return "maxSize";
}
get defaultOptions() {
return {
maxSize: null,
};
}
get plugins() {
return [
new Plugin({
appendTransaction: (transactions, oldState, newState) => {
const max = this.options.maxSize;
const oldLength = oldState.doc.content.size;
const newLength = newState.doc.content.size;
if (newLength > max && newLength > oldLength) {
let newTr = newState.tr;
newTr.insertText("", max + 1, newLength);
return newTr;
}
},
}),
];
}
}

View file

@ -0,0 +1,102 @@
<template>
<div class="list is-hoverable">
<b-radio-button
v-model="currentActor"
:native-value="availableActor"
class="list-item"
v-for="availableActor in actualAvailableActors"
:class="{ 'is-active': availableActor.id === currentActor.id }"
:key="availableActor.id"
>
<div class="media">
<figure class="image is-48x48" v-if="availableActor.avatar">
<img class="media-left is-rounded" :src="availableActor.avatar.url" alt="" />
</figure>
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
<div class="media-content">
<h3>{{ availableActor.name }}</h3>
<small>{{ `@${availableActor.preferredUsername}` }}</small>
</div>
</div>
</b-radio-button>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { IMember, IPerson, MemberRole, IActor, Actor } from "@/types/actor";
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { Paginate } from "@/types/paginate";
@Component({
apollo: {
groupMemberships: {
query: PERSON_MEMBERSHIPS,
variables() {
return {
id: this.identity.id,
};
},
update: (data) => data.person.memberships,
skip() {
return !this.identity.id;
},
},
},
})
export default class OrganizerPicker extends Vue {
@Prop() value!: IActor;
@Prop() identity!: IPerson;
@Prop({ required: false, default: false }) restrictModeratorLevel!: boolean;
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
currentActor: IActor = this.value;
Actor = Actor;
get actualMemberships(): IMember[] {
if (this.restrictModeratorLevel) {
return this.groupMemberships.elements.filter((membership: IMember) =>
[MemberRole.ADMINISTRATOR, MemberRole.MODERATOR, MemberRole.CREATOR].includes(
membership.role
)
);
}
return this.groupMemberships.elements;
}
get actualAvailableActors(): IActor[] {
return [this.identity, ...this.actualMemberships.map((member) => member.parent)];
}
@Watch("currentActor")
async fetchMembersForGroup(): Promise<void> {
this.$emit("input", this.currentActor);
}
}
</script>
<style lang="scss" scoped>
/deep/ .list-item {
box-sizing: content-box;
label.b-radio {
padding: 0.85rem 0;
.media {
padding: 0.25rem 0;
align-items: center;
figure.image,
span.icon.media-left {
margin-right: 0.5rem;
}
span.icon.media-left {
margin-left: -0.25rem;
}
}
}
}
</style>

View file

@ -0,0 +1,199 @@
<template>
<div class="organizer-picker">
<!-- If we have a current actor (inline) -->
<div v-if="inline && currentActor.id" class="inline box" @click="isComponentModalActive = true">
<div class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="currentActor.avatar">
<img
class="image is-rounded"
:src="currentActor.avatar.url"
:alt="currentActor.avatar.alt"
/>
</figure>
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="media-content" v-if="currentActor.name">
<p class="is-4">{{ currentActor.name }}</p>
<p class="is-6 has-text-grey">{{ `@${currentActor.preferredUsername}` }}</p>
</div>
<div class="media-content" v-else>
{{ `@${currentActor.preferredUsername}` }}
</div>
<b-button type="is-text" @click="isComponentModalActive = true">
{{ $t("Change") }}
</b-button>
</div>
</div>
<!-- If we have a current actor -->
<span v-else-if="currentActor.id" class="block" @click="isComponentModalActive = true">
<img
class="image is-48x48"
v-if="currentActor.avatar"
:src="currentActor.avatar.url"
:alt="currentActor.avatar.alt"
/>
<b-icon v-else size="is-large" icon="account-circle" />
</span>
<!-- If we have no current actor -->
<div v-if="groupMemberships.total === 0 || !currentActor.id" class="box">
<div class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="identity.avatar">
<img class="image is-rounded" :src="identity.avatar.url" :alt="identity.avatar.alt" />
</figure>
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="media-content" v-if="identity.name">
<p class="is-4">{{ identity.name }}</p>
<p class="is-6 has-text-grey">{{ `@${identity.preferredUsername}` }}</p>
</div>
<div class="media-content" v-else>
{{ `@${identity.preferredUsername}` }}
</div>
<b-button type="is-text" @click="isComponentModalActive = true">
{{ $t("Change") }}
</b-button>
</div>
</div>
<b-modal :active.sync="isComponentModalActive" has-modal-card>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ $t("Pick a profile or a group") }}</p>
</header>
<section class="modal-card-body">
<div class="columns">
<div class="column">
<organizer-picker
v-model="currentActor"
:identity.sync="identity"
@input="relay"
:restrict-moderator-level="true"
/>
</div>
<div class="column">
<div v-if="actorMembersForCurrentActor.length > 0">
<p>{{ $t("Add a contact") }}</p>
<p class="field" v-for="actor in actorMembersForCurrentActor" :key="actor.id">
<b-checkbox v-model="actualContacts" :native-value="actor.id">
<div class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="actor.avatar">
<img
class="image is-rounded"
:src="actor.avatar.url"
:alt="actor.avatar.alt"
/>
</figure>
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="media-content" v-if="actor.name">
<p class="is-4">{{ actor.name }}</p>
<p class="is-6 has-text-grey">{{ `@${actor.preferredUsername}` }}</p>
</div>
<div class="media-content" v-else>
{{ `@${actor.preferredUsername}` }}
</div>
</div>
</b-checkbox>
</p>
</div>
<div v-else class="content has-text-grey has-text-centered">
<p>{{ $t("Your profile will be shown as contact.") }}</p>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-primary" type="button" @click="pickActor">
{{ $t("Pick") }}
</button>
</footer>
</div>
</b-modal>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { IActor, IGroup, IMember, IPerson } from "../../types/actor";
import OrganizerPicker from "./OrganizerPicker.vue";
import { PERSON_MEMBERSHIPS_WITH_MEMBERS } from "../../graphql/actor";
import { Paginate } from "../../types/paginate";
@Component({
components: { OrganizerPicker },
apollo: {
groupMemberships: {
query: PERSON_MEMBERSHIPS_WITH_MEMBERS,
variables() {
return {
id: this.identity.id,
};
},
update: (data) => data.person.memberships,
skip() {
return !this.identity.id;
},
},
},
})
export default class OrganizerPickerWrapper extends Vue {
@Prop({ type: Object, required: true }) value!: IActor;
@Prop({ default: true, type: Boolean }) inline!: boolean;
@Prop({ type: Object, required: true }) identity!: IPerson;
isComponentModalActive = false;
currentActor: IActor = this.value;
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
@Prop({ type: Array, required: false, default: () => [] }) contacts!: IActor[];
actualContacts: (string | undefined)[] = this.contacts.map(({ id }) => id);
@Watch("contacts")
updateActualContacts(contacts: IActor[]): void {
this.actualContacts = contacts.map(({ id }) => id);
}
@Watch("value")
updateCurrentActor(value: IGroup): void {
this.currentActor = value;
}
async relay(group: IGroup): Promise<void> {
this.currentActor = group;
}
pickActor(): void {
this.$emit(
"update:contacts",
this.actorMembersForCurrentActor.filter(({ id }) => this.actualContacts.includes(id))
);
this.$emit("input", this.currentActor);
this.isComponentModalActive = false;
}
get actorMembersForCurrentActor(): IActor[] {
const currentMembership = this.groupMemberships.elements.find(
({ parent: { id } }) => id === this.currentActor.id
);
if (currentMembership) {
return currentMembership.parent.members.elements.map(({ actor }) => actor);
}
return [];
}
}
</script>
<style lang="scss" scoped>
.group-picker {
.block,
.no-group,
.inline {
cursor: pointer;
}
}
</style>

View file

@ -10,7 +10,7 @@
<router-link :to="{ name: RouteName.TERMS }">{{ $t("Terms") }}</router-link> <router-link :to="{ name: RouteName.TERMS }">{{ $t("Terms") }}</router-link>
</li> </li>
<li> <li>
<a href="https://framagit.org/framasoft/mobilizon/blob/master/LICENSE"> <a hreflang="en" href="https://framagit.org/framasoft/mobilizon/blob/master/LICENSE">
{{ $t("License") }} {{ $t("License") }}
</a> </a>
</li> </li>

View file

@ -1,11 +1,13 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<div>
<div class="media"> <div class="media">
<div class="media-left"> <div class="media-left">
<figure class="image is-48x48"> <figure class="image is-48x48" v-if="member.parent.avatar">
<img src="https://bulma.io/images/placeholders/96x96.png" alt="Placeholder image" /> <img class="is-rounded" :src="member.parent.avatar.url" alt="" />
</figure> </figure>
<b-icon v-else size="is-large" icon="account-group" />
</div> </div>
<div class="media-content"> <div class="media-content">
<router-link <router-link
@ -20,21 +22,39 @@
`@${member.parent.preferredUsername}@${member.parent.domain}` `@${member.parent.preferredUsername}@${member.parent.domain}`
}}</span> }}</span>
<span v-else>{{ `@${member.parent.preferredUsername}` }}</span> <span v-else>{{ `@${member.parent.preferredUsername}` }}</span>
<b-taglist>
<b-tag type="is-info" v-if="member.role === MemberRole.ADMINISTRATOR">{{
$t("Administrator")
}}</b-tag>
<b-tag type="is-info" v-else-if="member.role === MemberRole.MODERATOR">{{
$t("Moderator")
}}</b-tag>
</b-taglist>
</p> </p>
<b-tag type="is-info">{{ member.role }}</b-tag>
</router-link> </router-link>
</div> </div>
</div> </div>
<div class="content"> <div class="content" v-if="member.parent.summary">
<p>{{ member.parent.summary }}</p> <p>{{ member.parent.summary }}</p>
</div> </div>
</div> </div>
<div>
<b-dropdown aria-role="list" position="is-bottom-left">
<b-icon icon="dots-horizontal" slot="trigger" />
<b-dropdown-item aria-role="listitem" @click="$emit('leave')">
<b-icon icon="exit-to-app" />
{{ $t("Leave") }}
</b-dropdown-item>
</b-dropdown>
</div>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { IMember, usernameWithDomain } from "@/types/actor"; import { IMember, MemberRole, usernameWithDomain } from "@/types/actor";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
@Component @Component
@ -44,5 +64,21 @@ export default class GroupMemberCard extends Vue {
RouteName = RouteName; RouteName = RouteName;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
MemberRole = MemberRole;
} }
</script> </script>
<style lang="scss" scoped>
.card-content {
display: flex;
align-items: center;
& > div:first-child {
flex: 1;
}
& > div:last-child {
cursor: pointer;
}
}
</style>

View file

@ -7,7 +7,7 @@
<div class="list is-hoverable"> <div class="list is-hoverable">
<a <a
class="list-item" class="list-item"
v-for="groupMembership in groupMemberships.elements" v-for="groupMembership in actualMemberships"
:class="{ 'is-active': groupMembership.parent.id === currentGroup.id }" :class="{ 'is-active': groupMembership.parent.id === currentGroup.id }"
@click="changeCurrentGroup(groupMembership.parent)" @click="changeCurrentGroup(groupMembership.parent)"
:key="groupMembership.id" :key="groupMembership.id"
@ -36,7 +36,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { IGroup, IMember, IPerson, Group } from "@/types/actor"; import { IGroup, IMember, IPerson, Group, MemberRole } from "@/types/actor";
import { PERSON_MEMBERSHIPS } from "@/graphql/actor"; import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { Paginate } from "@/types/paginate"; import { Paginate } from "@/types/paginate";
@ -61,15 +61,28 @@ export default class GroupPicker extends Vue {
@Prop() identity!: IPerson; @Prop() identity!: IPerson;
@Prop({ required: false, default: false }) restrictModeratorLevel!: boolean;
groupMemberships: Paginate<IMember> = { elements: [], total: 0 }; groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
currentGroup: IGroup = this.value; currentGroup: IGroup = this.value;
Group = Group; Group = Group;
changeCurrentGroup(group: IGroup) { changeCurrentGroup(group: IGroup): void {
this.currentGroup = group; this.currentGroup = group;
this.$emit("input", group); this.$emit("input", group);
} }
get actualMemberships(): IMember[] {
if (this.restrictModeratorLevel) {
return this.groupMemberships.elements.filter((membership: IMember) =>
[MemberRole.ADMINISTRATOR, MemberRole.MODERATOR, MemberRole.CREATOR].includes(
membership.role
)
);
}
return this.groupMemberships.elements;
}
} }
</script> </script>

View file

@ -14,7 +14,11 @@
<div class="media"> <div class="media">
<div class="media-left"> <div class="media-left">
<figure class="image is-48x48" v-if="currentGroup.avatar"> <figure class="image is-48x48" v-if="currentGroup.avatar">
<img class="image" :src="currentGroup.avatar.url" :alt="currentGroup.avatar.alt" /> <img
class="image is-rounded"
:src="currentGroup.avatar.url"
:alt="currentGroup.avatar.alt"
/>
</figure> </figure>
<b-icon v-else size="is-large" icon="account-circle" /> <b-icon v-else size="is-large" icon="account-circle" />
</div> </div>
@ -46,7 +50,12 @@
</p> </p>
</div> </div>
<b-modal :active.sync="isComponentModalActive" has-modal-card> <b-modal :active.sync="isComponentModalActive" has-modal-card>
<group-picker v-model="currentGroup" :identity.sync="identity" @input="relay" /> <group-picker
v-model="currentGroup"
:identity.sync="identity"
@input="relay"
:restrict-moderator-level="true"
/>
</b-modal> </b-modal>
</div> </div>
</template> </template>
@ -88,11 +97,11 @@ export default class GroupPickerWrapper extends Vue {
groupMemberships: Paginate<IMember> = { elements: [], total: 0 }; groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
@Watch("value") @Watch("value")
updateCurrentGroup(value: IGroup) { updateCurrentGroup(value: IGroup): void {
this.currentGroup = value; this.currentGroup = value;
} }
relay(group: IGroup) { relay(group: IGroup): void {
this.currentGroup = group; this.currentGroup = group;
this.$emit("input", group); this.$emit("input", group);
this.isComponentModalActive = false; this.isComponentModalActive = false;

View file

@ -14,6 +14,7 @@ import { ACCEPT_INVITATION, REJECT_INVITATION } from "@/graphql/member";
import { IMember } from "@/types/actor"; import { IMember } from "@/types/actor";
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import InvitationCard from "@/components/Group/InvitationCard.vue"; import InvitationCard from "@/components/Group/InvitationCard.vue";
import { SnackbarProgrammatic as Snackbar } from "buefy";
@Component({ @Component({
components: { components: {
@ -23,7 +24,8 @@ import InvitationCard from "@/components/Group/InvitationCard.vue";
export default class Invitations extends Vue { export default class Invitations extends Vue {
@Prop({ required: true, type: Array }) invitations!: IMember; @Prop({ required: true, type: Array }) invitations!: IMember;
async acceptInvitation(id: string) { async acceptInvitation(id: string): Promise<void> {
try {
const { data } = await this.$apollo.mutate<{ acceptInvitation: IMember }>({ const { data } = await this.$apollo.mutate<{ acceptInvitation: IMember }>({
mutation: ACCEPT_INVITATION, mutation: ACCEPT_INVITATION,
variables: { variables: {
@ -33,9 +35,13 @@ export default class Invitations extends Vue {
if (data) { if (data) {
this.$emit("acceptInvitation", data.acceptInvitation); this.$emit("acceptInvitation", data.acceptInvitation);
} }
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
async rejectInvitation(id: string) { async rejectInvitation(id: string): Promise<void> {
try {
const { data } = await this.$apollo.mutate<{ rejectInvitation: IMember }>({ const { data } = await this.$apollo.mutate<{ rejectInvitation: IMember }>({
mutation: REJECT_INVITATION, mutation: REJECT_INVITATION,
variables: { variables: {
@ -45,6 +51,9 @@ export default class Invitations extends Vue {
if (data) { if (data) {
this.$emit("rejectInvitation", data.rejectInvitation); this.$emit("rejectInvitation", data.rejectInvitation);
} }
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
} }
</script> </script>

View file

@ -18,6 +18,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { IParticipant } from "../../types/event.model"; import { IParticipant } from "../../types/event.model";
import { CONFIRM_PARTICIPATION } from "../../graphql/event"; import { CONFIRM_PARTICIPATION } from "../../graphql/event";
@ -31,11 +32,11 @@ export default class ConfirmParticipation extends Vue {
failed = false; failed = false;
async created() { async created(): Promise<void> {
await this.validateAction(); await this.validateAction();
} }
async validateAction() { async validateAction(): Promise<void> {
try { try {
const { data } = await this.$apollo.mutate<{ const { data } = await this.$apollo.mutate<{
confirmParticipation: IParticipant; confirmParticipation: IParticipant;
@ -56,6 +57,8 @@ export default class ConfirmParticipation extends Vue {
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
Snackbar.open({ message: err.message, type: "is-danger", position: "is-bottom" });
this.failed = true; this.failed = true;
} finally { } finally {
this.loading = false; this.loading = false;

View file

@ -66,6 +66,7 @@ import { FETCH_EVENT, JOIN_EVENT } from "@/graphql/event";
import { IConfig } from "@/types/config.model"; import { IConfig } from "@/types/config.model";
import { CONFIG } from "@/graphql/config"; import { CONFIG } from "@/graphql/config";
import { addLocalUnconfirmedAnonymousParticipation } from "@/services/AnonymousParticipationStorage"; import { addLocalUnconfirmedAnonymousParticipation } from "@/services/AnonymousParticipationStorage";
import { Route } from "vue-router";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
@Component({ @Component({
@ -101,7 +102,7 @@ export default class ParticipationWithoutAccount extends Vue {
EventJoinOptions = EventJoinOptions; EventJoinOptions = EventJoinOptions;
async joinEvent() { async joinEvent(): Promise<Route> {
this.error = false; this.error = false;
try { try {
const { data } = await this.$apollo.mutate<{ joinEvent: IParticipant }>({ const { data } = await this.$apollo.mutate<{ joinEvent: IParticipant }>({
@ -134,10 +135,10 @@ export default class ParticipationWithoutAccount extends Vue {
} }
if (data.joinEvent.role === ParticipantRole.NOT_CONFIRMED) { if (data.joinEvent.role === ParticipantRole.NOT_CONFIRMED) {
event.participantStats.notConfirmed = event.participantStats.notConfirmed + 1; event.participantStats.notConfirmed += 1;
} else { } else {
event.participantStats.going = event.participantStats.going + 1; event.participantStats.going += 1;
event.participantStats.participant = event.participantStats.participant + 1; event.participantStats.participant += 1;
} }
console.log("just before writequery"); console.log("just before writequery");
@ -157,18 +158,12 @@ export default class ParticipationWithoutAccount extends Vue {
console.log("done with crypto stuff"); console.log("done with crypto stuff");
} }
} catch (e) { } catch (e) {
console.error(e); this.error = e.message;
if (e.message === "GraphQL error: You are already a participant of this event") {
this.error = this.$t(
"This email is already registered as participant for this event"
) as string;
} }
} finally {
return this.$router.push({ return this.$router.push({
name: RouteName.EVENT, name: RouteName.EVENT,
params: { uuid: this.event.uuid }, params: { uuid: this.event.uuid },
}); });
} }
}
} }
</script> </script>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="root"> <div class="root">
<figure class="image" v-if="imageSrc"> <figure class="image" v-if="actualImageSrc">
<img :src="imageSrc" /> <img :src="actualImageSrc" />
</figure> </figure>
<figure class="image is-128x128" v-else> <figure class="image is-128x128" v-else>
<div class="image-placeholder"> <div class="image-placeholder">
@ -54,6 +54,8 @@ import { Component, Model, Prop, Vue, Watch } from "vue-property-decorator";
export default class PictureUpload extends Vue { export default class PictureUpload extends Vue {
@Model("change", { type: File }) readonly pictureFile!: File; @Model("change", { type: File }) readonly pictureFile!: File;
@Prop({ type: String, required: false }) defaultImageSrc!: string;
@Prop({ type: String, required: false, default: "image/gif,image/png,image/jpeg,image/webp" }) @Prop({ type: String, required: false, default: "image/gif,image/png,image/jpeg,image/webp" })
accept!: string; accept!: string;
@ -61,7 +63,7 @@ export default class PictureUpload extends Vue {
type: String, type: String,
required: false, required: false,
default() { default() {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
return this.$t("Avatar"); return this.$t("Avatar");
}, },
@ -70,16 +72,16 @@ export default class PictureUpload extends Vue {
imageSrc: string | null = null; imageSrc: string | null = null;
mounted() { mounted(): void {
this.updatePreview(this.pictureFile); this.updatePreview(this.pictureFile);
} }
@Watch("pictureFile") @Watch("pictureFile")
onPictureFileChanged(val: File) { onPictureFileChanged(val: File): void {
this.updatePreview(val); this.updatePreview(val);
} }
onFileChanged(file: File) { onFileChanged(file: File): void {
this.$emit("change", file); this.$emit("change", file);
this.updatePreview(file); this.updatePreview(file);
@ -93,5 +95,9 @@ export default class PictureUpload extends Vue {
this.imageSrc = null; this.imageSrc = null;
} }
get actualImageSrc(): string | null {
return this.imageSrc || this.defaultImageSrc;
}
} }
</script> </script>

View file

@ -38,6 +38,7 @@
import { Component, Mixins, Prop } from "vue-property-decorator"; import { Component, Mixins, Prop } from "vue-property-decorator";
import { Route } from "vue-router"; import { Route } from "vue-router";
import Draggable, { ChangeEvent } from "vuedraggable"; import Draggable, { ChangeEvent } from "vuedraggable";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { IResource } from "../../types/resource"; import { IResource } from "../../types/resource";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import ResourceMixin from "../../mixins/resource"; import ResourceMixin from "../../mixins/resource";
@ -57,7 +58,7 @@ export default class FolderItem extends Mixins(ResourceMixin) {
list = []; list = [];
groupObject: object = { groupObject: Record<string, unknown> = {
name: `folder-${this.resource.title}`, name: `folder-${this.resource.title}`,
pull: false, pull: false,
put: ["resources"], put: ["resources"],
@ -91,6 +92,7 @@ export default class FolderItem extends Mixins(ResourceMixin) {
} }
async moveResource(resource: IResource): Promise<IResource | undefined> { async moveResource(resource: IResource): Promise<IResource | undefined> {
try {
const { data } = await this.$apollo.mutate<{ updateResource: IResource }>({ const { data } = await this.$apollo.mutate<{ updateResource: IResource }>({
mutation: UPDATE_RESOURCE, mutation: UPDATE_RESOURCE,
variables: { variables: {
@ -104,6 +106,10 @@ export default class FolderItem extends Mixins(ResourceMixin) {
return undefined; return undefined;
} }
return data.updateResource; return data.updateResource;
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
return undefined;
}
} }
} }
</script> </script>

View file

@ -36,12 +36,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { USER_SETTINGS, SET_USER_SETTINGS } from "../../graphql/user"; import { USER_SETTINGS, SET_USER_SETTINGS } from "../../graphql/user";
import { import { ICurrentUser } from "../../types/current-user.model";
ICurrentUser,
INotificationPendingParticipationEnum,
} from "../../types/current-user.model";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
@Component({ @Component({
@ -56,11 +54,15 @@ export default class NotificationsOnboarding extends Vue {
RouteName = RouteName; RouteName = RouteName;
async updateSetting(variables: object) { async updateSetting(variables: Record<string, unknown>): Promise<void> {
try {
await this.$apollo.mutate<{ setUserSettings: string }>({ await this.$apollo.mutate<{ setUserSettings: string }>({
mutation: SET_USER_SETTINGS, mutation: SET_USER_SETTINGS,
variables, variables,
}); });
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
} }
</script> </script>

View file

@ -21,6 +21,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ITodo } from "../../types/todos"; import { ITodo } from "../../types/todos";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { UPDATE_TODO } from "../../graphql/todos"; import { UPDATE_TODO } from "../../graphql/todos";
@ -41,8 +42,9 @@ export default class Todo extends Vue {
this.updateTodo({ status }); this.updateTodo({ status });
} }
updateTodo(params: object) { async updateTodo(params: Record<string, unknown>): Promise<void> {
this.$apollo.mutate({ try {
await this.$apollo.mutate({
mutation: UPDATE_TODO, mutation: UPDATE_TODO,
variables: { variables: {
id: this.todo.id, id: this.todo.id,
@ -50,6 +52,9 @@ export default class Todo extends Vue {
}, },
}); });
this.editMode = false; this.editMode = false;
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
} }
</script> </script>

View file

@ -19,6 +19,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { debounce } from "lodash"; import { debounce } from "lodash";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ITodo } from "../../types/todos"; import { ITodo } from "../../types/todos";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { UPDATE_TODO } from "../../graphql/todos"; import { UPDATE_TODO } from "../../graphql/todos";
@ -39,7 +40,7 @@ export default class Todo extends Vue {
// We put this in data because of issues like // We put this in data because of issues like
// https://github.com/vuejs/vue-class-component/issues/263 // https://github.com/vuejs/vue-class-component/issues/263
data() { data(): Record<string, unknown> {
return { return {
debounceUpdateTodo: debounce(this.updateTodo, 1000), debounceUpdateTodo: debounce(this.updateTodo, 1000),
}; };
@ -77,8 +78,9 @@ export default class Todo extends Vue {
this.debounceUpdateTodo({ dueDate }); this.debounceUpdateTodo({ dueDate });
} }
updateTodo(params: object) { async updateTodo(params: Record<string, unknown>): Promise<void> {
this.$apollo.mutate({ try {
await this.$apollo.mutate({
mutation: UPDATE_TODO, mutation: UPDATE_TODO,
variables: { variables: {
id: this.todo.id, id: this.todo.id,
@ -86,6 +88,9 @@ export default class Todo extends Vue {
}, },
}); });
this.editMode = false; this.editMode = false;
} catch (e) {
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
}
} }
} }
</script> </script>

View file

@ -359,6 +359,53 @@ export const PERSON_MEMBERSHIPS = gql`
} }
`; `;
export const PERSON_MEMBERSHIPS_WITH_MEMBERS = gql`
query PersonMembershipsWithMembers($id: ID!) {
person(id: $id) {
id
memberships {
total
elements {
id
role
parent {
id
preferredUsername
name
domain
avatar {
url
}
members {
total
elements {
id
role
actor {
id
preferredUsername
name
domain
avatar {
url
}
}
}
}
}
invitedBy {
id
preferredUsername
name
}
insertedAt
updatedAt
}
}
}
}
`;
export const CREATE_PERSON = gql` export const CREATE_PERSON = gql`
mutation CreatePerson( mutation CreatePerson(
$preferredUsername: String! $preferredUsername: String!

View file

@ -6,7 +6,7 @@ export const CONFIG = gql`
name name
description description
registrationsOpen registrationsOpen
registrationsWhitelist registrationsAllowlist
demoMode demoMode
countryCode countryCode
anonymous { anonymous {
@ -94,7 +94,7 @@ export const ABOUT = gql`
longDescription longDescription
contact contact
registrationsOpen registrationsOpen
registrationsWhitelist registrationsAllowlist
anonymous { anonymous {
participation { participation {
allowed allowed

View file

@ -111,6 +111,17 @@ export const FETCH_EVENT = gql`
id, id,
summary summary
}, },
contacts {
avatar {
url,
}
preferredUsername,
name,
summary,
domain,
url,
id
},
attributedTo { attributedTo {
avatar { avatar {
url, url,
@ -229,6 +240,7 @@ export const CREATE_EVENT = gql`
$category: String, $category: String,
$physicalAddress: AddressInput, $physicalAddress: AddressInput,
$options: EventOptionsInput, $options: EventOptionsInput,
$contacts: [Contact]
) { ) {
createEvent( createEvent(
organizerActorId: $organizerActorId, organizerActorId: $organizerActorId,
@ -248,6 +260,7 @@ export const CREATE_EVENT = gql`
category: $category, category: $category,
physicalAddress: $physicalAddress physicalAddress: $physicalAddress
options: $options, options: $options,
contacts: $contacts
) { ) {
id, id,
uuid, uuid,
@ -292,6 +305,16 @@ export const CREATE_EVENT = gql`
url, url,
id, id,
}, },
contacts {
avatar {
url
},
preferredUsername,
domain,
name,
url,
id,
},
participantStats { participantStats {
going, going,
notApproved, notApproved,
@ -327,6 +350,7 @@ export const EDIT_EVENT = gql`
$category: String, $category: String,
$physicalAddress: AddressInput, $physicalAddress: AddressInput,
$options: EventOptionsInput, $options: EventOptionsInput,
$contacts: [Contact]
) { ) {
updateEvent( updateEvent(
eventId: $id, eventId: $id,
@ -347,6 +371,7 @@ export const EDIT_EVENT = gql`
category: $category, category: $category,
physicalAddress: $physicalAddress physicalAddress: $physicalAddress
options: $options, options: $options,
contacts: $contacts
) { ) {
id, id,
uuid, uuid,
@ -381,6 +406,16 @@ export const EDIT_EVENT = gql`
url url
} }
}, },
contacts {
avatar {
url
},
preferredUsername,
domain,
name,
url,
id,
},
organizerActor { organizerActor {
avatar { avatar {
url url

View file

@ -35,6 +35,7 @@ export const GET_RESOURCE = gql`
actor { actor {
id id
preferredUsername preferredUsername
name
domain domain
} }
children { children {

View file

@ -11,6 +11,7 @@ export const GET_TODO = gql`
actor { actor {
id id
preferredUsername preferredUsername
name
} }
title title
id id
@ -51,6 +52,7 @@ export const FETCH_TODO_LIST = gql`
id id
preferredUsername preferredUsername
domain domain
name
} }
} }
} }

View file

@ -22,7 +22,6 @@
"All the places have already been taken": "All the places have been taken|One place is still available|{places} places are still available", "All the places have already been taken": "All the places have been taken|One place is still available|{places} places are still available",
"Allow all comments": "Allow all comments", "Allow all comments": "Allow all comments",
"Allow registrations": "Allow registrations", "Allow registrations": "Allow registrations",
"An error has occurred.": "An error has occurred.",
"Anonymous participant": "Anonymous participant", "Anonymous participant": "Anonymous participant",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonymous participants will be asked to confirm their participation through e-mail.", "Anonymous participants will be asked to confirm their participation through e-mail.": "Anonymous participants will be asked to confirm their participation through e-mail.",
"Anonymous participations": "Anonymous participations", "Anonymous participations": "Anonymous participations",
@ -38,7 +37,6 @@
"Back to previous page": "Back to previous page", "Back to previous page": "Back to previous page",
"Before you can login, you need to click on the link inside it to validate your account.": "Before you can login, you need to click on the link inside it to validate your account.", "Before you can login, you need to click on the link inside it to validate your account.": "Before you can login, you need to click on the link inside it to validate your account.",
"By @{username}": "By @{username}", "By @{username}": "By @{username}",
"By {username} and {group}": "By {username} and {group}",
"Cancel anonymous participation": "Cancel anonymous participation", "Cancel anonymous participation": "Cancel anonymous participation",
"Cancel creation": "Cancel creation", "Cancel creation": "Cancel creation",
"Cancel edition": "Cancel edition", "Cancel edition": "Cancel edition",
@ -120,8 +118,6 @@
"Ends on…": "Ends on…", "Ends on…": "Ends on…",
"Enter the link URL": "Enter the link URL", "Enter the link URL": "Enter the link URL",
"Error while changing email": "Error while changing email", "Error while changing email": "Error while changing email",
"Error while communicating with the server.": "Error while communicating with the server.",
"Error while saving report.": "Error while saving report.",
"Error while validating account": "Error while validating account", "Error while validating account": "Error while validating account",
"Error while validating participation": "Error while validating participation", "Error while validating participation": "Error while validating participation",
"Event already passed": "Event already passed", "Event already passed": "Event already passed",
@ -129,7 +125,6 @@
"Event creation": "Event creation", "Event creation": "Event creation",
"Event edition": "Event edition", "Event edition": "Event edition",
"Event list": "Event list", "Event list": "Event list",
"Event not found.": "Event not found.",
"Event page settings": "Event page settings", "Event page settings": "Event page settings",
"Event to be confirmed": "Event to be confirmed", "Event to be confirmed": "Event to be confirmed",
"Event {eventTitle} deleted": "Event {eventTitle} deleted", "Event {eventTitle} deleted": "Event {eventTitle} deleted",
@ -158,7 +153,6 @@
"Getting location": "Getting location", "Getting location": "Getting location",
"Go": "Go", "Go": "Go",
"Going as {name}": "Going as {name}", "Going as {name}": "Going as {name}",
"Group List": "Group List",
"Group name": "Group name", "Group name": "Group name",
"Group {displayName} created": "Group {displayName} created", "Group {displayName} created": "Group {displayName} created",
"Groups": "Groups", "Groups": "Groups",
@ -177,7 +171,6 @@
"If an account with this email exists, we just sent another confirmation email to {email}": "If an account with this email exists, we just sent another confirmation email to {email}", "If an account with this email exists, we just sent another confirmation email to {email}": "If an account with this email exists, we just sent another confirmation email to {email}",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.", "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.",
"If you want, you may send a message to the event organizer here.": "If you want, you may send a message to the event organizer here.", "If you want, you may send a message to the event organizer here.": "If you want, you may send a message to the event organizer here.",
"Impossible to login, your email or password seems incorrect.": "Impossible to login, your email or password seems incorrect.",
"In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.", "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.",
"Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.": "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.", "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.": "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.",
"Instance Name": "Instance Name", "Instance Name": "Instance Name",
@ -240,7 +233,6 @@
"No participant to reject|Reject participant|Reject {number} participants": "No participant to reject|Reject participant|Reject {number} participants", "No participant to reject|Reject participant|Reject {number} participants": "No participant to reject|Reject participant|Reject {number} participants",
"No resolved reports yet": "No resolved reports yet", "No resolved reports yet": "No resolved reports yet",
"No results for \"{queryText}\"": "No results for \"{queryText}\"", "No results for \"{queryText}\"": "No results for \"{queryText}\"",
"No user account with this email was found. Maybe you made a typo?": "No user account with this email was found. Maybe you made a typo?",
"Notes": "Notes", "Notes": "Notes",
"Email notifications": "Email notifications", "Email notifications": "Email notifications",
"Number of places": "Number of places", "Number of places": "Number of places",
@ -261,10 +253,7 @@
"Other software may also support this.": "Other software may also support this.", "Other software may also support this.": "Other software may also support this.",
"Otherwise this identity will just be removed from the group administrators.": "Otherwise this identity will just be removed from the group administrators.", "Otherwise this identity will just be removed from the group administrators.": "Otherwise this identity will just be removed from the group administrators.",
"Page limited to my group (asks for auth)": "Page limited to my group (asks for auth)", "Page limited to my group (asks for auth)": "Page limited to my group (asks for auth)",
"Page not found": "Page not found",
"Page": "Page", "Page": "Page",
"Participant already was rejected.": "Participant already was rejected.",
"Participant has already been approved as participant.": "Participant has already been approved as participant.",
"Participant": "Participant", "Participant": "Participant",
"Participants": "Participants", "Participants": "Participants",
"Participate using your email address": "Participate using your email address", "Participate using your email address": "Participate using your email address",
@ -282,7 +271,6 @@
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.", "Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.",
"Please enter your password to confirm this action.": "Please enter your password to confirm this action.", "Please enter your password to confirm this action.": "Please enter your password to confirm this action.",
"Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.", "Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.",
"Please refresh the page and retry.": "Please refresh the page and retry.",
"Post a comment": "Post a comment", "Post a comment": "Post a comment",
"Post a reply": "Post a reply", "Post a reply": "Post a reply",
"Postal Code": "Postal Code", "Postal Code": "Postal Code",
@ -309,7 +297,6 @@
"Registration is allowed, anyone can register.": "Registration is allowed, anyone can register.", "Registration is allowed, anyone can register.": "Registration is allowed, anyone can register.",
"Registration is closed.": "Registration is closed.", "Registration is closed.": "Registration is closed.",
"Registration is currently closed.": "Registration is currently closed.", "Registration is currently closed.": "Registration is currently closed.",
"Registrations are restricted by whitelisting.": "Registrations are restricted by whitelisting.",
"Reject": "Reject", "Reject": "Reject",
"Rejected": "Rejected", "Rejected": "Rejected",
"Reopen": "Reopen", "Reopen": "Reopen",
@ -323,7 +310,6 @@
"Reported identity": "Reported identity", "Reported identity": "Reported identity",
"Reported": "Reported", "Reported": "Reported",
"Reports": "Reports", "Reports": "Reports",
"Resend confirmation email": "Resend confirmation email",
"Reset my password": "Reset my password", "Reset my password": "Reset my password",
"Resolved": "Resolved", "Resolved": "Resolved",
"Resource provided is not an URL": "Resource provided is not an URL", "Resource provided is not an URL": "Resource provided is not an URL",
@ -352,8 +338,6 @@
"The account's email address was changed. Check your emails to verify it.": "The account's email address was changed. Check your emails to verify it.", "The account's email address was changed. Check your emails to verify it.": "The account's email address was changed. Check your emails to verify it.",
"The actual number of participants may differ, as this event is hosted on another instance.": "The actual number of participants may differ, as this event is hosted on another instance.", "The actual number of participants may differ, as this event is hosted on another instance.": "The actual number of participants may differ, as this event is hosted on another instance.",
"The content came from another server. Transfer an anonymous copy of the report?": "The content came from another server. Transfer an anonymous copy of the report ?", "The content came from another server. Transfer an anonymous copy of the report?": "The content came from another server. Transfer an anonymous copy of the report ?",
"The current identity doesn't have any permission on this event. You should probably change it.": "The current identity doesn't have any permission on this event. You should probably change it.",
"The current password is invalid": "The current password is invalid",
"The draft event has been updated": "The draft event has been updated", "The draft event has been updated": "The draft event has been updated",
"The event has been created as a draft": "The event has been created as a draft", "The event has been created as a draft": "The event has been created as a draft",
"The event has been published": "The event has been published", "The event has been published": "The event has been published",
@ -363,20 +347,14 @@
"The event organizer didn't add any description.": "The event organizer didn't add any description.", "The event organizer didn't add any description.": "The event organizer didn't add any description.",
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.", "The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.",
"The event title will be ellipsed.": "The event title will be ellipsed.", "The event title will be ellipsed.": "The event title will be ellipsed.",
"The new email doesn't seem to be valid": "The new email doesn't seem to be valid",
"The new email must be different": "The new email must be different",
"The new password must be different": "The new password must be different",
"The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.", "The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.",
"The password provided is invalid": "The password provided is invalid",
"The password was successfully changed": "The password was successfully changed", "The password was successfully changed": "The password was successfully changed",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.", "The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.",
"The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.": "The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.",
"The {default_terms} will be used. They will be translated in the user's language.": "The {default_terms} will be used. They will be translated in the user's language.", "The {default_terms} will be used. They will be translated in the user's language.": "The {default_terms} will be used. They will be translated in the user's language.",
"There are {participants} participants.": "There are {participants} participants.", "There are {participants} participants.": "There are {participants} participants.",
"There will be no way to recover your data.": "There will be no way to recover your data.", "There will be no way to recover your data.": "There will be no way to recover your data.",
"These events may interest you": "These events may interest you", "These events may interest you": "These events may interest you",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.", "This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.",
"This email is already registered as participant for this event": "This email is already registered as participant for this event",
"This information is saved only on your computer. Click for details": "This information is saved only on your computer. Click for details", "This information is saved only on your computer. Click for details": "This information is saved only on your computer. Click for details",
"This instance isn't opened to registrations, but you can register on other instances.": "This instance isn't opened to registrations, but you can register on other instances.", "This instance isn't opened to registrations, but you can register on other instances.": "This instance isn't opened to registrations, but you can register on other instances.",
"This is a demonstration site to test the beta version of Mobilizon.": "This is a demonstration site to test the beta version of Mobilizon.", "This is a demonstration site to test the beta version of Mobilizon.": "This is a demonstration site to test the beta version of Mobilizon.",
@ -417,19 +395,16 @@
"Who can view this event and participate": "Who can view this event and participate", "Who can view this event and participate": "Who can view this event and participate",
"World map": "World map", "World map": "World map",
"Write something…": "Write something…", "Write something…": "Write something…",
"You are already a participant of this event.": "You are already a participant of this event.",
"You are participating in this event anonymously but didn't confirm participation": "You are participating in this event anonymously but didn't confirm participation", "You are participating in this event anonymously but didn't confirm participation": "You are participating in this event anonymously but didn't confirm participation",
"You are participating in this event anonymously": "You are participating in this event anonymously", "You are participating in this event anonymously": "You are participating in this event anonymously",
"You can add tags by hitting the Enter key or by adding a comma": "You can add tags by hitting the Enter key or by adding a comma", "You can add tags by hitting the Enter key or by adding a comma": "You can add tags by hitting the Enter key or by adding a comma",
"You can try another search term or drag and drop the marker on the map": "You can try another search term or drag and drop the marker on the map", "You can try another search term or drag and drop the marker on the map": "You can try another search term or drag and drop the marker on the map",
"You can't remove your last identity.": "You can't remove your last identity.",
"You don't follow any instances yet.": "You don't follow any instances yet.", "You don't follow any instances yet.": "You don't follow any instances yet.",
"You have been disconnected": "You have been disconnected", "You have been disconnected": "You have been disconnected",
"You have cancelled your participation": "You have cancelled your participation", "You have cancelled your participation": "You have cancelled your participation",
"You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days", "You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days",
"You have one event today.": "You have no events today | You have one event today. | You have {count} events today", "You have one event today.": "You have no events today | You have one event today. | You have {count} events today",
"You have one event tomorrow.": "You have no events tomorrow | You have one event tomorrow. | You have {count} events tomorrow", "You have one event tomorrow.": "You have no events tomorrow | You have one event tomorrow. | You have {count} events tomorrow",
"You may also ask to {resend_confirmation_email}.": "You may also ask to {resend_confirmation_email}.",
"You need to login.": "You need to login.", "You need to login.": "You need to login.",
"You will be redirected to the original instance": "You will be redirected to the original instance", "You will be redirected to the original instance": "You will be redirected to the original instance",
"You wish to participate to the following event": "You wish to participate to the following event", "You wish to participate to the following event": "You wish to participate to the following event",
@ -441,7 +416,6 @@
"Your current email is {email}. You use it to log in.": "Your current email is {email}. You use it to log in.", "Your current email is {email}. You use it to log in.": "Your current email is {email}. You use it to log in.",
"Your email has been changed": "Your email has been changed", "Your email has been changed": "Your email has been changed",
"Your email is being changed": "Your email is being changed", "Your email is being changed": "Your email is being changed",
"Your email is not whitelisted, you can't register.": "Your email is not whitelisted, you can't register.",
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.", "Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.",
"Your federated identity": "Your federated identity", "Your federated identity": "Your federated identity",
"Your participation has been confirmed": "Your participation has been confirmed", "Your participation has been confirmed": "Your participation has been confirmed",
@ -462,7 +436,6 @@
"its source code is public": "its source code is public", "its source code is public": "its source code is public",
"on our blog": "on our blog", "on our blog": "on our blog",
"profile@instance": "profile@instance", "profile@instance": "profile@instance",
"resend confirmation email": "resend confirmation email",
"respect of the fundamental freedoms": "respect of the fundamental freedoms", "respect of the fundamental freedoms": "respect of the fundamental freedoms",
"with another identity…": "with another identity…", "with another identity…": "with another identity…",
"{approved} / {total} seats": "{approved} / {total} seats", "{approved} / {total} seats": "{approved} / {total} seats",
@ -470,7 +443,6 @@
"{count} requests waiting": "{count} requests waiting", "{count} requests waiting": "{count} requests waiting",
"© The OpenStreetMap Contributors": "© The OpenStreetMap Contributors", "© The OpenStreetMap Contributors": "© The OpenStreetMap Contributors",
"@{username} ({role})": "@{username} ({role})", "@{username} ({role})": "@{username} ({role})",
"@{username}": "@{username}",
"@{group}": "@{group}", "@{group}": "@{group}",
"{title} ({count} todos)": "{title} ({count} todos)", "{title} ({count} todos)": "{title} ({count} todos)",
"Pick a group": "Pick a group", "Pick a group": "Pick a group",
@ -483,8 +455,6 @@
"Organizers": "Organizers", "Organizers": "Organizers",
"Hide the organizer": "Hide the organizer", "Hide the organizer": "Hide the organizer",
"Don't show @{organizer} as event host alongside @{group}": "Don't show @{organizer} as event host alongside @{group}", "Don't show @{organizer} as event host alongside @{group}": "Don't show @{organizer} as event host alongside @{group}",
"Group": "Group",
"Ongoing tasks": "Ongoing tasks",
"You need to create the group before you create an event.": "You need to create a group before you create an event.", "You need to create the group before you create an event.": "You need to create a group before you create an event.",
"This identity is not a member of any group.": "This identity is not a member of any group.", "This identity is not a member of any group.": "This identity is not a member of any group.",
"(Masked)": "(Masked)", "(Masked)": "(Masked)",
@ -499,7 +469,7 @@
"Decline": "Decline", "Decline": "Decline",
"Rename": "Rename", "Rename": "Rename",
"Move": "Move", "Move": "Move",
"Contact": "Contact", "Contact": "Contact|Contacts",
"Website": "Website", "Website": "Website",
"Actor": "Actor", "Actor": "Actor",
"Statut": "Statut", "Statut": "Statut",
@ -612,7 +582,6 @@
"terms of service": "terms of service", "terms of service": "terms of service",
"Please read the instance's {fullRules}": "Please read the instance's {fullRules}", "Please read the instance's {fullRules}": "Please read the instance's {fullRules}",
"I agree to the {instanceRules} and {termsOfService}": "I agree to the {instanceRules} and {termsOfService}", "I agree to the {instanceRules} and {termsOfService}": "I agree to the {instanceRules} and {termsOfService}",
"This email is already used.": "This email is already used.",
"Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.", "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.",
"more than 1360 contributors": "more than 1360 contributors", "more than 1360 contributors": "more than 1360 contributors",
"{moderator} has unsuspended profile {profile}": "{moderator} has unsuspended profile {profile}", "{moderator} has unsuspended profile {profile}": "{moderator} has unsuspended profile {profile}",
@ -693,8 +662,6 @@
"You can't change your password because you are registered through {provider}.": "You can't change your password because you are registered through {provider}.", "You can't change your password because you are registered through {provider}.": "You can't change your password because you are registered through {provider}.",
"Error while login with {provider}. Retry or login another way.": "Error while login with {provider}. Retry or login another way.", "Error while login with {provider}. Retry or login another way.": "Error while login with {provider}. Retry or login another way.",
"Error while login with {provider}. This login provider doesn't exist.": "Error while login with {provider}. This login provider doesn't exist.", "Error while login with {provider}. This login provider doesn't exist.": "Error while login with {provider}. This login provider doesn't exist.",
"This user has been disabled": "This user has been disabled",
"You can't reset your password because you use a 3rd-party auth provider to login.": "You can't reset your password because you use a 3rd-party auth provider to login.",
"Post": "Post", "Post": "Post",
"By {author}": "By {author}", "By {author}": "By {author}",
"Right now": "Right now", "Right now": "Right now",
@ -738,13 +705,10 @@
"{count} team members": "{count} team members", "{count} team members": "{count} team members",
"No resources yet": "No resources yet", "No resources yet": "No resources yet",
"No posts yet": "No posts yet", "No posts yet": "No posts yet",
"No ongoing todos": "No ongoing todos",
"No discussions yet": "No discussions yet", "No discussions yet": "No discussions yet",
"Add / Remove…": "Add / Remove…", "Add / Remove…": "Add / Remove…",
"No public posts": "No public posts",
"You have been removed from this group's members.": "You have been removed from this group's members.", "You have been removed from this group's members.": "You have been removed from this group's members.",
"Since you are a new member, private content can take a few minutes to appear.": "Since you are a new member, private content can take a few minutes to appear.", "Since you are a new member, private content can take a few minutes to appear.": "Since you are a new member, private content can take a few minutes to appear.",
"Leave group": "Leave group",
"Remove": "Remove", "Remove": "Remove",
"Update": "Update", "Update": "Update",
"Search…": "Search…", "Search…": "Search…",
@ -765,7 +729,6 @@
"Federated Group Name": "Federated Group Name", "Federated Group Name": "Federated Group Name",
"This is like your federated username (<code>{username}</code>) for groups. It will allow you to be found on the federation, and is guaranteed to be unique.": "This is like your federated username (<code>{username}</code>) for groups. It will allow you to be found on the federation, and is guaranteed to be unique.", "This is like your federated username (<code>{username}</code>) for groups. It will allow you to be found on the federation, and is guaranteed to be unique.": "This is like your federated username (<code>{username}</code>) for groups. It will allow you to be found on the federation, and is guaranteed to be unique.",
"Banner": "Banner", "Banner": "Banner",
"A group with this name already exists": "A group with this name already exists",
"Create or join an group and start organizing with other people": "Create or join an group and start organizing with other people", "Create or join an group and start organizing with other people": "Create or join an group and start organizing with other people",
"From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves</b> inside MeetUp?": "From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves</b> inside MeetUp?", "From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves</b> inside MeetUp?": "From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves</b> inside MeetUp?",
"We want to develop a <b>digital common</b> that everyone can make their own, one which respects <b>privacy and activism by design</b>.": "We want to develop a <b>digital common</b> that everyone can make their own, one which respects <b>privacy and activism by design</b>.", "We want to develop a <b>digital common</b> that everyone can make their own, one which respects <b>privacy and activism by design</b>.": "We want to develop a <b>digital common</b> that everyone can make their own, one which respects <b>privacy and activism by design</b>.",
@ -785,7 +748,6 @@
"Bio": "Bio", "Bio": "Bio",
"+ Start a discussion": "+ Start a discussion", "+ Start a discussion": "+ Start a discussion",
"+ Add a resource": "+ Add a resource", "+ Add a resource": "+ Add a resource",
"+ Add a todo": "+ Add a todo",
"+ Create an event": "+ Create an event", "+ Create an event": "+ Create an event",
"+ Post a public message": "+ Post a public message", "+ Post a public message": "+ Post a public message",
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.", "A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.",
@ -793,5 +755,18 @@
"No posts found": "No posts found", "No posts found": "No posts found",
"Last sign-in": "Last sign-in", "Last sign-in": "Last sign-in",
"Last IP adress": "Last IP adress", "Last IP adress": "Last IP adress",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines." "You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.",
"Leave": "Leave",
"Group settings saved": "Group settings saved",
"Error": "Error",
"Registrations are restricted by allowlisting.": "Registrations are restricted by allowlisting.",
"Resend confirmation email": "Resend confirmation email",
"By {group}": "By {group}",
"Pick a profile or a group": "Pick a profile or a group",
"Add a contact": "Add a contact",
"Your profile will be shown as contact.": "Your profile will be shown as contact.",
"Pick": "Pick",
"The event will show as attributed to your personal profile.": "The event will show as attributed to your personal profile.",
"The event will show as attributed to this group.": "The event will show as attributed to this group.",
"<b>{contact}</b> will be displayed as contact.": "<b>{contact}</b> will be displayed as contact.|<b>{contact}</b> will be displayed as contacts."
} }

View file

@ -101,7 +101,7 @@
"Confirmed": "Confirmé·e", "Confirmed": "Confirmé·e",
"Confirmed at": "Confirmé·e à", "Confirmed at": "Confirmé·e à",
"Confirmed: Will happen": "Confirmé : aura lieu", "Confirmed: Will happen": "Confirmé : aura lieu",
"Contact": "Contact", "Contact": "Contact|Contacts",
"Continue editing": "Continuer la modification", "Continue editing": "Continuer la modification",
"Cookies and Local storage": "Cookies et stockage local", "Cookies and Local storage": "Cookies et stockage local",
"Country": "Pays", "Country": "Pays",
@ -793,5 +793,17 @@
"No posts found": "Aucun billet trouvé", "No posts found": "Aucun billet trouvé",
"Last sign-in": "Dernière connexion", "Last sign-in": "Dernière connexion",
"Last IP adress": "Dernière addresse IP", "Last IP adress": "Dernière addresse IP",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Vous aurez besoin de transmettre l'URL du groupe pour que d'autres personnes accèdent au profil du groupe. Le groupe ne sera pas trouvable dans la recherche de Mobilizon ni dans les moteurs de recherche habituels." "You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Vous aurez besoin de transmettre l'URL du groupe pour que d'autres personnes accèdent au profil du groupe. Le groupe ne sera pas trouvable dans la recherche de Mobilizon ni dans les moteurs de recherche habituels.",
"Pick a profile or a group": "Choisir un profil ou groupe",
"Add a contact": "Ajouter un contact",
"Your profile will be shown as contact.": "Votre profil sera affiché en tant que contact.",
"Pick": "Choisir",
"Leave": "Quitter",
"The event will show as attributed to your personal profile.": "L'événement sera affiché comme étant attribué à votre profil.",
"The event will show as attributed to this group.": "L'événement sera affiché comme étant attribué à ce groupe.",
"By {group}": "Par {group}",
"Group settings saved": "Paramètres du groupe sauvegardés",
"Error": "Erreur",
"Registrations are restricted by allowlisting.": "Les inscriptions sont restreintes par liste d'accès.",
"<b>{contact}</b> will be displayed as contact.": "<b>{contact}</b> sera affiché·e comme contact.|<b>{contact}</b> seront affiché·es comme contacts."
} }

View file

@ -7,7 +7,7 @@ import {
FETCH_EVENT, FETCH_EVENT,
LEAVE_EVENT, LEAVE_EVENT,
} from "../graphql/event"; } from "../graphql/event";
import RouteName from "../router/name"; import { SnackbarProgrammatic as Snackbar } from "buefy";
import { IPerson } from "../types/actor"; import { IPerson } from "../types/actor";
@Component @Component
@ -80,6 +80,7 @@ export default class EventMixin extends mixins(Vue) {
this.participationCancelledMessage(); this.participationCancelledMessage();
} }
} catch (error) { } catch (error) {
Snackbar.open({ message: error.message, type: "is-danger", position: "is-bottom" });
console.error(error); console.error(error);
} }
} }
@ -143,6 +144,8 @@ export default class EventMixin extends mixins(Vue) {
duration: 5000, duration: 5000,
}); });
} catch (error) { } catch (error) {
Snackbar.open({ message: error.message, type: "is-danger", position: "is-bottom" });
console.error(error); console.error(error);
} }
} }

View file

@ -1,11 +1,9 @@
import { RouteConfig } from "vue-router"; import { RouteConfig } from "vue-router";
import GroupList from "@/views/Group/GroupList.vue";
import CreateGroup from "@/views/Group/Create.vue"; import CreateGroup from "@/views/Group/Create.vue";
import Group from "@/views/Group/Group.vue"; import Group from "@/views/Group/Group.vue";
import MyGroups from "@/views/Group/MyGroups.vue"; import MyGroups from "@/views/Group/MyGroups.vue";
export enum ActorRouteName { export enum ActorRouteName {
GROUP_LIST = "GroupList",
GROUP = "Group", GROUP = "Group",
CREATE_GROUP = "CreateGroup", CREATE_GROUP = "CreateGroup",
PROFILE = "Profile", PROFILE = "Profile",
@ -13,12 +11,6 @@ export enum ActorRouteName {
} }
export const actorRoutes: RouteConfig[] = [ export const actorRoutes: RouteConfig[] = [
{
path: "/groups",
name: ActorRouteName.GROUP_LIST,
component: GroupList,
meta: { requiredAuth: false },
},
{ {
path: "/groups/create", path: "/groups/create",
name: ActorRouteName.CREATE_GROUP, name: ActorRouteName.CREATE_GROUP,

View file

@ -11,7 +11,7 @@ export const beforeRegisterGuard: NavigationGuard = async (to, from, next) => {
const { config } = data; const { config } = data;
if (!config.registrationsOpen && !config.registrationsWhitelist) { if (!config.registrationsOpen && !config.registrationsAllowlist) {
return next({ return next({
name: "Error", name: "Error",
query: { code: ErrorCode.REGISTRATION_CLOSED }, query: { code: ErrorCode.REGISTRATION_CLOSED },

View file

@ -16,8 +16,8 @@ export interface IActor {
summary: string; summary: string;
preferredUsername: string; preferredUsername: string;
suspended: boolean; suspended: boolean;
avatar: IPicture | null; avatar?: IPicture | null;
banner: IPicture | null; banner?: IPicture | null;
type: ActorType; type: ActorType;
} }

View file

@ -8,7 +8,7 @@ export interface IConfig {
contact: string; contact: string;
registrationsOpen: boolean; registrationsOpen: boolean;
registrationsWhitelist: boolean; registrationsAllowlist: boolean;
demoMode: boolean; demoMode: boolean;
countryCode: string; countryCode: string;
location: { location: {

View file

@ -151,6 +151,7 @@ export interface IEvent {
tags: ITag[]; tags: ITag[];
options: IEventOptions; options: IEventOptions;
contacts: IActor[];
toEditJSON(): IEventEditJSON; toEditJSON(): IEventEditJSON;
} }
@ -259,6 +260,8 @@ export class EventModel implements IEvent {
tags: ITag[] = []; tags: ITag[] = [];
contacts: IActor[] = [];
options: IEventOptions = new EventOptions(); options: IEventOptions = new EventOptions();
constructor(hash?: IEvent) { constructor(hash?: IEvent) {
@ -296,6 +299,8 @@ export class EventModel implements IEvent {
this.physicalAddress = hash.physicalAddress ? new Address(hash.physicalAddress) : undefined; this.physicalAddress = hash.physicalAddress ? new Address(hash.physicalAddress) : undefined;
this.participantStats = hash.participantStats; this.participantStats = hash.participantStats;
this.contacts = hash.contacts;
this.tags = hash.tags; this.tags = hash.tags;
if (hash.options) this.options = hash.options; if (hash.options) this.options = hash.options;
} }
@ -319,6 +324,9 @@ export class EventModel implements IEvent {
options: this.options, options: this.options,
// organizerActorId: this.organizerActor && this.organizerActor.id ? this.organizerActor.id : null, // organizerActorId: this.organizerActor && this.organizerActor.id ? this.organizerActor.id : null,
attributedToId: this.attributedTo && this.attributedTo.id ? this.attributedTo.id : null, attributedToId: this.attributedTo && this.attributedTo.id ? this.attributedTo.id : null,
contacts: this.contacts.map(({ id }) => ({
id,
})),
}; };
} }
} }
@ -340,4 +348,5 @@ interface IEventEditJSON {
physicalAddress?: IAddress; physicalAddress?: IAddress;
tags: string[]; tags: string[];
options: IEventOptions; options: IEventOptions;
contacts: { id?: string }[];
} }

View file

@ -1,5 +1,5 @@
export enum LoginErrorCode { export enum LoginErrorCode {
NEED_TO_LOGIN = "rouge", NEED_TO_LOGIN = "need_to_login",
} }
export enum LoginError { export enum LoginError {

17
js/src/typings/intl.d.ts vendored Normal file
View file

@ -0,0 +1,17 @@
declare namespace Intl {
type Locale = string;
type Locales = Locale[];
type Type = "conjunction" | "disjunction" | "unit";
type Style = "long" | "short" | "narrow";
type LocaleMatcher = "lookup" | "best fit";
interface ListFormatOptions {
type: Type;
style: Style;
localeMatcher: LocaleMatcher;
}
class ListFormat {
constructor(locales?: Locale | Locales | undefined, options?: Partial<ListFormatOptions>);
public format: (items: string[]) => string;
}
}

View file

@ -1,91 +0,0 @@
import { i18n } from "@/utils/i18n";
export const refreshSuggestion = i18n.t("Please refresh the page and retry.") as string;
export const defaultError: IError = {
match: / /,
value: i18n.t("An error has occurred.") as string,
};
export interface IError {
match: RegExp;
value: string | null;
suggestRefresh?: boolean;
}
export const errors: IError[] = [
{
match: /^Event with UUID .* not found$/,
value: i18n.t("Page not found") as string,
suggestRefresh: false,
},
{
match: /^Event not found$/,
value: i18n.t("Event not found.") as string,
},
{
match: /^Event with this ID .* doesn't exist$/,
value: i18n.t("Event not found.") as string,
},
{
match: /^Error while saving report$/,
value: i18n.t("Error while saving report.") as string,
},
{
match: /^Participant already has role rejected$/,
value: i18n.t("Participant already was rejected.") as string,
},
{
match: /^Participant already has role participant$/,
value: i18n.t("Participant has already been approved as participant.") as string,
},
{
match: /^You are already a participant of this event$/,
value: i18n.t("You are already a participant of this event.") as string,
},
{
match: /NetworkError when attempting to fetch resource.$/,
value: i18n.t("Error while communicating with the server.") as string,
},
{
match: /Provided moderator actor ID doesn't have permission on this event$/,
value: i18n.t(
"The current identity doesn't have any permission on this event. You should probably change it."
) as string,
suggestRefresh: false,
},
{
match: /Your email is not on the whitelist$/,
value: i18n.t("Your email is not whitelisted, you can't register.") as string,
suggestRefresh: false,
},
{
match: /Cannot remove the last identity of a user/,
value: i18n.t("You can't remove your last identity.") as string,
suggestRefresh: false,
},
{
match: /^No user with this email was found$/,
value: null,
},
{
match: /^Username is already taken$/,
value: null,
},
{
match: /^Impossible to authenticate, either your email or password are invalid.$/,
value: null,
},
{
match: /^No user to validate with this email was found$/,
value: null,
},
{
match: /^This email is already used.$/,
value: i18n.t("This email is already used.") as string,
},
{
match: /^User account not confirmed$/,
value: null,
},
];

View file

@ -13,3 +13,11 @@ export const i18n = new VueI18n({
messages, // set locale messages messages, // set locale messages
fallbackLocale: "en_US", fallbackLocale: "en_US",
}); });
export function formatList(list: string[]): string {
if (window.Intl && Intl.ListFormat) {
const formatter = new Intl.ListFormat(undefined, { style: "long", type: "conjunction" });
return formatter.format(list);
}
return list.join(",");
}

View file

@ -45,10 +45,10 @@
</tr> </tr>
<tr> <tr>
<td>{{ $t("Registrations") }}</td> <td>{{ $t("Registrations") }}</td>
<td v-if="config.registrationsOpen && config.registrationsWhitelist"> <td v-if="config.registrationsOpen && config.registrationsAllowlist">
{{ $t("Restricted") }} {{ $t("Restricted") }}
</td> </td>
<td v-if="config.registrationsOpen && !config.registrationsWhitelist"> <td v-if="config.registrationsOpen && !config.registrationsAllowlist">
{{ $t("Open") }} {{ $t("Open") }}
</td> </td>
<td v-else>{{ $t("Closed") }}</td> <td v-else>{{ $t("Closed") }}</td>

View file

@ -124,6 +124,7 @@ h1 {
<script lang="ts"> <script lang="ts">
import { Component, Prop, Watch } from "vue-property-decorator"; import { Component, Prop, Watch } from "vue-property-decorator";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import { Route } from "vue-router";
import { import {
CREATE_PERSON, CREATE_PERSON,
CURRENT_ACTOR_CLIENT, CURRENT_ACTOR_CLIENT,
@ -176,25 +177,25 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
RouteName = RouteName; RouteName = RouteName;
get message() { get message(): string | null {
if (this.isUpdate) return null; if (this.isUpdate) return null;
return this.$t("Only alphanumeric characters and underscores are supported."); return this.$t("Only alphanumeric characters and underscores are supported.") as string;
} }
@Watch("isUpdate") @Watch("isUpdate")
async isUpdateChanged() { async isUpdateChanged(): Promise<void> {
this.resetFields(); this.resetFields();
} }
@Watch("identityName", { immediate: true }) @Watch("identityName", { immediate: true })
async onIdentityParamChanged(val: string) { async onIdentityParamChanged(val: string): Promise<Route | undefined> {
// Only used when we update the identity // Only used when we update the identity
if (!this.isUpdate) return; if (!this.isUpdate) return;
await this.redirectIfNoIdentitySelected(val); await this.redirectIfNoIdentitySelected(val);
if (!this.identityName) { if (!this.identityName) {
return await this.$router.push({ name: "CreateIdentity" }); return this.$router.push({ name: "CreateIdentity" });
} }
if (this.identityName && this.identity) { if (this.identityName && this.identity) {
@ -202,7 +203,7 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
} }
} }
submit() { submit(): Promise<void> {
if (this.isUpdate) return this.updateIdentity(); if (this.isUpdate) return this.updateIdentity();
return this.createIdentity(); return this.createIdentity();
@ -211,7 +212,7 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
/** /**
* Delete an identity * Delete an identity
*/ */
async deleteIdentity() { async deleteIdentity(): Promise<void> {
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: DELETE_PERSON, mutation: DELETE_PERSON,
@ -252,7 +253,7 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
} }
} }
async updateIdentity() { async updateIdentity(): Promise<void> {
try { try {
const variables = await this.buildVariables(); const variables = await this.buildVariables();
@ -285,7 +286,7 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
} }
} }
async createIdentity() { async createIdentity(): Promise<void> {
try { try {
const variables = await this.buildVariables(); const variables = await this.buildVariables();
@ -320,11 +321,12 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
} }
} }
get getInstanceHost() { // eslint-disable-next-line class-methods-use-this
get getInstanceHost(): string {
return MOBILIZON_INSTANCE_HOST; return MOBILIZON_INSTANCE_HOST;
} }
openDeleteIdentityConfirmation() { openDeleteIdentityConfirmation(): void {
this.$buefy.dialog.prompt({ this.$buefy.dialog.prompt({
type: "is-danger", type: "is-danger",
title: this.$t("Delete your identity") as string, title: this.$t("Delete your identity") as string,

View file

@ -202,7 +202,6 @@ import { SUSPEND_PROFILE, UNSUSPEND_PROFILE } from "../../graphql/actor";
import { IGroup, MemberRole } from "../../types/actor"; import { IGroup, MemberRole } from "../../types/actor";
import { usernameWithDomain, IActor } from "../../types/actor/actor.model"; import { usernameWithDomain, IActor } from "../../types/actor/actor.model";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { IEvent } from "../../types/event.model";
import ActorCard from "../../components/Account/ActorCard.vue"; import ActorCard from "../../components/Account/ActorCard.vue";
const EVENTS_PER_PAGE = 10; const EVENTS_PER_PAGE = 10;
@ -248,22 +247,22 @@ export default class AdminGroupProfile extends Vue {
MemberRole = MemberRole; MemberRole = MemberRole;
get metadata(): Array<object> { get metadata(): Array<Record<string, string>> {
if (!this.group) return []; if (!this.group) return [];
const res: object[] = [ const res: Record<string, string>[] = [
{ {
key: this.$t("Status") as string, key: this.$t("Status") as string,
value: this.group.suspended ? this.$t("Suspended") : this.$t("Active"), value: (this.group.suspended ? this.$t("Suspended") : this.$t("Active")) as string,
}, },
{ {
key: this.$t("Domain") as string, key: this.$t("Domain") as string,
value: this.group.domain ? this.group.domain : this.$t("Local"), value: (this.group.domain ? this.group.domain : this.$t("Local")) as string,
}, },
]; ];
return res; return res;
} }
confirmSuspendProfile() { confirmSuspendProfile(): void {
const message = (this.group.domain const message = (this.group.domain
? this.$t( ? this.$t(
"Are you sure you want to <b>suspend</b> this group? As this group originates from instance {instance}, this will only remove local members and delete the local data, as well as rejecting all the future data.", "Are you sure you want to <b>suspend</b> this group? As this group originates from instance {instance}, this will only remove local members and delete the local data, as well as rejecting all the future data.",
@ -284,7 +283,7 @@ export default class AdminGroupProfile extends Vue {
}); });
} }
async suspendProfile() { async suspendProfile(): Promise<void> {
this.$apollo.mutate<{ suspendProfile: { id: string } }>({ this.$apollo.mutate<{ suspendProfile: { id: string } }>({
mutation: SUSPEND_PROFILE, mutation: SUSPEND_PROFILE,
variables: { variables: {
@ -318,9 +317,9 @@ export default class AdminGroupProfile extends Vue {
}); });
} }
async unsuspendProfile() { async unsuspendProfile(): Promise<void> {
const profileID = this.id; const profileID = this.id;
this.$apollo.mutate<{ unsuspendProfile: { id: string } }>({ await this.$apollo.mutate<{ unsuspendProfile: { id: string } }>({
mutation: UNSUSPEND_PROFILE, mutation: UNSUSPEND_PROFILE,
variables: { variables: {
id: this.id, id: this.id,
@ -336,7 +335,7 @@ export default class AdminGroupProfile extends Vue {
}); });
} }
async refreshProfile() { async refreshProfile(): Promise<void> {
this.$apollo.mutate<{ refreshProfile: IActor }>({ this.$apollo.mutate<{ refreshProfile: IActor }>({
mutation: REFRESH_PROFILE, mutation: REFRESH_PROFILE,
variables: { variables: {
@ -345,7 +344,7 @@ export default class AdminGroupProfile extends Vue {
}); });
} }
async onOrganizedEventsPageChange(page: number) { async onOrganizedEventsPageChange(page: number): Promise<void> {
this.organizedEventsPage = page; this.organizedEventsPage = page;
await this.$apollo.queries.group.fetchMore({ await this.$apollo.queries.group.fetchMore({
variables: { variables: {
@ -370,7 +369,7 @@ export default class AdminGroupProfile extends Vue {
}); });
} }
async onMembersPageChange(page: number) { async onMembersPageChange(page: number): Promise<void> {
this.membersPage = page; this.membersPage = page;
await this.$apollo.queries.group.fetchMore({ await this.$apollo.queries.group.fetchMore({
variables: { variables: {
@ -395,7 +394,7 @@ export default class AdminGroupProfile extends Vue {
}); });
} }
async onPostsPageChange(page: number) { async onPostsPageChange(page: number): Promise<void> {
this.postsPage = page; this.postsPage = page;
await this.$apollo.queries.group.fetchMore({ await this.$apollo.queries.group.fetchMore({
variables: { variables: {

View file

@ -11,7 +11,7 @@
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
>{{ `@${group.preferredUsername}` }}</router-link >{{ group.name }}</router-link
> >
</li> </li>
<li class="is-active"> <li class="is-active">

View file

@ -41,21 +41,31 @@
</b-field> </b-field>
<subtitle>{{ $t("Organizers") }}</subtitle> <subtitle>{{ $t("Organizers") }}</subtitle>
<div class="columns">
<div class="column"> <div v-if="config && config.features.groups">
<b-field :label="$t('Organizer')"> <b-field>
<identity-picker-wrapper <organizer-picker-wrapper
v-model="event.organizerActor" v-model="event.attributedTo"
:masked="event.options.hideOrganizerWhenGroupEvent" :contacts.sync="event.contacts"
@input="resetAttributedToOnOrganizerChange" :identity="event.organizerActor"
/> />
</b-field> </b-field>
</div> <p v-if="!event.attributedTo.id || attributedToEqualToOrganizerActor">
<div class="column" v-if="config && config.features.groups"> {{ $t("The event will show as attributed to your personal profile.") }}
<b-field :label="$t('Group')" v-if="event.organizerActor"> </p>
<group-picker-wrapper v-model="event.attributedTo" :identity="event.organizerActor" /> <p v-else>
</b-field> <span>{{ $t("The event will show as attributed to this group.") }}</span>
</div> <span
v-if="event.contacts && event.contacts.length"
v-html="
$tc('<b>{contact}</b> will be displayed as contact.', event.contacts.length, {
contact: formatList(
event.contacts.map((contact) => displayNameAndUsername(contact))
),
})
"
/>
</p>
</div> </div>
<!-- <div class="field" v-if="event.attributedTo.id">--> <!-- <div class="field" v-if="event.attributedTo.id">-->
<!-- <label class="label">{{ $t('Hide the organizer') }}</label>--> <!-- <label class="label">{{ $t('Hide the organizer') }}</label>-->
@ -332,8 +342,9 @@ import TagInput from "@/components/Event/TagInput.vue";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue"; import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue"; import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
import Subtitle from "@/components/Utils/Subtitle.vue"; import Subtitle from "@/components/Utils/Subtitle.vue";
import GroupPickerWrapper from "@/components/Group/GroupPickerWrapper.vue";
import { Route } from "vue-router"; import { Route } from "vue-router";
import { formatList } from "@/utils/i18n";
import OrganizerPickerWrapper from "../../components/Event/OrganizerPickerWrapper.vue";
import { import {
CREATE_EVENT, CREATE_EVENT,
EDIT_EVENT, EDIT_EVENT,
@ -354,7 +365,7 @@ import {
LOGGED_USER_DRAFTS, LOGGED_USER_DRAFTS,
LOGGED_USER_PARTICIPATIONS, LOGGED_USER_PARTICIPATIONS,
} from "../../graphql/actor"; } from "../../graphql/actor";
import { Group, IPerson, Person } from "../../types/actor"; import { IPerson, Person, displayNameAndUsername } from "../../types/actor";
import { TAGS } from "../../graphql/tags"; import { TAGS } from "../../graphql/tags";
import { ITag } from "../../types/tag.model"; import { ITag } from "../../types/tag.model";
import { buildFileFromIPicture, buildFileVariable, readFileAsync } from "../../utils/image"; import { buildFileFromIPicture, buildFileVariable, readFileAsync } from "../../utils/image";
@ -362,12 +373,13 @@ import RouteName from "../../router/name";
import "intersection-observer"; import "intersection-observer";
import { CONFIG } from "../../graphql/config"; import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model"; import { IConfig } from "../../types/config.model";
import { RefetchQueryDescription } from "apollo-client/core/watchQueryOptions";
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10; const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
@Component({ @Component({
components: { components: {
GroupPickerWrapper, OrganizerPickerWrapper,
Subtitle, Subtitle,
IdentityPickerWrapper, IdentityPickerWrapper,
FullAddressAutoComplete, FullAddressAutoComplete,
@ -398,6 +410,7 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
metaInfo() { metaInfo() {
return { return {
// if no subcomponents specify a metaInfo.title, this title will be used // if no subcomponents specify a metaInfo.title, this title will be used
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
title: (this.isUpdate ? this.$t("Event edition") : this.$t("Event creation")) as string, title: (this.isUpdate ? this.$t("Event edition") : this.$t("Event creation")) as string,
// all titles will be injected into this template // all titles will be injected into this template
@ -444,8 +457,12 @@ export default class EditEvent extends Vue {
endsOnNull = false; endsOnNull = false;
displayNameAndUsername = displayNameAndUsername;
formatList = formatList;
@Watch("eventId", { immediate: true }) @Watch("eventId", { immediate: true })
resetFormForCreation(eventId: string) { resetFormForCreation(eventId: string): void {
if (eventId === undefined) { if (eventId === undefined) {
this.event = new EventModel(); this.event = new EventModel();
} }
@ -467,9 +484,10 @@ export default class EditEvent extends Vue {
this.event.organizerActor = this.event.organizerActor || this.currentActor; this.event.organizerActor = this.event.organizerActor || this.currentActor;
} }
async mounted() { async mounted(): Promise<void> {
this.observer = new IntersectionObserver( this.observer = new IntersectionObserver(
(entries) => { (entries) => {
// eslint-disable-next-line no-restricted-syntax
for (const entry of entries) { for (const entry of entries) {
if (entry) { if (entry) {
this.showFixedNavbar = !entry.isIntersecting; this.showFixedNavbar = !entry.isIntersecting;
@ -489,16 +507,16 @@ export default class EditEvent extends Vue {
} }
} }
createOrUpdateDraft(e: Event) { createOrUpdateDraft(e: Event): void {
e.preventDefault(); e.preventDefault();
if (this.validateForm()) { if (this.validateForm()) {
if (this.eventId && !this.isDuplicate) return this.updateEvent(); if (this.eventId && !this.isDuplicate) this.updateEvent();
return this.createEvent(); this.createEvent();
} }
} }
createOrUpdatePublish(e: Event) { createOrUpdatePublish(e: Event): void {
if (this.validateForm()) { if (this.validateForm()) {
this.event.draft = false; this.event.draft = false;
this.createOrUpdateDraft(e); this.createOrUpdateDraft(e);
@ -506,21 +524,17 @@ export default class EditEvent extends Vue {
} }
@Watch("currentActor") @Watch("currentActor")
setCurrentActor() { setCurrentActor(): void {
this.event.organizerActor = this.currentActor; this.event.organizerActor = this.currentActor;
} }
@Watch("event") @Watch("event")
setInitialData() { setInitialData(): void {
if (this.isUpdate && this.unmodifiedEvent === undefined && this.event && this.event.uuid) { if (this.isUpdate && this.unmodifiedEvent === undefined && this.event && this.event.uuid) {
this.unmodifiedEvent = JSON.parse(JSON.stringify(this.event.toEditJSON())); this.unmodifiedEvent = JSON.parse(JSON.stringify(this.event.toEditJSON()));
} }
} }
resetAttributedToOnOrganizerChange() {
this.event.attributedTo = new Group();
}
// @Watch('event.attributedTo', { deep: true }) // @Watch('event.attributedTo', { deep: true })
// updateHideOrganizerWhenGroupEventOption(attributedTo) { // updateHideOrganizerWhenGroupEventOption(attributedTo) {
// if (!attributedTo.preferredUsername) { // if (!attributedTo.preferredUsername) {
@ -537,7 +551,7 @@ export default class EditEvent extends Vue {
return false; return false;
} }
async createEvent() { async createEvent(): Promise<void> {
const variables = await this.buildVariables(); const variables = await this.buildVariables();
try { try {
@ -565,7 +579,7 @@ export default class EditEvent extends Vue {
} }
} }
async updateEvent() { async updateEvent(): Promise<void> {
const variables = await this.buildVariables(); const variables = await this.buildVariables();
try { try {
@ -652,7 +666,8 @@ export default class EditEvent extends Vue {
/** /**
* Refresh drafts or participation cache depending if the event is still draft or not * Refresh drafts or participation cache depending if the event is still draft or not
*/ */
private postRefetchQueries(updateEvent: IEvent) { // eslint-disable-next-line class-methods-use-this
private postRefetchQueries(updateEvent: IEvent): RefetchQueryDescription {
if (updateEvent.draft) { if (updateEvent.draft) {
return [ return [
{ {
@ -670,6 +685,12 @@ export default class EditEvent extends Vue {
]; ];
} }
get attributedToEqualToOrganizerActor(): boolean {
return (this.event.organizerActor &&
this.event.attributedTo &&
this.event.attributedTo.id === this.event.organizerActor.id) as boolean;
}
/** /**
* Build variables for Event GraphQL creation query * Build variables for Event GraphQL creation query
*/ */
@ -680,9 +701,13 @@ export default class EditEvent extends Vue {
organizerActorId: this.event.organizerActor.id, organizerActorId: this.event.organizerActor.id,
}); });
} }
if (this.event.attributedTo) { const attributedToId =
res = Object.assign(res, { attributedToId: this.event.attributedTo.id }); this.event.attributedTo &&
} !this.attributedToEqualToOrganizerActor &&
this.event.attributedTo.id
? this.event.attributedTo.id
: null;
res = Object.assign(res, { attributedToId });
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
@ -736,7 +761,7 @@ export default class EditEvent extends Vue {
} }
@Watch("limitedPlaces") @Watch("limitedPlaces")
updatedEventCapacityOptions(limitedPlaces: boolean) { updatedEventCapacityOptions(limitedPlaces: boolean): void {
if (!limitedPlaces) { if (!limitedPlaces) {
this.event.options.maximumAttendeeCapacity = 0; this.event.options.maximumAttendeeCapacity = 0;
this.event.options.remainingAttendeeCapacity = 0; this.event.options.remainingAttendeeCapacity = 0;
@ -748,7 +773,7 @@ export default class EditEvent extends Vue {
} }
@Watch("needsApproval") @Watch("needsApproval")
updateEventJoinOptions(needsApproval: boolean) { updateEventJoinOptions(needsApproval: boolean): void {
if (needsApproval === true) { if (needsApproval === true) {
this.event.joinOptions = EventJoinOptions.RESTRICTED; this.event.joinOptions = EventJoinOptions.RESTRICTED;
} else { } else {
@ -756,16 +781,16 @@ export default class EditEvent extends Vue {
} }
} }
get checkTitleLength() { get checkTitleLength(): Array<string | undefined> {
return this.event.title.length > 80 return this.event.title.length > 80
? ["is-info", this.$t("The event title will be ellipsed.")] ? ["is-info", this.$t("The event title will be ellipsed.") as string]
: [undefined, undefined]; : [undefined, undefined];
} }
/** /**
* Confirm cancel * Confirm cancel
*/ */
confirmGoElsewhere(callback: (value?: string) => any) { confirmGoElsewhere(callback: (value?: string) => any): void | Function {
if (!this.isEventModified) { if (!this.isEventModified) {
return callback(); return callback();
} }
@ -796,11 +821,11 @@ export default class EditEvent extends Vue {
/** /**
* Confirm cancel * Confirm cancel
*/ */
confirmGoBack() { confirmGoBack(): void {
this.confirmGoElsewhere(() => this.$router.go(-1)); this.confirmGoElsewhere(() => this.$router.go(-1));
} }
beforeRouteLeave(to: Route, from: Route, next: Function) { beforeRouteLeave(to: Route, from: Route, next: Function): void {
if (to.name === RouteName.EVENT) return next(); if (to.name === RouteName.EVENT) return next();
this.confirmGoElsewhere(() => next()); this.confirmGoElsewhere(() => next());
} }
@ -814,7 +839,7 @@ export default class EditEvent extends Vue {
} }
@Watch("beginsOn", { deep: true }) @Watch("beginsOn", { deep: true })
onBeginsOnChanged(beginsOn: string) { onBeginsOnChanged(beginsOn: string): void {
if (!this.event.endsOn) return; if (!this.event.endsOn) return;
const dateBeginsOn = new Date(beginsOn); const dateBeginsOn = new Date(beginsOn);
const dateEndsOn = new Date(this.event.endsOn); const dateEndsOn = new Date(this.event.endsOn);

View file

@ -36,16 +36,7 @@
</popover-actor-card> </popover-actor-card>
</span> </span>
<span v-else-if="event.organizerActor && event.attributedTo"> <span v-else-if="event.organizerActor && event.attributedTo">
<i18n path="By {username} and {group}"> <i18n path="By {group}">
<popover-actor-card
:actor="event.organizerActor"
slot="username"
:inline="true"
>
{{
$t("@{username}", { username: usernameWithDomain(event.organizerActor) })
}}
</popover-actor-card>
<popover-actor-card :actor="event.attributedTo" slot="group" :inline="true"> <popover-actor-card :actor="event.attributedTo" slot="group" :inline="true">
<router-link <router-link
:to="{ :to="{
@ -338,11 +329,8 @@
:endsOn="event.endsOn" :endsOn="event.endsOn"
/> />
</event-metadata-block> </event-metadata-block>
<event-metadata-block :title="$t('Contact')"> <event-metadata-block :title="$tc('Contact', event.contacts.length)">
<popover-actor-card <popover-actor-card :actor="event.organizerActor" v-if="!event.attributedTo">
:actor="event.organizerActor"
v-if="!event.attributedTo || !event.options.hideOrganizerWhenGroupEvent"
>
<actor-card :actor="event.organizerActor" /> <actor-card :actor="event.organizerActor" />
</popover-actor-card> </popover-actor-card>
<router-link <router-link
@ -359,6 +347,14 @@
<actor-card :actor="event.attributedTo" /> <actor-card :actor="event.attributedTo" />
</popover-actor-card> </popover-actor-card>
</router-link> </router-link>
<popover-actor-card
:actor="contact"
v-for="contact in event.contacts"
:key="contact.id"
>
<actor-card :actor="contact" />
</popover-actor-card>
</event-metadata-block> </event-metadata-block>
<event-metadata-block <event-metadata-block
v-if="event.onlineAddress && urlToHostname(event.onlineAddress)" v-if="event.onlineAddress && urlToHostname(event.onlineAddress)"

View file

@ -8,7 +8,7 @@
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
>{{ group.preferredUsername }}</router-link >{{ group.name }}</router-link
> >
</li> </li>
<li class="is-active"> <li class="is-active">

View file

@ -51,7 +51,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator"; import { Component, Watch } from "vue-property-decorator";
import { Group, IPerson, usernameWithDomain, MemberRole } from "@/types/actor"; import { Group, IPerson, usernameWithDomain, MemberRole } from "@/types/actor";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor"; import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { CREATE_GROUP } from "@/graphql/group"; import { CREATE_GROUP } from "@/graphql/group";
@ -84,7 +84,7 @@ export default class CreateGroup extends mixins(IdentityEditionMixin) {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
async createGroup() { async createGroup(): Promise<void> {
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: CREATE_GROUP, mutation: CREATE_GROUP,
@ -125,6 +125,7 @@ export default class CreateGroup extends mixins(IdentityEditionMixin) {
} }
} }
// eslint-disable-next-line class-methods-use-this
get host(): string { get host(): string {
return window.location.hostname; return window.location.hostname;
} }
@ -152,11 +153,13 @@ export default class CreateGroup extends mixins(IdentityEditionMixin) {
if (this.bannerFile) { if (this.bannerFile) {
bannerObj = { bannerObj = {
banner: {
picture: { picture: {
name: this.bannerFile.name, name: this.bannerFile.name,
alt: `${this.group.preferredUsername}'s banner`, alt: `${this.group.preferredUsername}'s banner`,
file: this.bannerFile, file: this.bannerFile,
}, },
},
}; };
} }
@ -173,18 +176,7 @@ export default class CreateGroup extends mixins(IdentityEditionMixin) {
} }
private handleError(err: any) { private handleError(err: any) {
this.errors.push( this.errors.push(...err.graphQLErrors.map(({ message }: { message: string }) => message));
...err.graphQLErrors.map(({ message }: { message: string }) => this.convertMessage(message))
);
}
private convertMessage(message: string): string {
switch (message) {
case "A group with this name already exists":
return this.$t("A group with this name already exists") as string;
default:
return message;
}
} }
} }
</script> </script>

View file

@ -33,8 +33,8 @@
<header class="block-container presentation"> <header class="block-container presentation">
<div class="block-column media"> <div class="block-column media">
<div class="media-left"> <div class="media-left">
<figure class="image rounded is-128x128" v-if="group.avatar"> <figure class="image is-128x128" v-if="group.avatar">
<img :src="group.avatar.url" /> <img class="is-rounded" :src="group.avatar.url" alt="" />
</figure> </figure>
<b-icon v-else size="is-large" icon="account-group" /> <b-icon v-else size="is-large" icon="account-group" />
</div> </div>
@ -56,13 +56,6 @@
class="button is-outlined" class="button is-outlined"
>{{ $t("Group settings") }}</router-link >{{ $t("Group settings") }}</router-link
> >
<b-button
type="is-danger"
v-if="isCurrentActorAGroupMember"
outlined
@click="leaveGroup"
>{{ $t("Leave group") }}</b-button
>
</div> </div>
</div> </div>
</div> </div>
@ -114,6 +107,7 @@
>{{ $t("Show map") }}</span >{{ $t("Show map") }}</span
> >
</div> </div>
<img v-if="group.banner && group.banner.url" :src="group.banner.url" alt="" />
</header> </header>
</div> </div>
<div v-if="isCurrentActorAGroupMember" class="block-container"> <div v-if="isCurrentActorAGroupMember" class="block-container">
@ -186,50 +180,6 @@
> >
</template> </template>
</group-section> </group-section>
<!-- Todos -->
<group-section
:title="$t('Ongoing tasks')"
icon="checkbox-multiple-marked"
:route="{
name: RouteName.TODO_LISTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<template v-slot:default>
<div v-if="group.todoLists.elements.length > 0">
<div v-for="todoList in group.todoLists.elements" :key="todoList.id">
<router-link :to="{ name: RouteName.TODO_LIST, params: { id: todoList.id } }">
<h2 class="is-size-3">
{{
$tc("{title} ({count} todos)", todoList.todos.total, {
count: todoList.todos.total,
title: todoList.title,
})
}}
</h2>
</router-link>
<compact-todo
:todo="todo"
v-for="todo in todoList.todos.elements.slice(0, 3)"
:key="todo.id"
/>
</div>
</div>
<div v-else class="content has-text-grey has-text-centered">
<p>{{ $t("No ongoing todos") }}</p>
</div>
</template>
<template v-slot:create>
<router-link
:to="{
name: RouteName.TODO_LISTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Add a todo") }}</router-link
>
</template>
</group-section>
</div> </div>
<!-- Public things --> <!-- Public things -->
<div class="block-column"> <div class="block-column">
@ -349,7 +299,7 @@
import { Component, Prop, Vue, Watch } from "vue-property-decorator"; import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import EventCard from "@/components/Event/EventCard.vue"; import EventCard from "@/components/Event/EventCard.vue";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor"; import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { FETCH_GROUP, LEAVE_GROUP } from "@/graphql/group"; import { FETCH_GROUP } from "@/graphql/group";
import { import {
IActor, IActor,
IGroup, IGroup,
@ -369,7 +319,6 @@ import FolderItem from "@/components/Resource/FolderItem.vue";
import { Address } from "@/types/address.model"; import { Address } from "@/types/address.model";
import Invitations from "@/components/Group/Invitations.vue"; import Invitations from "@/components/Group/Invitations.vue";
import addMinutes from "date-fns/addMinutes"; import addMinutes from "date-fns/addMinutes";
import { Route } from "vue-router";
import GroupSection from "../../components/Group/GroupSection.vue"; import GroupSection from "../../components/Group/GroupSection.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
@ -451,16 +400,6 @@ export default class Group extends Vue {
} }
} }
async leaveGroup(): Promise<Route> {
await this.$apollo.mutate({
mutation: LEAVE_GROUP,
variables: {
groupId: this.group.id,
},
});
return this.$router.push({ name: RouteName.MY_GROUPS });
}
acceptInvitation(): void { acceptInvitation(): void {
if (this.groupMember) { if (this.groupMember) {
const index = this.person.memberships.elements.findIndex( const index = this.person.memberships.elements.findIndex(
@ -477,7 +416,7 @@ export default class Group extends Vue {
get groupTitle(): undefined | string { get groupTitle(): undefined | string {
if (!this.group) return undefined; if (!this.group) return undefined;
return this.group.preferredUsername; return this.group.name || this.group.preferredUsername;
} }
get groupSummary(): undefined | string { get groupSummary(): undefined | string {
@ -583,6 +522,8 @@ div.container {
&.presentation { &.presentation {
border: 2px solid $purple-2; border: 2px solid $purple-2;
padding: 10px 0; padding: 10px 0;
position: relative;
overflow: hidden;
h1 { h1 {
color: $purple-1; color: $purple-1;
@ -593,6 +534,20 @@ div.container {
.button.is-outlined { .button.is-outlined {
border-color: $purple-2; border-color: $purple-2;
} }
& > *:not(img) {
position: relative;
z-index: 2;
}
& > img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: auto;
opacity: 0.3;
}
} }
.members { .members {

View file

@ -1,66 +0,0 @@
<template>
<section class="container section">
<h1>{{ $t("Group List") }} ({{ groups.total }})</h1>
<b-loading :active.sync="$apollo.loading" />
<div class="columns">
<GroupMemberCard
v-for="group in groups.elements"
:key="group.uuid"
:group="group"
class="column is-one-quarter-desktop is-half-mobile"
/>
</div>
<router-link class="button" :to="{ name: RouteName.CREATE_GROUP }">{{
$t("Create group")
}}</router-link>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { LIST_GROUPS } from "@/graphql/group";
import { Group, IGroup } from "@/types/actor";
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
import { Paginate } from "@/types/paginate";
import RouteName from "../../router/name";
@Component({
apollo: {
groups: {
query: {
query: LIST_GROUPS,
fetchPolicy: "network-only",
},
},
},
components: {
GroupMemberCard,
},
})
export default class GroupList extends Vue {
groups!: Paginate<IGroup>;
loading = true;
RouteName = RouteName;
//
// usernameWithDomain(actor) {
// return actor.username + (actor.domain === null ? '' : `@${actor.domain}`);
// }
// viewActor(actor) {
// this.$router.push({
// name: RouteName.GROUP,
// params: { name: this.usernameWithDomain(actor) },
// });
// }
//
// joinGroup(group) {
// const router = this.$router;
// // FIXME: remove eventFetch
// // eventFetch(`/groups/${this.usernameWithDomain(group)}/join`, this.$store, { method: 'POST' })
// // .then(response => response.json())
// // .then(() => router.push({ name: 'Group', params: { name: this.usernameWithDomain(group) } }));
// }
}
</script>

View file

@ -210,14 +210,14 @@ export default class GroupMembers extends Vue {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
mounted() { mounted(): void {
const roleQuery = this.$route.query.role as string; const roleQuery = this.$route.query.role as string;
if (Object.values(MemberRole).includes(roleQuery as MemberRole)) { if (Object.values(MemberRole).includes(roleQuery as MemberRole)) {
this.roles = roleQuery as MemberRole; this.roles = roleQuery as MemberRole;
} }
} }
async inviteMember() { async inviteMember(): Promise<void> {
await this.$apollo.mutate<{ inviteMember: IMember }>({ await this.$apollo.mutate<{ inviteMember: IMember }>({
mutation: INVITE_MEMBER, mutation: INVITE_MEMBER,
variables: { variables: {
@ -253,7 +253,7 @@ export default class GroupMembers extends Vue {
} }
@Watch("page") @Watch("page")
loadMoreMembers() { loadMoreMembers(): void {
this.$apollo.queries.event.fetchMore({ this.$apollo.queries.event.fetchMore({
// New variables // New variables
variables: { variables: {
@ -279,7 +279,7 @@ export default class GroupMembers extends Vue {
}); });
} }
async removeMember(memberId: string) { async removeMember(memberId: string): Promise<void> {
await this.$apollo.mutate<{ removeMember: IMember }>({ await this.$apollo.mutate<{ removeMember: IMember }>({
mutation: REMOVE_MEMBER, mutation: REMOVE_MEMBER,
variables: { variables: {
@ -310,15 +310,15 @@ export default class GroupMembers extends Vue {
}); });
} }
promoteMember(memberId: string) { promoteMember(memberId: string): Promise<void> {
return this.updateMember(memberId, MemberRole.ADMINISTRATOR); return this.updateMember(memberId, MemberRole.ADMINISTRATOR);
} }
demoteMember(memberId: string) { demoteMember(memberId: string): Promise<void> {
return this.updateMember(memberId, MemberRole.MEMBER); return this.updateMember(memberId, MemberRole.MEMBER);
} }
async updateMember(memberId: string, role: MemberRole) { async updateMember(memberId: string, role: MemberRole): Promise<void> {
await this.$apollo.mutate<{ updateMember: IMember }>({ await this.$apollo.mutate<{ updateMember: IMember }>({
mutation: UPDATE_MEMBER, mutation: UPDATE_MEMBER,
variables: { variables: {

View file

@ -37,8 +37,23 @@
<b-input v-model="group.name" /> <b-input v-model="group.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" <editor mode="basic" v-model="group.summary" :maxSize="500"
/></b-field> /></b-field>
<b-field :label="$t('Avatar')">
<picture-upload
:textFallback="$t('Avatar')"
v-model="avatarFile"
:defaultImageSrc="group.avatar ? group.avatar.url : null"
/>
</b-field>
<b-field :label="$t('Banner')">
<picture-upload
:textFallback="$t('Banner')"
v-model="bannerFile"
:defaultImageSrc="group.banner ? group.banner.url : null"
/>
</b-field>
<p class="label">{{ $t("Group visibility") }}</p> <p class="label">{{ $t("Group visibility") }}</p>
<div class="field"> <div class="field">
<b-radio <b-radio
@ -106,6 +121,7 @@
import { Component, Vue } from "vue-property-decorator"; import { Component, Vue } 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 RouteName from "../../router/name"; import RouteName from "../../router/name";
import { FETCH_GROUP, UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group"; import { FETCH_GROUP, UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
import { IGroup, usernameWithDomain } from "../../types/actor"; import { IGroup, usernameWithDomain } from "../../types/actor";
@ -129,6 +145,7 @@ import { Group } from "../../types/actor/group.model";
}, },
components: { components: {
FullAddressAutoComplete, FullAddressAutoComplete,
PictureUpload,
editor: () => import("../../components/Editor.vue"), editor: () => import("../../components/Editor.vue"),
}, },
}) })
@ -141,6 +158,10 @@ export default class GroupSettings extends Vue {
newMemberUsername = ""; newMemberUsername = "";
avatarFile: File | null = null;
bannerFile: File | null = null;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
GroupVisibility = { GroupVisibility = {
@ -151,19 +172,12 @@ export default class GroupSettings extends Vue {
showCopiedTooltip = false; showCopiedTooltip = false;
async updateGroup(): Promise<void> { async updateGroup(): Promise<void> {
const variables = { ...this.group }; const variables = this.buildVariables();
// eslint-disable-next-line
// @ts-ignore
delete variables.__typename;
if (variables.physicalAddress) {
// eslint-disable-next-line
// @ts-ignore
delete variables.physicalAddress.__typename;
}
await this.$apollo.mutate<{ updateGroup: IGroup }>({ await this.$apollo.mutate<{ updateGroup: IGroup }>({
mutation: UPDATE_GROUP, mutation: UPDATE_GROUP,
variables, variables,
}); });
this.$notifier.success(this.$t("Group settings saved") as string);
} }
confirmDeleteGroup(): void { confirmDeleteGroup(): void {
@ -198,6 +212,52 @@ export default class GroupSettings extends Vue {
}, 2000); }, 2000);
} }
private buildVariables() {
let avatarObj = {};
let bannerObj = {};
const variables = { ...this.group };
// eslint-disable-next-line
// @ts-ignore
delete variables.__typename;
if (variables.physicalAddress) {
// eslint-disable-next-line
// @ts-ignore
delete variables.physicalAddress.__typename;
}
delete variables.avatar;
delete variables.banner;
if (this.avatarFile) {
avatarObj = {
avatar: {
picture: {
name: this.avatarFile.name,
alt: `${this.group.preferredUsername}'s avatar`,
file: this.avatarFile,
},
},
};
}
if (this.bannerFile) {
bannerObj = {
banner: {
picture: {
name: this.bannerFile.name,
alt: `${this.group.preferredUsername}'s banner`,
file: this.bannerFile,
},
},
};
}
return {
...variables,
...avatarObj,
...bannerObj,
};
}
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
get canShowCopyButton(): boolean { get canShowCopyButton(): boolean {
return window.isSecureContext; return window.isSecureContext;

View file

@ -8,7 +8,11 @@
) )
}} }}
</p> </p>
<router-link :to="{ name: RouteName.CREATE_GROUP }">{{ $t("Create group") }}</router-link> <div class="buttons">
<router-link class="button is-primary" :to="{ name: RouteName.CREATE_GROUP }">{{
$t("Create group")
}}</router-link>
</div>
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<invitations <invitations
:invitations="invitations" :invitations="invitations"
@ -16,7 +20,13 @@
@rejectInvitation="rejectInvitation" @rejectInvitation="rejectInvitation"
/> />
<section v-if="memberships && memberships.length > 0"> <section v-if="memberships && memberships.length > 0">
<GroupMemberCard v-for="member in memberships" :key="member.id" :member="member" /> <GroupMemberCard
class="group-member-card"
v-for="member in memberships"
:key="member.id"
:member="member"
@leave="leaveGroup(member.parent)"
/>
</section> </section>
<b-message v-if="$apollo.loading === false && memberships.length === 0" type="is-danger"> <b-message v-if="$apollo.loading === false && memberships.length === 0" type="is-danger">
{{ $t("No groups found") }} {{ $t("No groups found") }}
@ -27,10 +37,13 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor"; import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
import { LEAVE_GROUP } from "@/graphql/group";
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue"; import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
import Invitations from "@/components/Group/Invitations.vue"; import Invitations from "@/components/Group/Invitations.vue";
import { Paginate } from "@/types/paginate"; import { Paginate } from "@/types/paginate";
import { IMember, MemberRole, usernameWithDomain } from "@/types/actor"; import { IGroup, IMember, MemberRole, usernameWithDomain } from "@/types/actor";
import { Route } from "vue-router";
import { ApolloError } from "apollo-client";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
@Component({ @Component({
@ -64,14 +77,14 @@ export default class MyEvents extends Vue {
RouteName = RouteName; RouteName = RouteName;
acceptInvitation(member: IMember) { acceptInvitation(member: IMember): Promise<Route> {
return this.$router.push({ return this.$router.push({
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(member.parent) }, params: { preferredUsername: usernameWithDomain(member.parent) },
}); });
} }
rejectInvitation({ id: memberId }: { id: string }) { rejectInvitation({ id: memberId }: { id: string }): void {
const index = this.membershipsPages.elements.findIndex( const index = this.membershipsPages.elements.findIndex(
(membership) => membership.role === MemberRole.INVITED && membership.id === memberId (membership) => membership.role === MemberRole.INVITED && membership.id === memberId
); );
@ -81,14 +94,23 @@ export default class MyEvents extends Vue {
} }
} }
get invitations() { async leaveGroup(group: IGroup): Promise<void> {
await this.$apollo.mutate({
mutation: LEAVE_GROUP,
variables: {
groupId: group.id,
},
});
}
get invitations(): IMember[] {
if (!this.membershipsPages) return []; if (!this.membershipsPages) return [];
return this.membershipsPages.elements.filter( return this.membershipsPages.elements.filter(
(member: IMember) => member.role === MemberRole.INVITED (member: IMember) => member.role === MemberRole.INVITED
); );
} }
get memberships() { get memberships(): IMember[] {
if (!this.membershipsPages) return []; if (!this.membershipsPages) return [];
return this.membershipsPages.elements.filter( return this.membershipsPages.elements.filter(
(member: IMember) => ![MemberRole.INVITED, MemberRole.REJECTED].includes(member.role) (member: IMember) => ![MemberRole.INVITED, MemberRole.REJECTED].includes(member.role)
@ -114,4 +136,8 @@ section {
text-transform: capitalize; text-transform: capitalize;
} }
} }
.group-member-card {
margin-bottom: 1rem;
}
</style> </style>

View file

@ -23,10 +23,8 @@
</aside> </aside>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
import { Route } from "vue-router"; import { IGroup } from "@/types/actor";
import { IGroup, IPerson } from "@/types/actor";
import { FETCH_GROUP } from "@/graphql/group";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import SettingMenuSection from "../../components/Settings/SettingMenuSection.vue"; import SettingMenuSection from "../../components/Settings/SettingMenuSection.vue";
import SettingMenuItem from "../../components/Settings/SettingMenuItem.vue"; import SettingMenuItem from "../../components/Settings/SettingMenuItem.vue";

View file

@ -42,14 +42,11 @@
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Vue, Prop } from "vue-property-decorator";
import Editor from "@/components/Editor.vue"; import Editor from "@/components/Editor.vue";
import { GraphQLError } from "graphql"; import { GraphQLError } from "graphql";
import { FETCH_GROUP } from "@/graphql/group";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor"; import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { TAGS } from "../../graphql/tags"; import { FETCH_POST } from "../../graphql/post";
import { CONFIG } from "../../graphql/config";
import { FETCH_POST, CREATE_POST } from "../../graphql/post";
import { IPost, PostVisibility } from "../../types/post.model"; import { IPost } from "../../types/post.model";
import { IGroup, IMember, usernameWithDomain } from "../../types/actor"; import { IMember, 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";
@ -90,11 +87,11 @@ import Tag from "../../components/Tag.vue";
}, },
metaInfo() { metaInfo() {
return { return {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
title: this.post ? this.post.title : "", title: this.post ? this.post.title : "",
// all titles will be injected into this template // all titles will be injected into this template
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
titleTemplate: this.post ? "%s | Mobilizon" : "Mobilizon", titleTemplate: this.post ? "%s | Mobilizon" : "Mobilizon",
}; };
@ -116,7 +113,7 @@ export default class Post extends Vue {
return this.memberships.map(({ parent: { id } }) => id).includes(this.post.attributedTo.id); return this.memberships.map(({ parent: { id } }) => id).includes(this.post.attributedTo.id);
} }
async handleErrors(errors: GraphQLError[]) { async handleErrors(errors: GraphQLError[]): Promise<void> {
if (errors[0].message.includes("No such post")) { if (errors[0].message.includes("No such post")) {
await this.$router.push({ name: RouteName.PAGE_NOT_FOUND }); await this.$router.push({ name: RouteName.PAGE_NOT_FOUND });
} }

View file

@ -8,7 +8,7 @@
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(resource.actor) }, params: { preferredUsername: usernameWithDomain(resource.actor) },
}" }"
>{{ resource.actor.preferredUsername }}</router-link >{{ resource.actor.name }}</router-link
> >
</li> </li>
<li> <li>

View file

@ -177,6 +177,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue, Ref } from "vue-property-decorator"; import { Component, Vue, Ref } from "vue-property-decorator";
import { Route } from "vue-router";
import { CHANGE_EMAIL, CHANGE_PASSWORD, DELETE_ACCOUNT, LOGGED_USER } from "../../graphql/user"; import { CHANGE_EMAIL, CHANGE_PASSWORD, DELETE_ACCOUNT, LOGGED_USER } from "../../graphql/user";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { IUser, IAuthProvider } from "../../types/current-user.model"; import { IUser, IAuthProvider } from "../../types/current-user.model";
@ -210,7 +211,7 @@ export default class AccountSettings extends Vue {
RouteName = RouteName; RouteName = RouteName;
async resetEmailAction() { async resetEmailAction(): Promise<void> {
this.changeEmailErrors = []; this.changeEmailErrors = [];
try { try {
@ -234,7 +235,7 @@ export default class AccountSettings extends Vue {
} }
} }
async resetPasswordAction() { async resetPasswordAction(): Promise<void> {
this.changePasswordErrors = []; this.changePasswordErrors = [];
try { try {
@ -252,12 +253,12 @@ export default class AccountSettings extends Vue {
} }
} }
protected async openDeleteAccountModal() { protected openDeleteAccountModal(): void {
this.passwordForAccountDeletion = ""; this.passwordForAccountDeletion = "";
this.isDeleteAccountModalActive = true; this.isDeleteAccountModalActive = true;
} }
async deleteAccount() { async deleteAccount(): Promise<Route | void> {
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: DELETE_ACCOUNT, mutation: DELETE_ACCOUNT,
@ -275,19 +276,19 @@ export default class AccountSettings extends Vue {
return await this.$router.push({ name: RouteName.HOME }); return await this.$router.push({ name: RouteName.HOME });
} catch (err) { } catch (err) {
this.handleErrors("delete", err); return this.handleErrors("delete", err);
} }
} }
get canChangePassword() { get canChangePassword(): boolean {
return !this.loggedUser.provider; return !this.loggedUser.provider;
} }
get canChangeEmail() { get canChangeEmail(): boolean {
return !this.loggedUser.provider; return !this.loggedUser.provider;
} }
providerName(id: string) { static providerName(id: string): string {
if (SELECTED_PROVIDERS[id]) { if (SELECTED_PROVIDERS[id]) {
return SELECTED_PROVIDERS[id]; return SELECTED_PROVIDERS[id];
} }
@ -307,31 +308,17 @@ export default class AccountSettings extends Vue {
if (err.graphQLErrors !== undefined) { if (err.graphQLErrors !== undefined) {
err.graphQLErrors.forEach(({ message }: { message: string }) => { err.graphQLErrors.forEach(({ message }: { message: string }) => {
switch (type) { switch (type) {
case "email":
this.changeEmailErrors.push(this.convertMessage(message) as string);
break;
case "password": case "password":
this.changePasswordErrors.push(this.convertMessage(message) as string); this.changePasswordErrors.push(message);
break;
case "email":
default:
this.changeEmailErrors.push(message);
break; break;
} }
}); });
} }
} }
private convertMessage(message: string) {
switch (message) {
case "The password provided is invalid":
return this.$t("The password provided is invalid");
case "The new email must be different":
return this.$t("The new email must be different");
case "The new email doesn't seem to be valid":
return this.$t("The new email doesn't seem to be valid");
case "The current password is invalid":
return this.$t("The current password is invalid");
case "The new password must be different":
return this.$t("The new password must be different");
}
}
} }
</script> </script>
<style lang="scss"> <style lang="scss">

View file

@ -8,7 +8,7 @@
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: todo.todoList.actor.preferredUsername }, params: { preferredUsername: todo.todoList.actor.preferredUsername },
}" }"
>{{ todo.todoList.actor.preferredUsername }}</router-link >{{ todo.todoList.actor.name }}</router-link
> >
</li> </li>
<li> <li>

View file

@ -8,7 +8,7 @@
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: todoList.actor.preferredUsername }, params: { preferredUsername: todoList.actor.preferredUsername },
}" }"
>{{ todoList.actor.preferredUsername }}</router-link >{{ todoList.actor.name }}</router-link
> >
</li> </li>
<li> <li>

View file

@ -8,7 +8,7 @@
name: RouteName.GROUP, name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
>{{ group.preferredUsername }}</router-link >{{ group.name }}</router-link
> >
</li> </li>
<li class="is-active"> <li class="is-active">

View file

@ -30,33 +30,8 @@
}) })
}}</b-message }}</b-message
> >
<b-message title="Error" type="is-danger" v-for="error in errors" :key="error"> <b-message :title="$t('Error')" type="is-danger" v-for="error in errors" :key="error">
<span v-if="error === LoginError.USER_NOT_CONFIRMED"> {{ error }}
<span>
{{
$t(
"The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder."
)
}}
</span>
<i18n path="You may also ask to {resend_confirmation_email}.">
<router-link
slot="resend_confirmation_email"
:to="{ name: RouteName.RESEND_CONFIRMATION }"
>{{ $t("resend confirmation email") }}</router-link
>
</i18n>
</span>
<span v-if="error === LoginError.USER_EMAIL_PASSWORD_INVALID">{{
$t("Impossible to login, your email or password seems incorrect.")
}}</span>
<!-- TODO: Shouldn't we hide this information? -->
<span v-if="error === LoginError.USER_DOES_NOT_EXIST">{{
$t("No user account with this email was found. Maybe you made a typo?")
}}</span>
<span v-if="error === LoginError.USER_DISABLED">
{{ $t("This user has been disabled") }}
</span>
</b-message> </b-message>
<form @submit="loginAction"> <form @submit="loginAction">
<b-field :label="$t('Email')" label-for="email"> <b-field :label="$t('Email')" label-for="email">
@ -80,9 +55,10 @@
/> />
</b-field> </b-field>
<p class="control has-text-centered"> <p class="control has-text-centered" v-if="!submitted">
<button class="button is-primary is-large">{{ $t("Login") }}</button> <button class="button is-primary is-large">{{ $t("Login") }}</button>
</p> </p>
<b-loading :is-full-page="false" v-model="submitted" />
<div class="control" v-if="config && config.auth.oauthProviders.length > 0"> <div class="control" v-if="config && config.auth.oauthProviders.length > 0">
<auth-providers :oauthProviders="config.auth.oauthProviders" /> <auth-providers :oauthProviders="config.auth.oauthProviders" />
@ -121,6 +97,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { Route } from "vue-router";
import { LOGIN } from "../../graphql/auth"; import { LOGIN } from "../../graphql/auth";
import { validateEmailField, validateRequiredField } from "../../utils/validators"; import { validateEmailField, validateRequiredField } from "../../utils/validators";
import { initializeCurrentActor, NoIdentitiesException, saveUserData } from "../../utils/auth"; import { initializeCurrentActor, NoIdentitiesException, saveUserData } from "../../utils/auth";
@ -185,7 +162,9 @@ export default class Login extends Vue {
private redirect: string | null = null; private redirect: string | null = null;
mounted() { submitted = false;
mounted(): void {
this.credentials.email = this.email; this.credentials.email = this.email;
this.credentials.password = this.password; this.credentials.password = this.password;
@ -194,12 +173,16 @@ export default class Login extends Vue {
this.redirect = query.redirect as string; this.redirect = query.redirect as string;
} }
async loginAction(e: Event) { async loginAction(e: Event): Promise<Route | void> {
e.preventDefault(); e.preventDefault();
if (this.submitted) {
return;
}
this.errors = []; this.errors = [];
try { try {
this.submitted = true;
const { data } = await this.$apollo.mutate<{ login: ILogin }>({ const { data } = await this.$apollo.mutate<{ login: ILogin }>({
mutation: LOGIN, mutation: LOGIN,
variables: { variables: {
@ -242,6 +225,7 @@ export default class Login extends Vue {
window.localStorage.setItem("welcome-back", "yes"); window.localStorage.setItem("welcome-back", "yes");
return this.$router.push({ name: RouteName.HOME }); return this.$router.push({ name: RouteName.HOME });
} catch (err) { } catch (err) {
this.submitted = false;
console.error(err); console.error(err);
err.graphQLErrors.forEach(({ message }: { message: string }) => { err.graphQLErrors.forEach(({ message }: { message: string }) => {
this.errors.push(message); this.errors.push(message);

View file

@ -44,8 +44,8 @@
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<b-message type="is-warning" v-if="config.registrationsWhitelist"> <b-message type="is-warning" v-if="config.registrationsAllowlist">
{{ $t("Registrations are restricted by whitelisting.") }} {{ $t("Registrations are restricted by allowlisting.") }}
</b-message> </b-message>
<form v-on:submit.prevent="submit()"> <form v-on:submit.prevent="submit()">
<b-field <b-field

View file

@ -19,14 +19,7 @@
:key="error" :key="error"
@close="removeError(error)" @close="removeError(error)"
> >
<span v-if="error == ResetError.USER_IMPOSSIBLE_TO_RESET"> {{ error }}
{{
$t(
"You can't reset your password because you use a 3rd-party auth provider to login."
)
}}
</span>
<span v-else>{{ error }}</span>
</b-message> </b-message>
<form @submit="sendResetPasswordTokenAction" v-if="!validationSent"> <form @submit="sendResetPasswordTokenAction" v-if="!validationSent">
<b-field :label="$t('Email address')"> <b-field :label="$t('Email address')">
@ -59,7 +52,6 @@ import { Component, Prop, Vue } from "vue-property-decorator";
import { validateEmailField, validateRequiredField } from "../../utils/validators"; import { validateEmailField, validateRequiredField } from "../../utils/validators";
import { SEND_RESET_PASSWORD } from "../../graphql/auth"; import { SEND_RESET_PASSWORD } from "../../graphql/auth";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { ResetError } from "../../types/login-error-code.model";
@Component @Component
export default class SendPasswordReset extends Vue { export default class SendPasswordReset extends Vue {
@ -75,8 +67,6 @@ export default class SendPasswordReset extends Vue {
errors: string[] = []; errors: string[] = [];
ResetError = ResetError;
state = { state = {
email: { email: {
status: null, status: null,
@ -89,15 +79,15 @@ export default class SendPasswordReset extends Vue {
email: validateEmailField, email: validateEmailField,
}; };
mounted() { mounted(): void {
this.credentials.email = this.email; this.credentials.email = this.email;
} }
removeError(message: string) { removeError(message: string): void {
this.errors.splice(this.errors.indexOf(message)); this.errors.splice(this.errors.indexOf(message));
} }
async sendResetPasswordTokenAction(e: Event) { async sendResetPasswordTokenAction(e: Event): Promise<void> {
e.preventDefault(); e.preventDefault();
try { try {
@ -119,7 +109,7 @@ export default class SendPasswordReset extends Vue {
} }
} }
resetState() { resetState(): void {
this.state = { this.state = {
email: { email: {
status: null, status: null,

View file

@ -19,7 +19,7 @@ import * as AbsintheSocket from "@absinthe/socket";
import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link"; import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link";
import { getMainDefinition } from "apollo-utilities"; import { getMainDefinition } from "apollo-utilities";
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from "./api/_entrypoint"; import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from "./api/_entrypoint";
import { computeErrorMessage, fragmentMatcher, refreshAccessToken } from "./apollo/utils"; import { fragmentMatcher, refreshAccessToken } from "./apollo/utils";
// Install the vue plugin // Install the vue plugin
Vue.use(VueApollo); Vue.use(VueApollo);
@ -104,24 +104,18 @@ const errorLink = onError(({ graphQLErrors, networkError, forward, operation })
} }
if (graphQLErrors) { if (graphQLErrors) {
const messages: Set<string> = new Set(); graphQLErrors.map(({ message, locations, path }) =>
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
graphQLErrors.forEach(({ message, locations, path }) => { );
const computedMessage = computeErrorMessage(message);
if (computedMessage) {
console.log("computed message", computedMessage);
messages.add(computedMessage);
}
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`);
});
} }
if (networkError) { if (networkError) {
console.log(`[Network error]: ${networkError}`); console.log(`[Network error]: ${networkError}`);
const computedMessage = computeErrorMessage(networkError); Snackbar.open({
if (computedMessage) { message: "Please refresh the page and retry.",
Snackbar.open({ message: computedMessage, type: "is-danger", position: "is-bottom" }); type: "is-danger",
} position: "is-bottom",
});
} }
}); });

View file

@ -196,8 +196,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end end
) )
args = Map.put(args, :options, options) args
|> Map.put(:options, options)
Map.update(args, :tags, [], &ConverterUtils.fetch_tags/1) |> Map.update(:tags, [], &ConverterUtils.fetch_tags/1)
|> Map.update(:contacts, [], &ConverterUtils.fetch_actors/1)
end end
end end

View file

@ -3,8 +3,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
Various utils for converters. Various utils for converters.
""" """
alias Mobilizon.{Actors, Events}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.Tag alias Mobilizon.Events.Tag
alias Mobilizon.Mention alias Mobilizon.Mention
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
@ -42,6 +42,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
address address
end end
def fetch_actors(actors) when is_list(actors) do
Logger.debug("fetching contacts")
actors |> Enum.map(& &1.id) |> Enum.filter(& &1) |> Enum.map(&Actors.get_actor/1)
end
def fetch_actors(_), do: []
@spec build_tags([Tag.t()]) :: [map()] @spec build_tags([Tag.t()]) :: [map()]
def build_tags(tags) do def build_tags(tags) do
Enum.map(tags, fn %Tag{} = tag -> Enum.map(tags, fn %Tag{} = tag ->

View file

@ -28,7 +28,7 @@ defmodule Mobilizon.GraphQL.API.Groups do
{:error, "A group with this name already exists"} {:error, "A group with this name already exists"}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, "Profile is not owned by authenticated user"}
end end
end end
@ -44,7 +44,7 @@ defmodule Mobilizon.GraphQL.API.Groups do
{:error, "A group with this name already exists"} {:error, "A group with this name already exists"}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, "Profile is not owned by authenticated user"}
end end
end end
end end

View file

@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Refresher alias Mobilizon.Federation.ActivityPub.Refresher
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
require Logger require Logger
@ -20,10 +21,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
{:ok, actor} {:ok, actor}
%Actor{} -> %Actor{} ->
{:error, "Only remote actors may be refreshed"} {:error, dgettext("errors", "Only remote profiles may be refreshed")}
_ -> _ ->
{:error, "No actor found with this ID"} {:error, dgettext("errors", "No profile found with this ID")}
end end
end end
@ -50,22 +51,22 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
{:ok, actor} {:ok, actor}
%Actor{domain: nil} -> %Actor{domain: nil} ->
{:error, "No remote profile found with this ID"} {:error, dgettext("errors", "No remote profile found with this ID")}
end end
else else
{:moderator_actor, nil} -> {:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"} {:error, dgettext("errors", "No profile found for the moderator user")}
%Actor{suspended: true} -> %Actor{suspended: true} ->
{:error, "Actor already suspended"} {:error, dgettext("errors", "Profile already suspended")}
{:error, _} -> {:error, _} ->
{:error, "Error while performing background task"} {:error, dgettext("errors", "Error while performing background task")}
end end
end end
def suspend_profile(_parent, _args, _resolution) do def suspend_profile(_parent, _args, _resolution) do
{:error, "Only moderators and administrators can suspend a profile"} {:error, dgettext("errors", "Only moderators and administrators can suspend a profile")}
end end
def unsuspend_profile(_parent, %{id: id}, %{ def unsuspend_profile(_parent, %{id: id}, %{
@ -84,18 +85,18 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
{:ok, actor} {:ok, actor}
else else
{:moderator_actor, nil} -> {:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"} {:error, dgettext("errors", "No profile found for the moderator user")}
nil -> nil ->
{:error, "No remote profile found with this ID"} {:error, dgettext("errors", "No remote profile found with this ID")}
{:error, _} -> {:error, _} ->
{:error, "Error while performing background task"} {:error, dgettext("errors", "Error while performing background task")}
end end
end end
def unsuspend_profile(_parent, _args, _resolution) do def unsuspend_profile(_parent, _args, _resolution) do
{:error, "Only moderators and administrators can unsuspend a profile"} {:error, dgettext("errors", "Only moderators and administrators can unsuspend a profile")}
end end
@spec refresh_if_remote(Actor.t()) :: {:ok, Actor.t()} @spec refresh_if_remote(Actor.t()) :: {:ok, Actor.t()}

View file

@ -17,6 +17,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
alias Mobilizon.Service.Statistics alias Mobilizon.Service.Statistics
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
require Logger require Logger
def list_action_logs( def list_action_logs(
@ -47,7 +48,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
end end
def list_action_logs(_parent, _args, _resolution) do def list_action_logs(_parent, _args, _resolution) do
{:error, "You need to be logged-in and a moderator to list action logs"} {:error, dgettext("errors", "You need to be logged-in and a moderator to list action logs")}
end end
defp transform_action_log( defp transform_action_log(
@ -174,7 +175,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
end end
def get_dashboard(_parent, _args, _resolution) do def get_dashboard(_parent, _args, _resolution) do
{:error, "You need to be logged-in and an administrator to access dashboard statistics"} {:error,
dgettext(
"errors",
"You need to be logged-in and an administrator to access dashboard statistics"
)}
end end
def get_settings(_parent, _args, %{ def get_settings(_parent, _args, %{
@ -185,7 +190,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
end end
def get_settings(_parent, _args, _resolution) do def get_settings(_parent, _args, _resolution) do
{:error, "You need to be logged-in and an administrator to access admin settings"} {:error,
dgettext("errors", "You need to be logged-in and an administrator to access admin settings")}
end end
def save_settings(_parent, args, %{ def save_settings(_parent, args, %{
@ -212,7 +218,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
end end
def save_settings(_parent, _args, _resolution) do def save_settings(_parent, _args, _resolution) do
{:error, "You need to be logged-in and an administrator to save admin settings"} {:error,
dgettext("errors", "You need to be logged-in and an administrator to save admin settings")}
end end
def list_relay_followers( def list_relay_followers(

View file

@ -8,6 +8,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
alias Mobilizon.Discussions.Comment, as: CommentModel alias Mobilizon.Discussions.Comment, as: CommentModel
alias Mobilizon.Users alias Mobilizon.Users
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
alias Mobilizon.GraphQL.API.Comments alias Mobilizon.GraphQL.API.Comments
@ -32,12 +33,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
{:ok, comment} {:ok, comment}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
def create_comment(_parent, _args, _context) do def create_comment(_parent, _args, _context) do
{:error, "You are not allowed to create a comment if not connected"} {:error, dgettext("errors", "You are not allowed to create a comment if not connected")}
end end
def update_comment( def update_comment(
@ -59,7 +60,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
end end
def edit_comment(_parent, _args, _context) do def edit_comment(_parent, _args, _context) do
{:error, "You are not allowed to update a comment if not connected"} {:error, dgettext("errors", "You are not allowed to update a comment if not connected")}
end end
def delete_comment( def delete_comment(
@ -87,19 +88,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
end end
true -> true ->
{:error, "You cannot delete this comment"} {:error, dgettext("errors", "You cannot delete this comment")}
end end
else else
%CommentModel{deleted_at: deleted_at} when not is_nil(deleted_at) -> %CommentModel{deleted_at: deleted_at} when not is_nil(deleted_at) ->
{:error, "Comment is already deleted"} {:error, dgettext("errors", "Comment is already deleted")}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
def delete_comment(_parent, _args, %{}) do def delete_comment(_parent, _args, %{}) do
{:error, "You are not allowed to delete a comment if not connected"} {:error, dgettext("errors", "You are not allowed to delete a comment if not connected")}
end end
defp do_delete_comment(%CommentModel{} = comment, %Actor{} = actor) do defp do_delete_comment(%CommentModel{} = comment, %Actor{} = actor) do

View file

@ -70,7 +70,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
%{ %{
name: Config.instance_name(), name: Config.instance_name(),
registrations_open: Config.instance_registrations_open?(), registrations_open: Config.instance_registrations_open?(),
registrations_whitelist: Config.instance_registrations_whitelist?(), registrations_allowlist: Config.instance_registrations_allowlist?(),
contact: Config.contact(), contact: Config.contact(),
demo_mode: Config.instance_demo_mode?(), demo_mode: Config.instance_demo_mode?(),
description: Config.instance_description(), description: Config.instance_description(),

View file

@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
def find_discussions_for_actor( def find_discussions_for_actor(
%Actor{id: group_id}, %Actor{id: group_id},
@ -57,12 +58,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do {:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do
{:ok, discussion} {:ok, discussion}
else else
nil -> {:error, "No such discussion"} nil -> {:error, dgettext("errors", "Discussion not found")}
end end
end end
def get_discussion(_parent, _args, _resolution), def get_discussion(_parent, _args, _resolution),
do: {:error, "You need to be logged-in to access discussions"} do: {:error, dgettext("errors", "You need to be logged-in to access discussions")}
def get_comments_for_discussion( def get_comments_for_discussion(
%Discussion{id: discussion_id}, %Discussion{id: discussion_id},
@ -177,10 +178,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
{:ok, discussion} {:ok, discussion}
else else
{:no_discussion, _} -> {:no_discussion, _} ->
{:error, "No discussion with ID #{discussion_id}"} {:error, dgettext("errors", "No discussion with ID %{id}", id: discussion_id)}
{:member, _} -> {:member, _} ->
{:error, "You are not a member of the group the discussion belongs to"} {:error,
dgettext("errors", "You are not a member of the group the discussion belongs to")}
end end
end end
end end

View file

@ -13,6 +13,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
alias Mobilizon.GraphQL.Resolvers.Person alias Mobilizon.GraphQL.Resolvers.Person
alias Mobilizon.Federation.ActivityPub.Activity alias Mobilizon.Federation.ActivityPub.Activity
import Mobilizon.Web.Gettext
# We limit the max number of events that can be retrieved # We limit the max number of events that can be retrieved
@event_max_limit 100 @event_max_limit 100
@ -37,12 +38,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))} {:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
{:has_event, _} -> {:has_event, _} ->
{:error, "Event with UUID #{uuid} not found"} {:error, dgettext("errors", "Event with UUID %{uuid} not found", uuid: uuid)}
end end
end end
defp find_private_event(_parent, %{uuid: uuid}, _resolution) do defp find_private_event(_parent, %{uuid: uuid}, _resolution) do
{:error, "Event with UUID #{uuid} not found"} {:error, dgettext("errors", "Event with UUID %{uuid} not found", uuid: uuid)}
end end
def find_event(parent, %{uuid: uuid} = args, %{context: context} = resolution) do def find_event(parent, %{uuid: uuid} = args, %{context: context} = resolution) do
@ -56,7 +57,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
find_private_event(parent, args, resolution) find_private_event(parent, args, resolution)
{:access_valid, _} -> {:access_valid, _} ->
{:error, "Event with UUID #{uuid} not found"} {:error, dgettext("errors", "Event with UUID %{uuid} not found", uuid)}
end end
end end
@ -96,10 +97,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:ok, participants} {:ok, participants}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"} {:error, dgettext("errors", "Moderator profile is not owned by authenticated user")}
{:actor_approve_permission, _} -> {:actor_approve_permission, _} ->
{:error, "Provided moderator actor ID doesn't have permission on this event"} {:error,
dgettext("errors", "Provided moderator profile doesn't have permission on this event")}
end end
end end
@ -197,7 +199,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:ok, event} {:ok, event}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Organizer actor id is not owned by the user"} {:error, dgettext("errors", "Organizer profile is not owned by the user")}
{:error, _, %Ecto.Changeset{} = error, _} -> {:error, _, %Ecto.Changeset{} = error, _} ->
{:error, error} {:error, error}
@ -208,7 +210,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end end
def create_event(_parent, _args, _resolution) do def create_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create events"} {:error, dgettext("errors", "You need to be logged-in to create events")}
end end
@doc """ @doc """
@ -231,10 +233,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:ok, event} {:ok, event}
else else
{:error, :event_not_found} -> {:error, :event_not_found} ->
{:error, "Event not found"} {:error, dgettext("errors", "Event not found")}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "User doesn't own actor"} {:error, dgettext("errors", "User doesn't own profile")}
{:error, _, %Ecto.Changeset{} = error, _} -> {:error, _, %Ecto.Changeset{} = error, _} ->
{:error, error} {:error, error}
@ -242,7 +244,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end end
def update_event(_parent, _args, _resolution) do def update_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update an event"} {:error, dgettext("errors", "You need to be logged-in to update an event")}
end end
@doc """ @doc """
@ -269,19 +271,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end end
true -> true ->
{:error, "You cannot delete this event"} {:error, dgettext("errors", "You cannot delete this event")}
end end
else else
{:error, :event_not_found} -> {:error, :event_not_found} ->
{:error, "Event not found"} {:error, dgettext("errors", "Event not found")}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
def delete_event(_parent, _args, _resolution) do def delete_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete an event"} {:error, dgettext("errors", "You need to be logged-in to delete an event")}
end end
defp do_delete_event(%Event{} = event, %Actor{} = actor, federate \\ true) defp do_delete_event(%Event{} = event, %Actor{} = actor, federate \\ true)

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
alias Mobilizon.Events alias Mobilizon.Events
alias Mobilizon.Events.FeedToken alias Mobilizon.Events.FeedToken
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
require Logger require Logger
@ -24,7 +25,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
{:ok, feed_token} {:ok, feed_token}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
@ -40,7 +41,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
@spec create_feed_token(any, map, map) :: {:error, String.t()} @spec create_feed_token(any, map, map) :: {:error, String.t()}
def create_feed_token(_parent, _args, %{}) do def create_feed_token(_parent, _args, %{}) do
{:error, "You are not allowed to create a feed token if not connected"} {:error, dgettext("errors", "You are not allowed to create a feed token if not connected")}
end end
@doc """ @doc """
@ -62,21 +63,21 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
{:ok, res} {:ok, res}
else else
{:error, nil} -> {:error, nil} ->
{:error, "No such feed token"} {:error, dgettext("errors", "No such feed token")}
:error -> :error ->
{:error, "Token is not a valid UUID"} {:error, dgettext("errors", "Token is not a valid UUID")}
{:no_token, _} -> {:no_token, _} ->
{:error, "Token does not exist"} {:error, dgettext("errors", "Token does not exist")}
{:token_from_user, false} -> {:token_from_user, false} ->
{:error, "You don't have permission to delete this token"} {:error, dgettext("errors", "You don't have permission to delete this token")}
end end
end end
@spec delete_feed_token(any, map, map) :: {:error, String.t()} @spec delete_feed_token(any, map, map) :: {:error, String.t()}
def delete_feed_token(_parent, _args, %{}) do def delete_feed_token(_parent, _args, %{}) do
{:error, "You are not allowed to delete a feed token if not connected"} {:error, dgettext("errors", "You are not allowed to delete a feed token if not connected")}
end end
end end

View file

@ -10,6 +10,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
alias Mobilizon.GraphQL.API alias Mobilizon.GraphQL.API
alias Mobilizon.GraphQL.Resolvers.Person alias Mobilizon.GraphQL.Resolvers.Person
alias Mobilizon.Users.User alias Mobilizon.Users.User
alias Mobilizon.Web.Upload
import Mobilizon.Web.Gettext
require Logger require Logger
@ -36,7 +38,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
find_group(parent, args, nil) find_group(parent, args, nil)
_ -> _ ->
{:error, "Group with name #{name} not found"} {:error, dgettext("errors", "Group with name %{name} not found", name: name)}
end end
end end
@ -50,7 +52,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:ok, actor} {:ok, actor}
else else
_ -> _ ->
{:error, "Group with name #{name} not found"} {:error, dgettext("errors", "Group with name %{name} not found", name: name)}
end end
end end
@ -64,7 +66,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:ok, actor} {:ok, actor}
else else
_ -> _ ->
{:error, "Group with ID #{id} not found"} {:error, dgettext("errors", "Group with ID %{id} not found", id: id)}
end end
end end
@ -92,7 +94,23 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
end end
def list_groups(_parent, _args, _resolution), def list_groups(_parent, _args, _resolution),
do: {:error, "You may not list groups unless moderator."} do: {:error, dgettext("errors", "You may not list groups unless moderator.")}
# TODO Move me to somewhere cleaner
defp save_attached_pictures(args) do
Enum.reduce([:avatar, :banner], args, fn key, args ->
if Map.has_key?(args, key) && !is_nil(args[key][:picture]) do
pic = args[key][:picture]
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
Upload.store(pic.file, type: key, description: pic.alt) do
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
end
else
args
end
end)
end
@doc """ @doc """
Create a new group. The creator is automatically added as admin Create a new group. The creator is automatically added as admin
@ -109,6 +127,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
with creator_actor_id <- Map.get(args, :creator_actor_id), with creator_actor_id <- Map.get(args, :creator_actor_id),
{:is_owned, %Actor{} = creator_actor} <- User.owns_actor(user, creator_actor_id), {:is_owned, %Actor{} = creator_actor} <- User.owns_actor(user, creator_actor_id),
args <- Map.put(args, :creator_actor, creator_actor), args <- Map.put(args, :creator_actor, creator_actor),
args <- save_attached_pictures(args),
{:ok, _activity, %Actor{type: :Group} = group} <- {:ok, _activity, %Actor{type: :Group} = group} <-
API.Groups.create_group(args) do API.Groups.create_group(args) do
{:ok, group} {:ok, group}
@ -117,7 +136,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:error, err} {:error, err}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Creator actor id is not owned by the current user"} {:error, dgettext("errors", "Creator profile is not owned by the current user")}
end end
end end
@ -139,6 +158,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
) do ) do
with %Actor{} = updater_actor <- Users.get_actor_for_user(user), with %Actor{} = updater_actor <- Users.get_actor_for_user(user),
args <- Map.put(args, :updater_actor, updater_actor), args <- Map.put(args, :updater_actor, updater_actor),
args <- save_attached_pictures(args),
{:ok, _activity, %Actor{type: :Group} = group} <- {:ok, _activity, %Actor{type: :Group} = group} <-
API.Groups.update_group(args) do API.Groups.update_group(args) do
{:ok, group} {:ok, group}
@ -147,12 +167,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:error, err} {:error, err}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Creator actor id is not owned by the current user"} {:error, dgettext("errors", "Creator profile is not owned by the current user")}
end end
end end
def update_group(_parent, _args, _resolution) do def update_group(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update a group"} {:error, dgettext("errors", "You need to be logged-in to update a group")}
end end
@doc """ @doc """
@ -175,18 +195,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:ok, %{id: group.id}} {:ok, %{id: group.id}}
else else
{:error, :group_not_found} -> {:error, :group_not_found} ->
{:error, "Group not found"} {:error, dgettext("errors", "Group not found")}
{:error, :member_not_found} -> {:error, :member_not_found} ->
{:error, "Actor id is not a member of this group"} {:error, dgettext("errors", "Current profile is not a member of this group")}
{:is_admin, false} -> {:is_admin, false} ->
{:error, "Actor id is not an administrator of the selected group"} {:error,
dgettext("errors", "Current profile is not an administrator of the selected group")}
end end
end end
def delete_group(_parent, _args, _resolution) do def delete_group(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete a group"} {:error, dgettext("errors", "You need to be logged-in to delete a group")}
end end
@doc """ @doc """
@ -219,21 +240,21 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:error, :group_not_found} -> {:error, :group_not_found} ->
{:error, "Group id not found"} {:error, dgettext("errors", "Group not found")}
{:is_able_to_join, false} -> {:is_able_to_join, false} ->
{:error, "You cannot join this group"} {:error, dgettext("errors", "You cannot join this group")}
{:ok, %Member{}} -> {:ok, %Member{}} ->
{:error, "You are already a member of this group"} {:error, dgettext("errors", "You are already a member of this group")}
end end
end end
def join_group(_parent, _args, _resolution) do def join_group(_parent, _args, _resolution) do
{:error, "You need to be logged-in to join a group"} {:error, dgettext("errors", "You need to be logged-in to join a group")}
end end
@doc """ @doc """
@ -254,18 +275,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:ok, member} {:ok, member}
else else
{:error, :member_not_found} -> {:error, :member_not_found} ->
{:error, "Member not found"} {:error, dgettext("errors", "Member not found")}
{:group, nil} -> {:group, nil} ->
{:error, "Group not found"} {:error, dgettext("errors", "Group not found")}
{:is_not_only_admin, false} -> {:is_not_only_admin, false} ->
{:error, "You can't leave this group because you are the only administrator"} {:error,
dgettext("errors", "You can't leave this group because you are the only administrator")}
end end
end end
def leave_group(_parent, _args, _resolution) do def leave_group(_parent, _args, _resolution) do
{:error, "You need to be logged-in to leave a group"} {:error, dgettext("errors", "You need to be logged-in to leave a group")}
end end
def find_events_for_group( def find_events_for_group(

View file

@ -10,6 +10,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
alias Mobilizon.Federation.ActivityPub.Refresher alias Mobilizon.Federation.ActivityPub.Refresher
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
@doc """ @doc """
Find members for group. Find members for group.
@ -73,22 +74,22 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
{:ok, member} {:ok, member}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:error, :group_not_found} -> {:error, :group_not_found} ->
{:error, "Group id not found"} {:error, dgettext("errors", "Group not found")}
{:target_actor_username, _} -> {:target_actor_username, _} ->
{:error, "Actor invited doesn't exist"} {:error, dgettext("errors", "Profile invited doesn't exist")}
{:has_rights_to_invite, {:error, :member_not_found}} -> {:has_rights_to_invite, {:error, :member_not_found}} ->
{:error, "You are not a member of this group"} {:error, dgettext("errors", "You are not a member of this group")}
{:has_rights_to_invite, _} -> {:has_rights_to_invite, _} ->
{:error, "You cannot invite to this group"} {:error, dgettext("errors", "You cannot invite to this group")}
{:ok, %Member{}} -> {:ok, %Member{}} ->
{:error, "Actor is already a member of this group"} {:error, dgettext("errors", "Profile is already a member of this group")}
end end
end end
@ -133,11 +134,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
{:ok, member} {:ok, member}
else else
{:has_rights_to_update_role, {:error, :member_not_found}} -> {:has_rights_to_update_role, {:error, :member_not_found}} ->
{:error, "You are not a moderator or admin for this group"} {:error, dgettext("errors", "You are not a moderator or admin for this group")}
{:is_only_admin, true} -> {:is_only_admin, true} ->
{:error, {:error,
"You can't set yourself to a lower member role for this group because you are the only administrator"} dgettext(
"errors",
"You can't set yourself to a lower member role for this group because you are the only administrator"
)}
end end
end end

View file

@ -11,6 +11,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
alias Mobilizon.Web.Email alias Mobilizon.Web.Email
alias Mobilizon.Web.Email.Checker alias Mobilizon.Web.Email.Checker
require Logger require Logger
import Mobilizon.Web.Gettext
@doc """ @doc """
Join an event for an regular actor Join an event for an regular actor
@ -25,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
do_actor_join_event(actor, event_id, args) do_actor_join_event(actor, event_id, args)
_ -> _ ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
@ -82,28 +83,29 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:error, err} {:error, err}
{:has_event, _} -> {:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"} {:error,
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))}
{:anonymous_participation_enabled, false} -> {:anonymous_participation_enabled, false} ->
{:error, "Anonymous participation is not enabled"} {:error, dgettext("errors", "Anonymous participation is not enabled")}
{:anonymous_actor_id, false} -> {:anonymous_actor_id, false} ->
{:error, "Actor ID provided is not the anonymous actor one"} {:error, dgettext("errors", "Profile ID provided is not the anonymous profile one")}
{:email_required, _} -> {:email_required, _} ->
{:error, "A valid email is required by your instance"} {:error, dgettext("errors", "A valid email is required by your instance")}
{:actor_not_found, _} -> {:actor_not_found, _} ->
Logger.error( Logger.error(
"The actor ID \"#{actor_id}\" provided by configuration doesn't match any actor in database" "The actor ID \"#{actor_id}\" provided by configuration doesn't match any actor in database"
) )
{:error, "Internal Error"} {:error, dgettext("errors", "Internal Error")}
end end
end end
def actor_join_event(_parent, _args, _resolution) do def actor_join_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to join an event"} {:error, dgettext("errors", "You need to be logged-in to join an event")}
end end
@spec do_actor_join_event(Actor.t(), integer | String.t(), map()) :: @spec do_actor_join_event(Actor.t(), integer | String.t(), map()) ::
@ -119,16 +121,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:ok, participant} {:ok, participant}
else else
{:maximum_attendee_capacity, _} -> {:maximum_attendee_capacity, _} ->
{:error, "The event has already reached its maximum capacity"} {:error, dgettext("errors", "The event has already reached its maximum capacity")}
{:has_event, _} -> {:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"} {:error,
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))}
{:error, :event_not_found} -> {:error, :event_not_found} ->
{:error, "Event id not found"} {:error, dgettext("errors", "Event id not found")}
{:ok, %Participant{}} -> {:ok, %Participant{}} ->
{:error, "You are already a participant of this event"} {:error, dgettext("errors", "You are already a participant of this event")}
end end
end end
@ -153,16 +156,21 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}, id: participant_id}} {:ok, %{event: %{id: event_id}, actor: %{id: actor_id}, id: participant_id}}
else else
{:has_event, _} -> {:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"} {:error,
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:only_organizer, true} -> {:only_organizer, true} ->
{:error, "You can't leave event because you're the only event creator participant"} {:error,
dgettext(
"errors",
"You can't leave event because you're the only event creator participant"
)}
{:error, :participant_not_found} -> {:error, :participant_not_found} ->
{:error, "Participant not found"} {:error, dgettext("errors", "Participant not found")}
end end
end end
@ -181,18 +189,22 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"} {:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:only_organizer, true} -> {:only_organizer, true} ->
{:error, "You can't leave event because you're the only event creator participant"} {:error,
dgettext(
"errors",
"You can't leave event because you're the only event creator participant"
)}
{:error, :participant_not_found} -> {:error, :participant_not_found} ->
{:error, "Participant not found"} {:error, dgettext("errors", "Participant not found")}
end end
end end
def actor_leave_event(_parent, _args, _resolution) do def actor_leave_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to leave an event"} {:error, dgettext("errors", "You need to be logged-in to leave an event")}
end end
def update_participation( def update_participation(
@ -219,19 +231,20 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:ok, participation} {:ok, participation}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"} {:error, dgettext("errors", "Moderator profile is not owned by authenticated user")}
{:has_participation, nil} -> {:has_participation, nil} ->
{:error, "Participant not found"} {:error, dgettext("errors", "Participant not found")}
{:actor_approve_permission, _} -> {:actor_approve_permission, _} ->
{:error, "Provided moderator actor ID doesn't have permission on this event"} {:error,
dgettext("errors", "Provided moderator profile doesn't have permission on this event")}
{:same_role, true} -> {:same_role, true} ->
{:error, "Participant already has role #{new_role}"} {:error, dgettext("errors", "Participant already has role %{role}", role: new_role)}
{:error, :participant_not_found} -> {:error, :participant_not_found} ->
{:error, "Participant not found"} {:error, dgettext("errors", "Participant not found")}
end end
end end
@ -251,7 +264,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:ok, participant} {:ok, participant}
else else
{:has_participant, _} -> {:has_participant, _} ->
{:error, "This token is invalid"} {:error, dgettext("errors", "This token is invalid")}
end end
end end

View file

@ -12,6 +12,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users alias Mobilizon.Users
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
@ -27,7 +28,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, actor} {:ok, actor}
else else
_ -> _ ->
{:error, "Person with ID #{id} not found"} {:error, dgettext("errors", "Person with ID %{id} not found", id: id)}
end end
end end
@ -41,7 +42,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, actor} {:ok, actor}
else else
_ -> _ ->
{:error, "Person with username #{preferred_username} not found"} {:error,
dgettext("errors", "Person with username %{username} not found",
username: preferred_username
)}
end end
end end
@ -66,7 +70,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
end end
def list_persons(_parent, _args, _resolution) do def list_persons(_parent, _args, _resolution) do
{:error, "You need to be logged-in and a moderator to list persons"} {:error, dgettext("errors", "You need to be logged-in and a moderator to list persons")}
end end
@doc """ @doc """
@ -77,7 +81,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
end end
def get_current_person(_parent, _args, _resolution) do def get_current_person(_parent, _args, _resolution) do
{:error, "You need to be logged-in to view current person"} {:error, dgettext("errors", "You need to be logged-in to view current person")}
end end
@doc """ @doc """
@ -88,7 +92,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
end end
def identities(_parent, _args, _resolution) do def identities(_parent, _args, _resolution) do
{:error, "You need to be logged-in to view your list of identities"} {:error, dgettext("errors", "You need to be logged-in to view your list of identities")}
end end
@doc """ @doc """
@ -111,7 +115,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
This function is used to create more identities from an existing user This function is used to create more identities from an existing user
""" """
def create_person(_parent, _args, _resolution) do def create_person(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create a new identity"} {:error, dgettext("errors", "You need to be logged-in to create a new identity")}
end end
@doc """ @doc """
@ -132,15 +136,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, actor} {:ok, actor}
else else
{:find_actor, nil} -> {:find_actor, nil} ->
{:error, "Actor not found"} {:error, dgettext("errors", "Profile not found")}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
def update_person(_parent, _args, _resolution) do def update_person(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update an identity"} {:error, dgettext("errors", "You need to be logged-in to update an identity")}
end end
@doc """ @doc """
@ -160,21 +164,21 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, actor} {:ok, actor}
else else
{:find_actor, nil} -> {:find_actor, nil} ->
{:error, "Actor not found"} {:error, dgettext("errors", "Profile not found")}
{:last_identity, true} -> {:last_identity, true} ->
{:error, "Cannot remove the last identity of a user"} {:error, dgettext("errors", "Cannot remove the last identity of a user")}
{:last_admin, true} -> {:last_admin, true} ->
{:error, "Cannot remove the last administrator of a group"} {:error, dgettext("errors", "Cannot remove the last administrator of a group")}
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
def delete_person(_parent, _args, _resolution) do def delete_person(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete an identity"} {:error, dgettext("errors", "You need to be logged-in to delete an identity")}
end end
defp last_identity?(user) do defp last_identity?(user) do
@ -210,10 +214,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, new_person} {:ok, new_person}
else else
{:error, :user_not_found} -> {:error, :user_not_found} ->
{:error, "No user with this email was found"} {:error, dgettext("errors", "No user with this email was found")}
{:no_actor, _} -> {:no_actor, _} ->
{:error, "You already have a profile for this user"} {:error, dgettext("errors", "You already have a profile for this user")}
{:error, %Ecto.Changeset{} = e} -> {:error, %Ecto.Changeset{} = e} ->
{:error, e} {:error, e}
@ -234,7 +238,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, %Page{elements: [participant], total: 1}} {:ok, %Page{elements: [participant], total: 1}}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:no_participant, _} -> {:no_participant, _} ->
{:ok, %Page{elements: [], total: 0}} {:ok, %Page{elements: [], total: 0}}
@ -266,7 +270,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, page} {:ok, page}
else else
{:is_owned, false} -> {:is_owned, false} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
@ -279,7 +283,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, participations} {:ok, participations}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
end end
end end
@ -301,7 +305,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, nil} {:ok, nil}
_ -> _ ->
{:error, "User not found"} {:error, dgettext("errors", "User not found")}
end end
end end

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
alias Mobilizon.Media alias Mobilizon.Media
alias Mobilizon.Media.Picture alias Mobilizon.Media.Picture
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
@doc """ @doc """
Get picture for an event's pic Get picture for an event's pic
@ -41,7 +42,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
}} }}
_error -> _error ->
{:error, "Picture with ID #{picture_id} was not found"} {:error, dgettext("errors", "Picture with ID %{id} was not found", id: picture_id)}
end end
end end
@ -71,7 +72,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
}} }}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
error -> error ->
{:error, error} {:error, error}
@ -79,6 +80,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
end end
def upload_picture(_parent, _args, _resolution) do def upload_picture(_parent, _args, _resolution) do
{:error, "You need to login to upload a picture"} {:error, dgettext("errors", "You need to login to upload a picture")}
end end
end end

View file

@ -10,6 +10,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
alias Mobilizon.Posts.Post alias Mobilizon.Posts.Post
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
require Logger require Logger
@ -75,7 +76,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
{:ok, post} {:ok, post}
else else
{:member, false} -> get_post(parent, %{slug: slug}, nil) {:member, false} -> get_post(parent, %{slug: slug}, nil)
{:post, _} -> {:error, "No such post"} {:post, _} -> {:error, dgettext("errors", "No such post")}
end end
end end
@ -90,12 +91,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
{:ok, post} {:ok, post}
{:post, _} -> {:post, _} ->
{:error, "No such post"} {:error, dgettext("errors", "No such post")}
end end
end end
def get_post(_parent, _args, _resolution) do def get_post(_parent, _args, _resolution) do
{:error, "No such post"} {:error, dgettext("errors", "No such post")}
end end
def create_post( def create_post(
@ -120,16 +121,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
) do ) do
{:ok, post} {:ok, post}
else else
{:own_check, _} ->
{:error, "Parent post doesn't match this group"}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
def create_post(_parent, _args, _resolution) do def create_post(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create posts"} {:error, dgettext("errors", "You need to be logged-in to create posts")}
end end
def update_post( def update_post(
@ -151,18 +149,18 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
{:ok, post} {:ok, post}
else else
{:uuid, :error} -> {:uuid, :error} ->
{:error, "Post ID is not a valid ID"} {:error, dgettext("errors", "Post ID is not a valid ID")}
{:post, _} -> {:post, _} ->
{:error, "Post doesn't exist"} {:error, dgettext("errors", "Post doesn't exist")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
def update_post(_parent, _args, _resolution) do def update_post(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update posts"} {:error, dgettext("errors", "You need to be logged-in to update posts")}
end end
def delete_post( def delete_post(
@ -184,17 +182,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
{:ok, post} {:ok, post}
else else
{:uuid, :error} -> {:uuid, :error} ->
{:error, "Post ID is not a valid ID"} {:error, dgettext("errors", "Post ID is not a valid ID")}
{:post, _} -> {:post, _} ->
{:error, "Post doesn't exist"} {:error, dgettext("errors", "Post doesn't exist")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
def delete_post(_parent, _args, _resolution) do def delete_post(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete posts"} {:error, dgettext("errors", "You need to be logged-in to delete posts")}
end end
end end

View file

@ -11,6 +11,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
alias Mobilizon.Reports alias Mobilizon.Reports
alias Mobilizon.Reports.{Note, Report} alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
alias Mobilizon.GraphQL.API alias Mobilizon.GraphQL.API
@ -24,7 +25,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
end end
def list_reports(_parent, _args, _resolution) do def list_reports(_parent, _args, _resolution) do
{:error, "You need to be logged-in and a moderator to list reports"} {:error, dgettext("errors", "You need to be logged-in and a moderator to list reports")}
end end
def get_report(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}}) def get_report(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}})
@ -34,12 +35,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
{:ok, report} {:ok, report}
nil -> nil ->
{:error, "Report not found"} {:error, dgettext("errors", "Report not found")}
end end
end end
def get_report(_parent, _args, _resolution) do def get_report(_parent, _args, _resolution) do
{:error, "You need to be logged-in and a moderator to view a report"} {:error, dgettext("errors", "You need to be logged-in and a moderator to view a report")}
end end
@doc """ @doc """
@ -55,10 +56,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
{:ok, report} {:ok, report}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Reporter actor id is not owned by authenticated user"} {:error, dgettext("errors", "Reporter profile is not owned by authenticated user")}
_error -> _error ->
{:error, "Error while saving report"} {:error, dgettext("errors", "Error while saving report")}
end end
end end
@ -77,18 +78,18 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
{:ok, report} {:ok, report}
else else
{:anonymous_reporting_allowed, _} -> {:anonymous_reporting_allowed, _} ->
{:error, "You need to be logged-in to create reports"} {:error, dgettext("errors", "You need to be logged-in to create reports")}
{:wrong_id, _} -> {:wrong_id, _} ->
{:error, "Reporter ID is not the anonymous actor id"} {:error, dgettext("errors", "Reporter ID does not match the anonymous profile id")}
_error -> _error ->
{:error, "Error while saving report"} {:error, dgettext("errors", "Error while saving report")}
end end
end end
def create_report(_parent, _args, _resolution) do def create_report(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create reports"} {:error, dgettext("errors", "You need to be logged-in to create reports")}
end end
@doc """ @doc """
@ -106,15 +107,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
{:ok, report} {:ok, report}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
_error -> _error ->
{:error, "Error while updating report"} {:error, dgettext("errors", "Error while updating report")}
end end
end end
def update_report(_parent, _args, _resolution) do def update_report(_parent, _args, _resolution) do
{:error, "You need to be logged-in and a moderator to update a report"} {:error, dgettext("errors", "You need to be logged-in and a moderator to update a report")}
end end
def create_report_note( def create_report_note(

View file

@ -11,6 +11,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
alias Mobilizon.Service.RichMedia.Parser alias Mobilizon.Service.RichMedia.Parser
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
require Logger require Logger
@ -82,13 +83,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do {:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do
{:ok, resource} {:ok, resource}
else else
{:member, false} -> {:error, "Actor is not member of group"} {:member, false} -> {:error, dgettext("errors", "Profile is not member of group")}
{:resource, _} -> {:error, "No such resource"} {:resource, _} -> {:error, dgettext("errors", "No such resource")}
end end
end end
def get_resource(_parent, _args, _resolution) do def get_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to access resources"} {:error, dgettext("errors", "You need to be logged-in to access resources")}
end end
def create_resource( def create_resource(
@ -116,15 +117,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:ok, resource} {:ok, resource}
else else
{:own_check, _} -> {:own_check, _} ->
{:error, "Parent resource doesn't match this group"} {:error, dgettext("errors", "Parent resource doesn't belong to this group")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
def create_resource(_parent, _args, _resolution) do def create_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create resources"} {:error, dgettext("errors", "You need to be logged-in to create resources")}
end end
def update_resource( def update_resource(
@ -145,15 +146,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:ok, resource} {:ok, resource}
else else
{:resource, _} -> {:resource, _} ->
{:error, "Resource doesn't exist"} {:error, dgettext("errors", "Resource doesn't exist")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
def update_resource(_parent, _args, _resolution) do def update_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update resources"} {:error, dgettext("errors", "You need to be logged-in to update resources")}
end end
def delete_resource( def delete_resource(
@ -174,15 +175,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:ok, resource} {:ok, resource}
else else
{:resource, _} -> {:resource, _} ->
{:error, "Resource doesn't exist"} {:error, dgettext("errors", "Resource doesn't exist")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
def delete_resource(_parent, _args, _resolution) do def delete_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete resources"} {:error, dgettext("errors", "You need to be logged-in to delete resources")}
end end
def preview_resource_link( def preview_resource_link(
@ -200,7 +201,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
end end
def preview_resource_link(_parent, _args, _resolution) do def preview_resource_link(_parent, _args, _resolution) do
{:error, "You need to be logged-in to view a resource preview"} {:error, dgettext("errors", "You need to be logged-in to view a resource preview")}
end end
@spec get_eventual_parent(map()) :: Resource.t() | nil @spec get_eventual_parent(map()) :: Resource.t() | nil

View file

@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Todos.{Todo, TodoList} alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
require Logger require Logger
@ -53,10 +54,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:ok, page} {:ok, page}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"} {:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
@ -74,13 +75,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:ok, todo} {:ok, todo}
else else
{:todo, nil} -> {:todo, nil} ->
{:error, "Todo list doesn't exist"} {:error, dgettext("errors", "Todo list doesn't exist")}
{:actor, nil} -> {:actor, nil} ->
{:error, "No actor found for user"} {:error, dgettext("errors", "No profile found for user")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
@ -98,7 +99,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:ok, todo_list} {:ok, todo_list}
else else
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
@ -118,10 +119,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# {:ok, todo} # {:ok, todo}
# else # else
# {:todo_list, _} -> # {:todo_list, _} ->
# {:error, "TodoList doesn't exist"} # {:error, "Todo list doesn't exist"}
# {:member, _} -> # {:member, _} ->
# {:error, "Actor id is not member of group"} # {:error, "Profile is not member of group"}
# end # end
# end # end
@ -141,10 +142,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# {:ok, todo} # {:ok, todo}
# else # else
# {:todo_list, _} -> # {:todo_list, _} ->
# {:error, "TodoList doesn't exist"} # {:error, "Todo list doesn't exist"}
# {:member, _} -> # {:member, _} ->
# {:error, "Actor id is not member of group"} # {:error, "Profile is not member of group"}
# end # end
# end # end
@ -164,13 +165,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:ok, todo} {:ok, todo}
else else
{:todo, nil} -> {:todo, nil} ->
{:error, "Todo doesn't exist"} {:error, dgettext("errors", "Todo doesn't exist")}
{:actor, nil} -> {:actor, nil} ->
{:error, "No actor found for user"} {:error, dgettext("errors", "No profile found for user")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
@ -190,10 +191,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:ok, todo} {:ok, todo}
else else
{:todo_list, _} -> {:todo_list, _} ->
{:error, "TodoList doesn't exist"} {:error, dgettext("errors", "Todo list doesn't exist")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
@ -215,13 +216,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:ok, todo} {:ok, todo}
else else
{:todo_list, _} -> {:todo_list, _} ->
{:error, "TodoList doesn't exist"} {:error, dgettext("errors", "Todo list doesn't exist")}
{:todo, _} -> {:todo, _} ->
{:error, "Todo doesn't exist"} {:error, dgettext("errors", "Todo doesn't exist")}
{:member, _} -> {:member, _} ->
{:error, "Actor id is not member of group"} {:error, dgettext("errors", "Profile is not member of group")}
end end
end end
@ -243,13 +244,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# {:ok, todo} # {:ok, todo}
# else # else
# {:todo_list, _} -> # {:todo_list, _} ->
# {:error, "TodoList doesn't exist"} # {:error, "Todo list doesn't exist"}
# {:todo, _} -> # {:todo, _} ->
# {:error, "Todo doesn't exist"} # {:error, "Todo doesn't exist"}
# {:member, _} -> # {:member, _} ->
# {:error, "Actor id is not member of group"} # {:error, "Profile is not member of group"}
# end # end
# end # end
end end

View file

@ -15,6 +15,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.Users.{Setting, User} alias Mobilizon.Users.{Setting, User}
alias Mobilizon.Web.{Auth, Email} alias Mobilizon.Web.{Auth, Email}
import Mobilizon.Web.Gettext
require Logger require Logger
@ -54,7 +55,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end end
def list_users(_parent, _args, _resolution) do def list_users(_parent, _args, _resolution) do
{:error, "You need to have admin access to list users"} {:error, dgettext("errors", "You need to have admin access to list users")}
end end
@doc """ @doc """
@ -72,13 +73,17 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, user_and_tokens} {:ok, user_and_tokens}
else else
{:error, :user_not_found} -> {:error, :user_not_found} ->
{:error, "No user with this email was found"} {:error, dgettext("errors", "No user with this email was found")}
{:error, :disabled_user} -> {:error, :disabled_user} ->
{:error, "This user has been disabled"} {:error, dgettext("errors", "This user has been disabled")}
{:error, _error} -> {:error, _error} ->
{:error, "Impossible to authenticate, either your email or password are invalid."} {:error,
dgettext(
"errors",
"Impossible to authenticate, either your email or password are invalid."
)}
end end
end end
@ -95,12 +100,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
else else
{:error, message} -> {:error, message} ->
Logger.debug("Cannot refresh user token: #{inspect(message)}") Logger.debug("Cannot refresh user token: #{inspect(message)}")
{:error, "Cannot refresh the token"} {:error, dgettext("errors", "Cannot refresh the token")}
end end
end end
def refresh_token(_parent, _params, _context) do def refresh_token(_parent, _params, _context) do
{:error, "You need to have an existing token to get a refresh token"} {:error, dgettext("errors", "You need to have an existing token to get a refresh token")}
end end
@doc """ @doc """
@ -117,10 +122,10 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, user} {:ok, user}
else else
:registration_closed -> :registration_closed ->
{:error, "Registrations are not enabled"} {:error, dgettext("errors", "Registrations are not open")}
:not_whitelisted -> :not_allowlisted ->
{:error, "Your email is not on the whitelist"} {:error, dgettext("errors", "Your email is not on the allowlist")}
error -> error ->
error error
@ -133,22 +138,22 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
Config.instance_registrations_open?() -> Config.instance_registrations_open?() ->
:registration_ok :registration_ok
Config.instance_registrations_whitelist?() -> Config.instance_registrations_allowlist?() ->
check_white_listed_email?(email) check_allow_listed_email?(email)
true -> true ->
:registration_closed :registration_closed
end end
end end
@spec check_white_listed_email?(String.t()) :: :registration_ok | :not_whitelisted @spec check_allow_listed_email?(String.t()) :: :registration_ok | :not_allowlisted
defp check_white_listed_email?(email) do defp check_allow_listed_email?(email) do
[_, domain] = String.split(email, "@", parts: 2, trim: true) [_, domain] = String.split(email, "@", parts: 2, trim: true)
if domain in Config.instance_registrations_whitelist() or if domain in Config.instance_registrations_allowlist() or
email in Config.instance_registrations_whitelist(), email in Config.instance_registrations_allowlist(),
do: :registration_ok, do: :registration_ok,
else: :not_whitelisted else: :not_allowlisted
end end
@doc """ @doc """
@ -171,7 +176,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
Logger.info("Unable to validate user with token #{token}") Logger.info("Unable to validate user with token #{token}")
Logger.debug(inspect(error)) Logger.debug(inspect(error))
{:error, "Unable to validate user"} {:error, dgettext("errors", "Unable to validate user")}
end end
end end
@ -187,10 +192,10 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, email} {:ok, email}
else else
{:error, :user_not_found} -> {:error, :user_not_found} ->
{:error, "No user to validate with this email was found"} {:error, dgettext("errors", "No user to validate with this email was found")}
{:error, :email_too_soon} -> {:error, :email_too_soon} ->
{:error, "You requested again a confirmation email too soon"} {:error, dgettext("errors", "You requested again a confirmation email too soon")}
end end
end end
@ -207,14 +212,14 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, email} {:ok, email}
else else
{:can_reset_password, false} -> {:can_reset_password, false} ->
{:error, "This user can't reset their password"} {:error, dgettext("errors", "This user can't reset their password")}
{:error, :user_not_found} -> {:error, :user_not_found} ->
# TODO : implement rate limits for this endpoint # TODO : implement rate limits for this endpoint
{:error, "No user with this email was found"} {:error, dgettext("errors", "No user with this email was found")}
{:error, :email_too_soon} -> {:error, :email_too_soon} ->
{:error, "You requested again a confirmation email too soon"} {:error, dgettext("errors", "You requested again a confirmation email too soon")}
end end
end end
@ -322,19 +327,22 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, user} {:ok, user}
else else
{:current_password, _} -> {:current_password, _} ->
{:error, "The current password is invalid"} {:error, dgettext("errors", "The current password is invalid")}
{:same_password, true} -> {:same_password, true} ->
{:error, "The new password must be different"} {:error, dgettext("errors", "The new password must be different")}
{:error, %Ecto.Changeset{errors: [password: {"registration.error.password_too_short", _}]}} -> {:error, %Ecto.Changeset{errors: [password: {"registration.error.password_too_short", _}]}} ->
{:error, {:error,
"The password you have chosen is too short. Please make sure your password contains at least 6 characters."} dgettext(
"errors",
"The password you have chosen is too short. Please make sure your password contains at least 6 characters."
)}
end end
end end
def change_password(_parent, _args, _resolution) do def change_password(_parent, _args, _resolution) do
{:error, "You need to be logged-in to change your password"} {:error, dgettext("errors", "You need to be logged-in to change your password")}
end end
def change_email(_parent, %{email: new_email, password: password}, %{ def change_email(_parent, %{email: new_email, password: password}, %{
@ -365,18 +373,18 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, user} {:ok, user}
else else
{:current_password, _} -> {:current_password, _} ->
{:error, "The password provided is invalid"} {:error, dgettext("errors", "The password provided is invalid")}
{:same_email, true} -> {:same_email, true} ->
{:error, "The new email must be different"} {:error, dgettext("errors", "The new email must be different")}
{:email_valid, _} -> {:email_valid, _} ->
{:error, "The new email doesn't seem to be valid"} {:error, dgettext("errors", "The new email doesn't seem to be valid")}
end end
end end
def change_email(_parent, _args, _resolution) do def change_email(_parent, _args, _resolution) do
{:error, "You need to be logged-in to change your email"} {:error, dgettext("errors", "You need to be logged-in to change your email")}
end end
def validate_email(_parent, %{token: token}, _resolution) do def validate_email(_parent, %{token: token}, _resolution) do
@ -406,10 +414,10 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
Admin.log_action(moderator_actor, "delete", user) Admin.log_action(moderator_actor, "delete", user)
else else
{:moderator_actor, nil} -> {:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"} {:error, dgettext("errors", "No profile found for the moderator user")}
%User{disabled: true} -> %User{disabled: true} ->
{:error, "User already disabled"} {:error, dgettext("errors", "User already disabled")}
end end
end end
@ -428,15 +436,15 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
do_delete_account(user) do_delete_account(user)
{:confirmation_password, nil} -> {:confirmation_password, nil} ->
{:error, "The password provided is invalid"} {:error, dgettext("errors", "The password provided is invalid")}
{:current_password, _} -> {:current_password, _} ->
{:error, "The password provided is invalid"} {:error, dgettext("errors", "The password provided is invalid")}
end end
end end
def delete_account(_parent, _args, _resolution) do def delete_account(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete your account"} {:error, dgettext("errors", "You need to be logged-in to delete your account")}
end end
defp do_delete_account(%User{} = user, actor_performing \\ nil) do defp do_delete_account(%User{} = user, actor_performing \\ nil) do
@ -478,7 +486,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, settings} {:ok, settings}
else else
{:same_user, _} -> {:same_user, _} ->
{:error, "User requested is not logged-in"} {:error, dgettext("errors", "User requested is not logged-in")}
end end
end end
@ -503,7 +511,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:error, changeset} -> {:error, changeset} ->
Logger.debug(inspect(changeset)) Logger.debug(inspect(changeset))
{:error, "Error while saving user setting"} {:error, dgettext("errors", "Error while saving user settings")}
end end
end end

View file

@ -15,7 +15,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:contact, :string) field(:contact, :string)
field(:registrations_open, :boolean) field(:registrations_open, :boolean)
field(:registrations_whitelist, :boolean) field(:registrations_allowlist, :boolean)
field(:demo_mode, :boolean) field(:demo_mode, :boolean)
field(:country_code, :string) field(:country_code, :string)
field(:location, :lonlat) field(:location, :lonlat)

View file

@ -76,6 +76,8 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
resolve(&Event.list_participants_for_event/3) resolve(&Event.list_participants_for_event/3)
end end
field(:contacts, list_of(:actor), description: "The events contacts")
field(:related_events, list_of(:event), field(:related_events, list_of(:event),
resolve: &Event.list_related_events/3, resolve: &Event.list_related_events/3,
description: "Events related to this one" description: "Events related to this one"
@ -258,6 +260,10 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
) )
end end
input_object :contact do
field(:id, :string, description: "The Contact Actor ID")
end
object :event_queries do object :event_queries do
@desc "Get all events" @desc "Get all events"
field :events, list_of(:event) do field :events, list_of(:event) do
@ -303,6 +309,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
arg(:physical_address, :address_input) arg(:physical_address, :address_input)
arg(:options, :event_options_input) arg(:options, :event_options_input)
arg(:draft, :boolean, default_value: false) arg(:draft, :boolean, default_value: false)
arg(:contacts, list_of(:contact), default_value: [])
resolve(handle_errors(&Event.create_event/3)) resolve(handle_errors(&Event.create_event/3))
end end
@ -334,6 +341,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
arg(:physical_address, :address_input) arg(:physical_address, :address_input)
arg(:options, :event_options_input) arg(:options, :event_options_input)
arg(:draft, :boolean) arg(:draft, :boolean)
arg(:contacts, list_of(:contact), default_value: [])
resolve(handle_errors(&Event.update_event/3)) resolve(handle_errors(&Event.update_event/3))
end end

View file

@ -1579,7 +1579,8 @@ defmodule Mobilizon.Actors do
:comments, :comments,
:attributed_to, :attributed_to,
:tags, :tags,
:physical_address :physical_address,
:contacts
]) ])
ActivityPub.delete(event, actor, false) ActivityPub.delete(event, actor, false)

View file

@ -99,11 +99,11 @@ defmodule Mobilizon.Config do
) )
) )
@spec instance_registrations_whitelist :: list(String.t()) @spec instance_registrations_allowlist :: list(String.t())
def instance_registrations_whitelist, do: instance_config()[:registration_email_whitelist] def instance_registrations_allowlist, do: instance_config()[:registration_email_allowlist]
@spec instance_registrations_whitelist? :: boolean @spec instance_registrations_allowlist? :: boolean
def instance_registrations_whitelist?, do: length(instance_registrations_whitelist()) > 0 def instance_registrations_allowlist?, do: length(instance_registrations_allowlist()) > 0
@spec instance_demo_mode? :: boolean @spec instance_demo_mode? :: boolean
def instance_demo_mode?, do: to_boolean(instance_config()[:demo]) def instance_demo_mode?, do: to_boolean(instance_config()[:demo])

View file

@ -59,7 +59,8 @@ defmodule Mobilizon.Events.Event do
sessions: [Session.t()], sessions: [Session.t()],
mentions: [Mention.t()], mentions: [Mention.t()],
tags: [Tag.t()], tags: [Tag.t()],
participants: [Actor.t()] participants: [Actor.t()],
contacts: [Actor.t()]
} }
@update_required_attrs [:title, :begins_on, :organizer_actor_id] @update_required_attrs [:title, :begins_on, :organizer_actor_id]
@ -114,6 +115,7 @@ defmodule Mobilizon.Events.Event do
has_many(:sessions, Session) has_many(:sessions, Session)
has_many(:mentions, Mention) has_many(:mentions, Mention)
has_many(:comments, Comment) has_many(:comments, Comment)
many_to_many(:contacts, Actor, join_through: "event_contacts", on_replace: :delete)
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete) many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
many_to_many(:participants, Actor, join_through: Participant) many_to_many(:participants, Actor, join_through: Participant)
@ -147,6 +149,7 @@ defmodule Mobilizon.Events.Event do
defp common_changeset(%Changeset{} = changeset, attrs) do defp common_changeset(%Changeset{} = changeset, attrs) do
changeset changeset
|> cast_embed(:options) |> cast_embed(:options)
|> put_assoc(:contacts, Map.get(attrs, :contacts, []))
|> put_tags(attrs) |> put_tags(attrs)
|> put_address(attrs) |> put_address(attrs)
|> put_picture(attrs) |> put_picture(attrs)

View file

@ -83,7 +83,8 @@ defmodule Mobilizon.Events do
:comments, :comments,
:participants, :participants,
:physical_address, :physical_address,
:picture :picture,
:contacts
] ]
@doc """ @doc """
@ -1019,7 +1020,7 @@ defmodule Mobilizon.Events do
) do ) do
with {:update_event_participation_stats, true} <- with {:update_event_participation_stats, true} <-
{:update_event_participation_stats, update_event_participation_stats}, {:update_event_participation_stats, update_event_participation_stats},
{:ok, %Event{} = event} <- get_event(event_id), {:ok, %Event{} = event} <- get_event_with_preload(event_id),
%EventParticipantStats{} = participant_stats <- %EventParticipantStats{} = participant_stats <-
Map.get(event, :participant_stats), Map.get(event, :participant_stats),
%EventParticipantStats{} = participant_stats <- %EventParticipantStats{} = participant_stats <-

View file

@ -12,6 +12,7 @@ defmodule Mobilizon.Users.User do
alias Mobilizon.Events.FeedToken alias Mobilizon.Events.FeedToken
alias Mobilizon.Users.{Setting, UserRole} alias Mobilizon.Users.{Setting, UserRole}
alias Mobilizon.Web.Email.Checker alias Mobilizon.Web.Email.Checker
import Mobilizon.Web.Gettext
@type t :: %__MODULE__{ @type t :: %__MODULE__{
email: String.t(), email: String.t(),
@ -100,9 +101,13 @@ defmodule Mobilizon.Users.User do
user user
|> cast(attrs, @attrs) |> cast(attrs, @attrs)
|> validate_required(@required_attrs) |> validate_required(@required_attrs)
|> unique_constraint(:email, message: "This email is already used.") |> unique_constraint(:email, message: dgettext("errors", "This email is already used."))
|> Checker.validate_changeset() |> Checker.validate_changeset()
|> validate_length(:password, min: 6, max: 200, message: "The chosen password is too short.") |> validate_length(:password,
min: 6,
max: 200,
message: dgettext("errors", "The chosen password is too short.")
)
if Map.has_key?(attrs, :default_actor) do if Map.has_key?(attrs, :default_actor) do
put_assoc(changeset, :default_actor, attrs.default_actor) put_assoc(changeset, :default_actor, attrs.default_actor)
@ -129,7 +134,11 @@ defmodule Mobilizon.Users.User do
|> save_confirmation_token() |> save_confirmation_token()
|> unique_constraint( |> unique_constraint(
:confirmation_token, :confirmation_token,
message: "The registration token is already in use, this looks like an issue on our side." message:
dgettext(
"errors",
"The registration token is already in use, this looks like an issue on our side."
)
) )
end end

View file

@ -5,6 +5,8 @@ defmodule Mobilizon.Web.Endpoint do
use Phoenix.Endpoint, otp_app: :mobilizon use Phoenix.Endpoint, otp_app: :mobilizon
use Absinthe.Phoenix.Endpoint use Absinthe.Phoenix.Endpoint
plug(Mobilizon.Web.Plugs.SetLocalePlug)
# For e2e tests # For e2e tests
if Application.get_env(:mobilizon, :sql_sandbox) do if Application.get_env(:mobilizon, :sql_sandbox) do
plug(Phoenix.Ecto.SQL.Sandbox, plug(Phoenix.Ecto.SQL.Sandbox,

View file

@ -0,0 +1,67 @@
# Portions of this file are derived from Pleroma:
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# NOTE: this module is based on https://github.com/smeevil/set_locale
defmodule Mobilizon.Web.Plugs.SetLocalePlug do
@moduledoc """
Plug to set locale for Gettext
"""
import Plug.Conn, only: [get_req_header: 2, assign: 3]
def init(_), do: nil
def call(conn, _) do
locale = get_locale_from_header(conn) || Gettext.get_locale()
Gettext.put_locale(locale)
assign(conn, :locale, locale)
end
defp get_locale_from_header(conn) do
conn
|> extract_accept_language()
|> Enum.find(&supported_locale?/1)
end
defp extract_accept_language(conn) do
case get_req_header(conn, "accept-language") do
[value | _] ->
value
|> String.split(",")
|> Enum.map(&parse_language_option/1)
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
|> ensure_language_fallbacks()
_ ->
[]
end
end
defp supported_locale?(locale) do
Mobilizon.Web.Gettext
|> Gettext.known_locales()
|> Enum.member?(locale)
end
defp parse_language_option(string) do
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
quality =
case Float.parse(captures["quality"] || "1.0") do
{val, _} -> val
:error -> 1.0
end
%{tag: captures["tag"], quality: quality}
end
defp ensure_language_fallbacks(tags) do
Enum.flat_map(tags, fn tag ->
[language | _] = String.split(tag, "-")
if Enum.member?(tags, language), do: [tag], else: [tag, language]
end)
end
end

View file

@ -53,7 +53,7 @@ defmodule Mobilizon.Web.ReverseProxy do
* `inline_content_types`: * `inline_content_types`:
* `true` will not alter `content-disposition` (up to the upstream), * `true` will not alter `content-disposition` (up to the upstream),
* `false` will add `content-disposition: attachment` to any request, * `false` will add `content-disposition: attachment` to any request,
* a list of whitelisted content types * a list of allowlisted content types
* `keep_user_agent` will forward the client's user-agent to the upstream. * `keep_user_agent` will forward the client's user-agent to the upstream.
This may be useful if the upstream is doing content transformation This may be useful if the upstream is doing content transformation

View file

@ -117,3 +117,734 @@ msgstr ""
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:103
msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
msgid "Current profile is not an administrator of the selected group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:514
msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:69
msgid "Group with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:41 lib/graphql/resolvers/group.ex:55
msgid "Group with name %{name} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:83
msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
msgid "Member not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:58 lib/graphql/resolvers/actor.ex:88
#: lib/graphql/resolvers/user.ex:417
msgid "No profile found for the moderator user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:195
msgid "No user to validate with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:217 lib/graphql/resolvers/user.ex:76
#: lib/graphql/resolvers/user.ex:219
msgid "No user with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:36 lib/graphql/resolvers/comment.ex:98
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:142
#: lib/graphql/resolvers/person.ex:176 lib/graphql/resolvers/person.ex:241 lib/graphql/resolvers/person.ex:273
#: lib/graphql/resolvers/person.ex:286 lib/graphql/resolvers/picture.ex:75 lib/graphql/resolvers/report.ex:110
#: lib/graphql/resolvers/todos.ex:57
msgid "Profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:125
msgid "Registrations are not open"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:330
msgid "The current password is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:382
msgid "The new email doesn't seem to be valid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:379
msgid "The new email must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:333
msgid "The new password must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:376 lib/graphql/resolvers/user.ex:439
#: lib/graphql/resolvers/user.ex:442
msgid "The password provided is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:337
msgid "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:215
msgid "This user can't reset their password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:79
msgid "This user has been disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:179
msgid "Unable to validate user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:420
msgid "User already disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:489
msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
msgid "You cannot join this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:97
msgid "You may not list groups unless moderator."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:387
msgid "You need to be logged-in to change your email"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:345
msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
msgid "You need to be logged-in to delete a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:447
msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
msgid "You need to be logged-in to update a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:58
msgid "You need to have admin access to list users"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:108
msgid "You need to have an existing token to get a refresh token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:198 lib/graphql/resolvers/user.ex:222
msgid "You requested again a confirmation email too soon"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:128
msgid "Your email is not on the allowlist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:64 lib/graphql/resolvers/actor.ex:94
msgid "Error while performing background task"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:27
msgid "No profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:54 lib/graphql/resolvers/actor.ex:91
msgid "No remote profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:69
msgid "Only moderators and administrators can suspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:99
msgid "Only moderators and administrators can unsuspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:24
msgid "Only remote profiles may be refreshed"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:61
msgid "Profile already suspended"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:96
msgid "A valid email is required by your instance"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:90
msgid "Anonymous participation is not enabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:173
msgid "Cannot remove the last administrator of a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:170
msgid "Cannot remove the last identity of a user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:95
msgid "Comment is already deleted"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:61
msgid "Discussion not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:62 lib/graphql/resolvers/report.ex:87
msgid "Error while saving report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:113
msgid "Error while updating report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:131
msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:236 lib/graphql/resolvers/event.ex:278
msgid "Event not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:41 lib/graphql/resolvers/event.ex:46
#: lib/graphql/resolvers/event.ex:60
msgid "Event with UUID %{uuid} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:87
#: lib/graphql/resolvers/participant.ex:128 lib/graphql/resolvers/participant.ex:160
msgid "Event with this ID %{id} doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:103
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:100 lib/graphql/resolvers/participant.ex:234
msgid "Moderator profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:181
msgid "No discussion with ID %{id}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:81 lib/graphql/resolvers/todos.ex:171
msgid "No profile found for user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:66
msgid "No such feed token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:79 lib/graphql/resolvers/post.ex:94
#: lib/graphql/resolvers/post.ex:99
msgid "No such post"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:87
msgid "No such resource"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:202
msgid "Organizer profile is not owned by the user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:244
msgid "Participant already has role %{role}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:173
#: lib/graphql/resolvers/participant.ex:202 lib/graphql/resolvers/participant.ex:237
#: lib/graphql/resolvers/participant.ex:247
msgid "Participant not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:31
msgid "Person with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:46
msgid "Person with username %{username} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:45
msgid "Picture with ID %{id} was not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:152 lib/graphql/resolvers/post.ex:185
msgid "Post ID is not a valid ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:155 lib/graphql/resolvers/post.ex:188
msgid "Post doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:83
msgid "Profile invited doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:92
msgid "Profile is already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:125 lib/graphql/resolvers/post.ex:158
#: lib/graphql/resolvers/post.ex:191 lib/graphql/resolvers/resource.ex:86 lib/graphql/resolvers/resource.ex:123
#: lib/graphql/resolvers/resource.ex:152 lib/graphql/resolvers/resource.ex:181 lib/graphql/resolvers/todos.ex:60
#: lib/graphql/resolvers/todos.ex:84 lib/graphql/resolvers/todos.ex:102 lib/graphql/resolvers/todos.ex:174
#: lib/graphql/resolvers/todos.ex:197 lib/graphql/resolvers/todos.ex:225
msgid "Profile is not member of group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:139 lib/graphql/resolvers/person.ex:167
msgid "Profile not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:104 lib/graphql/resolvers/participant.ex:241
msgid "Provided moderator profile doesn't have permission on this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:38
msgid "Report not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:149 lib/graphql/resolvers/resource.ex:178
msgid "Resource doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:124
msgid "The event has already reached its maximum capacity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:267
msgid "This token is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:168 lib/graphql/resolvers/todos.ex:222
msgid "Todo doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:78 lib/graphql/resolvers/todos.ex:194
#: lib/graphql/resolvers/todos.ex:219
msgid "Todo list doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:72
msgid "Token does not exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:69
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:308
msgid "User not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:220
msgid "You already have a profile for this user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:134
msgid "You are already a participant of this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:185
msgid "You are not a member of the group the discussion belongs to"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:86
msgid "You are not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:137
msgid "You are not a moderator or admin for this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:41
msgid "You are not allowed to create a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:44
msgid "You are not allowed to create a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:103
msgid "You are not allowed to delete a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:81
msgid "You are not allowed to delete a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:63
msgid "You are not allowed to update a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:167
#: lib/graphql/resolvers/participant.ex:196
msgid "You can't leave event because you're the only event creator participant"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:141
msgid "You can't set yourself to a lower member role for this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:91
msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
msgid "You cannot delete this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:89
msgid "You cannot invite to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:75
msgid "You don't have permission to delete this token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:51
msgid "You need to be logged-in and a moderator to list action logs"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:73
msgid "You need to be logged-in and a moderator to list persons"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:28
msgid "You need to be logged-in and a moderator to list reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:118
msgid "You need to be logged-in and a moderator to update a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:43
msgid "You need to be logged-in and a moderator to view a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:194
msgid "You need to be logged-in and an administrator to access admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:179
msgid "You need to be logged-in and an administrator to access dashboard statistics"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:222
msgid "You need to be logged-in and an administrator to save admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:66
msgid "You need to be logged-in to access discussions"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:92
msgid "You need to be logged-in to access resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:118
msgid "You need to be logged-in to create a new identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:213
msgid "You need to be logged-in to create events"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:130
msgid "You need to be logged-in to create posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:81 lib/graphql/resolvers/report.ex:92
msgid "You need to be logged-in to create reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:128
msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
msgid "You need to be logged-in to delete an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:181
msgid "You need to be logged-in to delete an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:196
msgid "You need to be logged-in to delete posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:186
msgid "You need to be logged-in to delete resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:108
msgid "You need to be logged-in to join an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:207
msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
msgid "You need to be logged-in to update an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:147
msgid "You need to be logged-in to update an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:163
msgid "You need to be logged-in to update posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:157
msgid "You need to be logged-in to update resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:204
msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:84
msgid "You need to be logged-in to view current person"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:95
msgid "You need to be logged-in to view your list of identities"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
msgid "You need to login to upload a picture"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:84
msgid "Reporter ID does not match the anonymous profile id"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:59
msgid "Reporter profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:120
msgid "Parent resource doesn't belong to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:93
msgid "Profile ID provided is not the anonymous profile one"
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:109
msgid "The chosen password is too short."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:138
msgid "The registration token is already in use, this looks like an issue on our side."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:104
msgid "This email is already used."
msgstr ""

View file

@ -91,3 +91,734 @@ msgstr ""
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:103
msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
msgid "Current profile is not an administrator of the selected group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:514
msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:69
msgid "Group with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:41 lib/graphql/resolvers/group.ex:55
msgid "Group with name %{name} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:83
msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
msgid "Member not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:58 lib/graphql/resolvers/actor.ex:88
#: lib/graphql/resolvers/user.ex:417
msgid "No profile found for the moderator user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:195
msgid "No user to validate with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:217 lib/graphql/resolvers/user.ex:76
#: lib/graphql/resolvers/user.ex:219
msgid "No user with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:36 lib/graphql/resolvers/comment.ex:98
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:142
#: lib/graphql/resolvers/person.ex:176 lib/graphql/resolvers/person.ex:241 lib/graphql/resolvers/person.ex:273
#: lib/graphql/resolvers/person.ex:286 lib/graphql/resolvers/picture.ex:75 lib/graphql/resolvers/report.ex:110
#: lib/graphql/resolvers/todos.ex:57
msgid "Profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:125
msgid "Registrations are not open"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:330
msgid "The current password is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:382
msgid "The new email doesn't seem to be valid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:379
msgid "The new email must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:333
msgid "The new password must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:376 lib/graphql/resolvers/user.ex:439
#: lib/graphql/resolvers/user.ex:442
msgid "The password provided is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:337
msgid "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:215
msgid "This user can't reset their password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:79
msgid "This user has been disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:179
msgid "Unable to validate user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:420
msgid "User already disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:489
msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
msgid "You cannot join this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:97
msgid "You may not list groups unless moderator."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:387
msgid "You need to be logged-in to change your email"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:345
msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
msgid "You need to be logged-in to delete a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:447
msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
msgid "You need to be logged-in to update a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:58
msgid "You need to have admin access to list users"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:108
msgid "You need to have an existing token to get a refresh token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:198 lib/graphql/resolvers/user.ex:222
msgid "You requested again a confirmation email too soon"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:128
msgid "Your email is not on the allowlist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:64 lib/graphql/resolvers/actor.ex:94
msgid "Error while performing background task"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:27
msgid "No profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:54 lib/graphql/resolvers/actor.ex:91
msgid "No remote profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:69
msgid "Only moderators and administrators can suspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:99
msgid "Only moderators and administrators can unsuspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:24
msgid "Only remote profiles may be refreshed"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:61
msgid "Profile already suspended"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:96
msgid "A valid email is required by your instance"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:90
msgid "Anonymous participation is not enabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:173
msgid "Cannot remove the last administrator of a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:170
msgid "Cannot remove the last identity of a user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:95
msgid "Comment is already deleted"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:61
msgid "Discussion not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:62 lib/graphql/resolvers/report.ex:87
msgid "Error while saving report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:113
msgid "Error while updating report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:131
msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:236 lib/graphql/resolvers/event.ex:278
msgid "Event not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:41 lib/graphql/resolvers/event.ex:46
#: lib/graphql/resolvers/event.ex:60
msgid "Event with UUID %{uuid} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:87
#: lib/graphql/resolvers/participant.ex:128 lib/graphql/resolvers/participant.ex:160
msgid "Event with this ID %{id} doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:103
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:100 lib/graphql/resolvers/participant.ex:234
msgid "Moderator profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:181
msgid "No discussion with ID %{id}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:81 lib/graphql/resolvers/todos.ex:171
msgid "No profile found for user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:66
msgid "No such feed token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:79 lib/graphql/resolvers/post.ex:94
#: lib/graphql/resolvers/post.ex:99
msgid "No such post"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:87
msgid "No such resource"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:202
msgid "Organizer profile is not owned by the user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:244
msgid "Participant already has role %{role}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:173
#: lib/graphql/resolvers/participant.ex:202 lib/graphql/resolvers/participant.ex:237
#: lib/graphql/resolvers/participant.ex:247
msgid "Participant not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:31
msgid "Person with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:46
msgid "Person with username %{username} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:45
msgid "Picture with ID %{id} was not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:152 lib/graphql/resolvers/post.ex:185
msgid "Post ID is not a valid ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:155 lib/graphql/resolvers/post.ex:188
msgid "Post doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:83
msgid "Profile invited doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:92
msgid "Profile is already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:125 lib/graphql/resolvers/post.ex:158
#: lib/graphql/resolvers/post.ex:191 lib/graphql/resolvers/resource.ex:86 lib/graphql/resolvers/resource.ex:123
#: lib/graphql/resolvers/resource.ex:152 lib/graphql/resolvers/resource.ex:181 lib/graphql/resolvers/todos.ex:60
#: lib/graphql/resolvers/todos.ex:84 lib/graphql/resolvers/todos.ex:102 lib/graphql/resolvers/todos.ex:174
#: lib/graphql/resolvers/todos.ex:197 lib/graphql/resolvers/todos.ex:225
msgid "Profile is not member of group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:139 lib/graphql/resolvers/person.ex:167
msgid "Profile not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:104 lib/graphql/resolvers/participant.ex:241
msgid "Provided moderator profile doesn't have permission on this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:38
msgid "Report not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:149 lib/graphql/resolvers/resource.ex:178
msgid "Resource doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:124
msgid "The event has already reached its maximum capacity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:267
msgid "This token is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:168 lib/graphql/resolvers/todos.ex:222
msgid "Todo doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:78 lib/graphql/resolvers/todos.ex:194
#: lib/graphql/resolvers/todos.ex:219
msgid "Todo list doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:72
msgid "Token does not exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:69
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:308
msgid "User not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:220
msgid "You already have a profile for this user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:134
msgid "You are already a participant of this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:185
msgid "You are not a member of the group the discussion belongs to"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:86
msgid "You are not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:137
msgid "You are not a moderator or admin for this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:41
msgid "You are not allowed to create a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:44
msgid "You are not allowed to create a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:103
msgid "You are not allowed to delete a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:81
msgid "You are not allowed to delete a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:63
msgid "You are not allowed to update a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:167
#: lib/graphql/resolvers/participant.ex:196
msgid "You can't leave event because you're the only event creator participant"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:141
msgid "You can't set yourself to a lower member role for this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:91
msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
msgid "You cannot delete this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:89
msgid "You cannot invite to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:75
msgid "You don't have permission to delete this token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:51
msgid "You need to be logged-in and a moderator to list action logs"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:73
msgid "You need to be logged-in and a moderator to list persons"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:28
msgid "You need to be logged-in and a moderator to list reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:118
msgid "You need to be logged-in and a moderator to update a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:43
msgid "You need to be logged-in and a moderator to view a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:194
msgid "You need to be logged-in and an administrator to access admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:179
msgid "You need to be logged-in and an administrator to access dashboard statistics"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:222
msgid "You need to be logged-in and an administrator to save admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:66
msgid "You need to be logged-in to access discussions"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:92
msgid "You need to be logged-in to access resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:118
msgid "You need to be logged-in to create a new identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:213
msgid "You need to be logged-in to create events"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:130
msgid "You need to be logged-in to create posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:81 lib/graphql/resolvers/report.ex:92
msgid "You need to be logged-in to create reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:128
msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
msgid "You need to be logged-in to delete an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:181
msgid "You need to be logged-in to delete an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:196
msgid "You need to be logged-in to delete posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:186
msgid "You need to be logged-in to delete resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:108
msgid "You need to be logged-in to join an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:207
msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
msgid "You need to be logged-in to update an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:147
msgid "You need to be logged-in to update an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:163
msgid "You need to be logged-in to update posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:157
msgid "You need to be logged-in to update resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:204
msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:84
msgid "You need to be logged-in to view current person"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:95
msgid "You need to be logged-in to view your list of identities"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
msgid "You need to login to upload a picture"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:84
msgid "Reporter ID does not match the anonymous profile id"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:59
msgid "Reporter profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:120
msgid "Parent resource doesn't belong to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:93
msgid "Profile ID provided is not the anonymous profile one"
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:109
msgid "The chosen password is too short."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:138
msgid "The registration token is already in use, this looks like an issue on our side."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:104
msgid "This email is already used."
msgstr ""

View file

@ -85,3 +85,734 @@ msgstr ""
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:103
msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
msgid "Current profile is not an administrator of the selected group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:514
msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:69
msgid "Group with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:41 lib/graphql/resolvers/group.ex:55
msgid "Group with name %{name} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:83
msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
msgid "Member not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:58 lib/graphql/resolvers/actor.ex:88
#: lib/graphql/resolvers/user.ex:417
msgid "No profile found for the moderator user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:195
msgid "No user to validate with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:217 lib/graphql/resolvers/user.ex:76
#: lib/graphql/resolvers/user.ex:219
msgid "No user with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:36 lib/graphql/resolvers/comment.ex:98
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:142
#: lib/graphql/resolvers/person.ex:176 lib/graphql/resolvers/person.ex:241 lib/graphql/resolvers/person.ex:273
#: lib/graphql/resolvers/person.ex:286 lib/graphql/resolvers/picture.ex:75 lib/graphql/resolvers/report.ex:110
#: lib/graphql/resolvers/todos.ex:57
msgid "Profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:125
msgid "Registrations are not open"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:330
msgid "The current password is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:382
msgid "The new email doesn't seem to be valid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:379
msgid "The new email must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:333
msgid "The new password must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:376 lib/graphql/resolvers/user.ex:439
#: lib/graphql/resolvers/user.ex:442
msgid "The password provided is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:337
msgid "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:215
msgid "This user can't reset their password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:79
msgid "This user has been disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:179
msgid "Unable to validate user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:420
msgid "User already disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:489
msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
msgid "You cannot join this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:97
msgid "You may not list groups unless moderator."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:387
msgid "You need to be logged-in to change your email"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:345
msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
msgid "You need to be logged-in to delete a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:447
msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
msgid "You need to be logged-in to update a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:58
msgid "You need to have admin access to list users"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:108
msgid "You need to have an existing token to get a refresh token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:198 lib/graphql/resolvers/user.ex:222
msgid "You requested again a confirmation email too soon"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:128
msgid "Your email is not on the allowlist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:64 lib/graphql/resolvers/actor.ex:94
msgid "Error while performing background task"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:27
msgid "No profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:54 lib/graphql/resolvers/actor.ex:91
msgid "No remote profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:69
msgid "Only moderators and administrators can suspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:99
msgid "Only moderators and administrators can unsuspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:24
msgid "Only remote profiles may be refreshed"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:61
msgid "Profile already suspended"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:96
msgid "A valid email is required by your instance"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:90
msgid "Anonymous participation is not enabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:173
msgid "Cannot remove the last administrator of a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:170
msgid "Cannot remove the last identity of a user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:95
msgid "Comment is already deleted"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:61
msgid "Discussion not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:62 lib/graphql/resolvers/report.ex:87
msgid "Error while saving report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:113
msgid "Error while updating report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:131
msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:236 lib/graphql/resolvers/event.ex:278
msgid "Event not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:41 lib/graphql/resolvers/event.ex:46
#: lib/graphql/resolvers/event.ex:60
msgid "Event with UUID %{uuid} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:87
#: lib/graphql/resolvers/participant.ex:128 lib/graphql/resolvers/participant.ex:160
msgid "Event with this ID %{id} doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:103
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:100 lib/graphql/resolvers/participant.ex:234
msgid "Moderator profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:181
msgid "No discussion with ID %{id}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:81 lib/graphql/resolvers/todos.ex:171
msgid "No profile found for user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:66
msgid "No such feed token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:79 lib/graphql/resolvers/post.ex:94
#: lib/graphql/resolvers/post.ex:99
msgid "No such post"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:87
msgid "No such resource"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:202
msgid "Organizer profile is not owned by the user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:244
msgid "Participant already has role %{role}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:173
#: lib/graphql/resolvers/participant.ex:202 lib/graphql/resolvers/participant.ex:237
#: lib/graphql/resolvers/participant.ex:247
msgid "Participant not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:31
msgid "Person with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:46
msgid "Person with username %{username} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:45
msgid "Picture with ID %{id} was not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:152 lib/graphql/resolvers/post.ex:185
msgid "Post ID is not a valid ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:155 lib/graphql/resolvers/post.ex:188
msgid "Post doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:83
msgid "Profile invited doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:92
msgid "Profile is already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:125 lib/graphql/resolvers/post.ex:158
#: lib/graphql/resolvers/post.ex:191 lib/graphql/resolvers/resource.ex:86 lib/graphql/resolvers/resource.ex:123
#: lib/graphql/resolvers/resource.ex:152 lib/graphql/resolvers/resource.ex:181 lib/graphql/resolvers/todos.ex:60
#: lib/graphql/resolvers/todos.ex:84 lib/graphql/resolvers/todos.ex:102 lib/graphql/resolvers/todos.ex:174
#: lib/graphql/resolvers/todos.ex:197 lib/graphql/resolvers/todos.ex:225
msgid "Profile is not member of group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:139 lib/graphql/resolvers/person.ex:167
msgid "Profile not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:104 lib/graphql/resolvers/participant.ex:241
msgid "Provided moderator profile doesn't have permission on this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:38
msgid "Report not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:149 lib/graphql/resolvers/resource.ex:178
msgid "Resource doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:124
msgid "The event has already reached its maximum capacity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:267
msgid "This token is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:168 lib/graphql/resolvers/todos.ex:222
msgid "Todo doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:78 lib/graphql/resolvers/todos.ex:194
#: lib/graphql/resolvers/todos.ex:219
msgid "Todo list doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:72
msgid "Token does not exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:69
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:308
msgid "User not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:220
msgid "You already have a profile for this user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:134
msgid "You are already a participant of this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:185
msgid "You are not a member of the group the discussion belongs to"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:86
msgid "You are not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:137
msgid "You are not a moderator or admin for this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:41
msgid "You are not allowed to create a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:44
msgid "You are not allowed to create a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:103
msgid "You are not allowed to delete a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:81
msgid "You are not allowed to delete a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:63
msgid "You are not allowed to update a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:167
#: lib/graphql/resolvers/participant.ex:196
msgid "You can't leave event because you're the only event creator participant"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:141
msgid "You can't set yourself to a lower member role for this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:91
msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
msgid "You cannot delete this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:89
msgid "You cannot invite to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:75
msgid "You don't have permission to delete this token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:51
msgid "You need to be logged-in and a moderator to list action logs"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:73
msgid "You need to be logged-in and a moderator to list persons"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:28
msgid "You need to be logged-in and a moderator to list reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:118
msgid "You need to be logged-in and a moderator to update a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:43
msgid "You need to be logged-in and a moderator to view a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:194
msgid "You need to be logged-in and an administrator to access admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:179
msgid "You need to be logged-in and an administrator to access dashboard statistics"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:222
msgid "You need to be logged-in and an administrator to save admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:66
msgid "You need to be logged-in to access discussions"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:92
msgid "You need to be logged-in to access resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:118
msgid "You need to be logged-in to create a new identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:213
msgid "You need to be logged-in to create events"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:130
msgid "You need to be logged-in to create posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:81 lib/graphql/resolvers/report.ex:92
msgid "You need to be logged-in to create reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:128
msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
msgid "You need to be logged-in to delete an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:181
msgid "You need to be logged-in to delete an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:196
msgid "You need to be logged-in to delete posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:186
msgid "You need to be logged-in to delete resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:108
msgid "You need to be logged-in to join an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:207
msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
msgid "You need to be logged-in to update an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:147
msgid "You need to be logged-in to update an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:163
msgid "You need to be logged-in to update posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:157
msgid "You need to be logged-in to update resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:204
msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:84
msgid "You need to be logged-in to view current person"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:95
msgid "You need to be logged-in to view your list of identities"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
msgid "You need to login to upload a picture"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:84
msgid "Reporter ID does not match the anonymous profile id"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:59
msgid "Reporter profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:120
msgid "Parent resource doesn't belong to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:93
msgid "Profile ID provided is not the anonymous profile one"
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:109
msgid "The chosen password is too short."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:138
msgid "The registration token is already in use, this looks like an issue on our side."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:104
msgid "This email is already used."
msgstr ""

View file

@ -91,3 +91,734 @@ msgstr ""
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:103
msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
msgid "Current profile is not an administrator of the selected group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:514
msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:69
msgid "Group with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:41 lib/graphql/resolvers/group.ex:55
msgid "Group with name %{name} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:83
msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
msgid "Member not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:58 lib/graphql/resolvers/actor.ex:88
#: lib/graphql/resolvers/user.ex:417
msgid "No profile found for the moderator user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:195
msgid "No user to validate with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:217 lib/graphql/resolvers/user.ex:76
#: lib/graphql/resolvers/user.ex:219
msgid "No user with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:36 lib/graphql/resolvers/comment.ex:98
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:142
#: lib/graphql/resolvers/person.ex:176 lib/graphql/resolvers/person.ex:241 lib/graphql/resolvers/person.ex:273
#: lib/graphql/resolvers/person.ex:286 lib/graphql/resolvers/picture.ex:75 lib/graphql/resolvers/report.ex:110
#: lib/graphql/resolvers/todos.ex:57
msgid "Profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:125
msgid "Registrations are not open"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:330
msgid "The current password is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:382
msgid "The new email doesn't seem to be valid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:379
msgid "The new email must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:333
msgid "The new password must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:376 lib/graphql/resolvers/user.ex:439
#: lib/graphql/resolvers/user.ex:442
msgid "The password provided is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:337
msgid "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:215
msgid "This user can't reset their password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:79
msgid "This user has been disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:179
msgid "Unable to validate user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:420
msgid "User already disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:489
msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
msgid "You cannot join this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:97
msgid "You may not list groups unless moderator."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:387
msgid "You need to be logged-in to change your email"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:345
msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
msgid "You need to be logged-in to delete a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:447
msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
msgid "You need to be logged-in to update a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:58
msgid "You need to have admin access to list users"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:108
msgid "You need to have an existing token to get a refresh token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:198 lib/graphql/resolvers/user.ex:222
msgid "You requested again a confirmation email too soon"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:128
msgid "Your email is not on the allowlist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:64 lib/graphql/resolvers/actor.ex:94
msgid "Error while performing background task"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:27
msgid "No profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:54 lib/graphql/resolvers/actor.ex:91
msgid "No remote profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:69
msgid "Only moderators and administrators can suspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:99
msgid "Only moderators and administrators can unsuspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:24
msgid "Only remote profiles may be refreshed"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:61
msgid "Profile already suspended"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:96
msgid "A valid email is required by your instance"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:90
msgid "Anonymous participation is not enabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:173
msgid "Cannot remove the last administrator of a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:170
msgid "Cannot remove the last identity of a user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:95
msgid "Comment is already deleted"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:61
msgid "Discussion not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:62 lib/graphql/resolvers/report.ex:87
msgid "Error while saving report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:113
msgid "Error while updating report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:131
msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:236 lib/graphql/resolvers/event.ex:278
msgid "Event not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:41 lib/graphql/resolvers/event.ex:46
#: lib/graphql/resolvers/event.ex:60
msgid "Event with UUID %{uuid} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:87
#: lib/graphql/resolvers/participant.ex:128 lib/graphql/resolvers/participant.ex:160
msgid "Event with this ID %{id} doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:103
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:100 lib/graphql/resolvers/participant.ex:234
msgid "Moderator profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:181
msgid "No discussion with ID %{id}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:81 lib/graphql/resolvers/todos.ex:171
msgid "No profile found for user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:66
msgid "No such feed token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:79 lib/graphql/resolvers/post.ex:94
#: lib/graphql/resolvers/post.ex:99
msgid "No such post"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:87
msgid "No such resource"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:202
msgid "Organizer profile is not owned by the user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:244
msgid "Participant already has role %{role}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:173
#: lib/graphql/resolvers/participant.ex:202 lib/graphql/resolvers/participant.ex:237
#: lib/graphql/resolvers/participant.ex:247
msgid "Participant not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:31
msgid "Person with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:46
msgid "Person with username %{username} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:45
msgid "Picture with ID %{id} was not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:152 lib/graphql/resolvers/post.ex:185
msgid "Post ID is not a valid ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:155 lib/graphql/resolvers/post.ex:188
msgid "Post doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:83
msgid "Profile invited doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:92
msgid "Profile is already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:125 lib/graphql/resolvers/post.ex:158
#: lib/graphql/resolvers/post.ex:191 lib/graphql/resolvers/resource.ex:86 lib/graphql/resolvers/resource.ex:123
#: lib/graphql/resolvers/resource.ex:152 lib/graphql/resolvers/resource.ex:181 lib/graphql/resolvers/todos.ex:60
#: lib/graphql/resolvers/todos.ex:84 lib/graphql/resolvers/todos.ex:102 lib/graphql/resolvers/todos.ex:174
#: lib/graphql/resolvers/todos.ex:197 lib/graphql/resolvers/todos.ex:225
msgid "Profile is not member of group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:139 lib/graphql/resolvers/person.ex:167
msgid "Profile not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:104 lib/graphql/resolvers/participant.ex:241
msgid "Provided moderator profile doesn't have permission on this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:38
msgid "Report not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:149 lib/graphql/resolvers/resource.ex:178
msgid "Resource doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:124
msgid "The event has already reached its maximum capacity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:267
msgid "This token is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:168 lib/graphql/resolvers/todos.ex:222
msgid "Todo doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:78 lib/graphql/resolvers/todos.ex:194
#: lib/graphql/resolvers/todos.ex:219
msgid "Todo list doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:72
msgid "Token does not exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:69
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:308
msgid "User not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:220
msgid "You already have a profile for this user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:134
msgid "You are already a participant of this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:185
msgid "You are not a member of the group the discussion belongs to"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:86
msgid "You are not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:137
msgid "You are not a moderator or admin for this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:41
msgid "You are not allowed to create a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:44
msgid "You are not allowed to create a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:103
msgid "You are not allowed to delete a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:81
msgid "You are not allowed to delete a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:63
msgid "You are not allowed to update a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:167
#: lib/graphql/resolvers/participant.ex:196
msgid "You can't leave event because you're the only event creator participant"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:141
msgid "You can't set yourself to a lower member role for this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:91
msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
msgid "You cannot delete this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:89
msgid "You cannot invite to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:75
msgid "You don't have permission to delete this token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:51
msgid "You need to be logged-in and a moderator to list action logs"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:73
msgid "You need to be logged-in and a moderator to list persons"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:28
msgid "You need to be logged-in and a moderator to list reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:118
msgid "You need to be logged-in and a moderator to update a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:43
msgid "You need to be logged-in and a moderator to view a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:194
msgid "You need to be logged-in and an administrator to access admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:179
msgid "You need to be logged-in and an administrator to access dashboard statistics"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:222
msgid "You need to be logged-in and an administrator to save admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:66
msgid "You need to be logged-in to access discussions"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:92
msgid "You need to be logged-in to access resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:118
msgid "You need to be logged-in to create a new identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:213
msgid "You need to be logged-in to create events"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:130
msgid "You need to be logged-in to create posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:81 lib/graphql/resolvers/report.ex:92
msgid "You need to be logged-in to create reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:128
msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
msgid "You need to be logged-in to delete an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:181
msgid "You need to be logged-in to delete an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:196
msgid "You need to be logged-in to delete posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:186
msgid "You need to be logged-in to delete resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:108
msgid "You need to be logged-in to join an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:207
msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
msgid "You need to be logged-in to update an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:147
msgid "You need to be logged-in to update an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:163
msgid "You need to be logged-in to update posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:157
msgid "You need to be logged-in to update resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:204
msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:84
msgid "You need to be logged-in to view current person"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:95
msgid "You need to be logged-in to view your list of identities"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
msgid "You need to login to upload a picture"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:84
msgid "Reporter ID does not match the anonymous profile id"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:59
msgid "Reporter profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:120
msgid "Parent resource doesn't belong to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:93
msgid "Profile ID provided is not the anonymous profile one"
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:109
msgid "The chosen password is too short."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:138
msgid "The registration token is already in use, this looks like an issue on our side."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:104
msgid "This email is already used."
msgstr ""

View file

@ -85,3 +85,734 @@ msgstr ""
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:103
msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
msgid "Current profile is not an administrator of the selected group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:514
msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:69
msgid "Group with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:41 lib/graphql/resolvers/group.ex:55
msgid "Group with name %{name} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:83
msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
msgid "Member not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:58 lib/graphql/resolvers/actor.ex:88
#: lib/graphql/resolvers/user.ex:417
msgid "No profile found for the moderator user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:195
msgid "No user to validate with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:217 lib/graphql/resolvers/user.ex:76
#: lib/graphql/resolvers/user.ex:219
msgid "No user with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:36 lib/graphql/resolvers/comment.ex:98
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:142
#: lib/graphql/resolvers/person.ex:176 lib/graphql/resolvers/person.ex:241 lib/graphql/resolvers/person.ex:273
#: lib/graphql/resolvers/person.ex:286 lib/graphql/resolvers/picture.ex:75 lib/graphql/resolvers/report.ex:110
#: lib/graphql/resolvers/todos.ex:57
msgid "Profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:125
msgid "Registrations are not open"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:330
msgid "The current password is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:382
msgid "The new email doesn't seem to be valid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:379
msgid "The new email must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:333
msgid "The new password must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:376 lib/graphql/resolvers/user.ex:439
#: lib/graphql/resolvers/user.ex:442
msgid "The password provided is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:337
msgid "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:215
msgid "This user can't reset their password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:79
msgid "This user has been disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:179
msgid "Unable to validate user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:420
msgid "User already disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:489
msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
msgid "You cannot join this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:97
msgid "You may not list groups unless moderator."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:387
msgid "You need to be logged-in to change your email"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:345
msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
msgid "You need to be logged-in to delete a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:447
msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
msgid "You need to be logged-in to update a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:58
msgid "You need to have admin access to list users"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:108
msgid "You need to have an existing token to get a refresh token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:198 lib/graphql/resolvers/user.ex:222
msgid "You requested again a confirmation email too soon"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:128
msgid "Your email is not on the allowlist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:64 lib/graphql/resolvers/actor.ex:94
msgid "Error while performing background task"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:27
msgid "No profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:54 lib/graphql/resolvers/actor.ex:91
msgid "No remote profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:69
msgid "Only moderators and administrators can suspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:99
msgid "Only moderators and administrators can unsuspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:24
msgid "Only remote profiles may be refreshed"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:61
msgid "Profile already suspended"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:96
msgid "A valid email is required by your instance"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:90
msgid "Anonymous participation is not enabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:173
msgid "Cannot remove the last administrator of a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:170
msgid "Cannot remove the last identity of a user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:95
msgid "Comment is already deleted"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:61
msgid "Discussion not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:62 lib/graphql/resolvers/report.ex:87
msgid "Error while saving report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:113
msgid "Error while updating report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:131
msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:236 lib/graphql/resolvers/event.ex:278
msgid "Event not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:41 lib/graphql/resolvers/event.ex:46
#: lib/graphql/resolvers/event.ex:60
msgid "Event with UUID %{uuid} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:87
#: lib/graphql/resolvers/participant.ex:128 lib/graphql/resolvers/participant.ex:160
msgid "Event with this ID %{id} doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:103
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:100 lib/graphql/resolvers/participant.ex:234
msgid "Moderator profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:181
msgid "No discussion with ID %{id}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:81 lib/graphql/resolvers/todos.ex:171
msgid "No profile found for user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:66
msgid "No such feed token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:79 lib/graphql/resolvers/post.ex:94
#: lib/graphql/resolvers/post.ex:99
msgid "No such post"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:87
msgid "No such resource"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:202
msgid "Organizer profile is not owned by the user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:244
msgid "Participant already has role %{role}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:173
#: lib/graphql/resolvers/participant.ex:202 lib/graphql/resolvers/participant.ex:237
#: lib/graphql/resolvers/participant.ex:247
msgid "Participant not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:31
msgid "Person with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:46
msgid "Person with username %{username} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:45
msgid "Picture with ID %{id} was not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:152 lib/graphql/resolvers/post.ex:185
msgid "Post ID is not a valid ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:155 lib/graphql/resolvers/post.ex:188
msgid "Post doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:83
msgid "Profile invited doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:92
msgid "Profile is already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:125 lib/graphql/resolvers/post.ex:158
#: lib/graphql/resolvers/post.ex:191 lib/graphql/resolvers/resource.ex:86 lib/graphql/resolvers/resource.ex:123
#: lib/graphql/resolvers/resource.ex:152 lib/graphql/resolvers/resource.ex:181 lib/graphql/resolvers/todos.ex:60
#: lib/graphql/resolvers/todos.ex:84 lib/graphql/resolvers/todos.ex:102 lib/graphql/resolvers/todos.ex:174
#: lib/graphql/resolvers/todos.ex:197 lib/graphql/resolvers/todos.ex:225
msgid "Profile is not member of group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:139 lib/graphql/resolvers/person.ex:167
msgid "Profile not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:104 lib/graphql/resolvers/participant.ex:241
msgid "Provided moderator profile doesn't have permission on this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:38
msgid "Report not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:149 lib/graphql/resolvers/resource.ex:178
msgid "Resource doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:124
msgid "The event has already reached its maximum capacity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:267
msgid "This token is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:168 lib/graphql/resolvers/todos.ex:222
msgid "Todo doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:78 lib/graphql/resolvers/todos.ex:194
#: lib/graphql/resolvers/todos.ex:219
msgid "Todo list doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:72
msgid "Token does not exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:69
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:308
msgid "User not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:220
msgid "You already have a profile for this user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:134
msgid "You are already a participant of this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:185
msgid "You are not a member of the group the discussion belongs to"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:86
msgid "You are not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:137
msgid "You are not a moderator or admin for this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:41
msgid "You are not allowed to create a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:44
msgid "You are not allowed to create a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:103
msgid "You are not allowed to delete a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:81
msgid "You are not allowed to delete a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:63
msgid "You are not allowed to update a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:167
#: lib/graphql/resolvers/participant.ex:196
msgid "You can't leave event because you're the only event creator participant"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:141
msgid "You can't set yourself to a lower member role for this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:91
msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
msgid "You cannot delete this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:89
msgid "You cannot invite to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:75
msgid "You don't have permission to delete this token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:51
msgid "You need to be logged-in and a moderator to list action logs"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:73
msgid "You need to be logged-in and a moderator to list persons"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:28
msgid "You need to be logged-in and a moderator to list reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:118
msgid "You need to be logged-in and a moderator to update a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:43
msgid "You need to be logged-in and a moderator to view a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:194
msgid "You need to be logged-in and an administrator to access admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:179
msgid "You need to be logged-in and an administrator to access dashboard statistics"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:222
msgid "You need to be logged-in and an administrator to save admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:66
msgid "You need to be logged-in to access discussions"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:92
msgid "You need to be logged-in to access resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:118
msgid "You need to be logged-in to create a new identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:213
msgid "You need to be logged-in to create events"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:130
msgid "You need to be logged-in to create posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:81 lib/graphql/resolvers/report.ex:92
msgid "You need to be logged-in to create reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:128
msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
msgid "You need to be logged-in to delete an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:181
msgid "You need to be logged-in to delete an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:196
msgid "You need to be logged-in to delete posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:186
msgid "You need to be logged-in to delete resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:108
msgid "You need to be logged-in to join an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:207
msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
msgid "You need to be logged-in to update an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:147
msgid "You need to be logged-in to update an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:163
msgid "You need to be logged-in to update posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:157
msgid "You need to be logged-in to update resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:204
msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:84
msgid "You need to be logged-in to view current person"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:95
msgid "You need to be logged-in to view your list of identities"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
msgid "You need to login to upload a picture"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:84
msgid "Reporter ID does not match the anonymous profile id"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:59
msgid "Reporter profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:120
msgid "Parent resource doesn't belong to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:93
msgid "Profile ID provided is not the anonymous profile one"
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:109
msgid "The chosen password is too short."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:138
msgid "The registration token is already in use, this looks like an issue on our side."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:104
msgid "This email is already used."
msgstr ""

View file

@ -95,3 +95,734 @@ msgstr ""
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:103
msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
msgid "Current profile is not an administrator of the selected group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:514
msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:69
msgid "Group with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:41 lib/graphql/resolvers/group.ex:55
msgid "Group with name %{name} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:83
msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
msgid "Member not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:58 lib/graphql/resolvers/actor.ex:88
#: lib/graphql/resolvers/user.ex:417
msgid "No profile found for the moderator user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:195
msgid "No user to validate with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:217 lib/graphql/resolvers/user.ex:76
#: lib/graphql/resolvers/user.ex:219
msgid "No user with this email was found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:36 lib/graphql/resolvers/comment.ex:98
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:142
#: lib/graphql/resolvers/person.ex:176 lib/graphql/resolvers/person.ex:241 lib/graphql/resolvers/person.ex:273
#: lib/graphql/resolvers/person.ex:286 lib/graphql/resolvers/picture.ex:75 lib/graphql/resolvers/report.ex:110
#: lib/graphql/resolvers/todos.ex:57
msgid "Profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:125
msgid "Registrations are not open"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:330
msgid "The current password is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:382
msgid "The new email doesn't seem to be valid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:379
msgid "The new email must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:333
msgid "The new password must be different"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:376 lib/graphql/resolvers/user.ex:439
#: lib/graphql/resolvers/user.ex:442
msgid "The password provided is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:337
msgid "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:215
msgid "This user can't reset their password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:79
msgid "This user has been disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:179
msgid "Unable to validate user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:420
msgid "User already disabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:489
msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
msgid "You cannot join this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:97
msgid "You may not list groups unless moderator."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:387
msgid "You need to be logged-in to change your email"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:345
msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
msgid "You need to be logged-in to delete a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:447
msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
msgid "You need to be logged-in to update a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:58
msgid "You need to have admin access to list users"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:108
msgid "You need to have an existing token to get a refresh token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:198 lib/graphql/resolvers/user.ex:222
msgid "You requested again a confirmation email too soon"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/user.ex:128
msgid "Your email is not on the allowlist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:64 lib/graphql/resolvers/actor.ex:94
msgid "Error while performing background task"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:27
msgid "No profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:54 lib/graphql/resolvers/actor.ex:91
msgid "No remote profile found with this ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:69
msgid "Only moderators and administrators can suspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:99
msgid "Only moderators and administrators can unsuspend a profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:24
msgid "Only remote profiles may be refreshed"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/actor.ex:61
msgid "Profile already suspended"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:96
msgid "A valid email is required by your instance"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:90
msgid "Anonymous participation is not enabled"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:173
msgid "Cannot remove the last administrator of a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:170
msgid "Cannot remove the last identity of a user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:95
msgid "Comment is already deleted"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:61
msgid "Discussion not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:62 lib/graphql/resolvers/report.ex:87
msgid "Error while saving report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:113
msgid "Error while updating report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:131
msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:236 lib/graphql/resolvers/event.ex:278
msgid "Event not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:41 lib/graphql/resolvers/event.ex:46
#: lib/graphql/resolvers/event.ex:60
msgid "Event with UUID %{uuid} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:87
#: lib/graphql/resolvers/participant.ex:128 lib/graphql/resolvers/participant.ex:160
msgid "Event with this ID %{id} doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:103
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:100 lib/graphql/resolvers/participant.ex:234
msgid "Moderator profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:181
msgid "No discussion with ID %{id}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:81 lib/graphql/resolvers/todos.ex:171
msgid "No profile found for user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:66
msgid "No such feed token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:79 lib/graphql/resolvers/post.ex:94
#: lib/graphql/resolvers/post.ex:99
msgid "No such post"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:87
msgid "No such resource"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:202
msgid "Organizer profile is not owned by the user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:244
msgid "Participant already has role %{role}"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:173
#: lib/graphql/resolvers/participant.ex:202 lib/graphql/resolvers/participant.ex:237
#: lib/graphql/resolvers/participant.ex:247
msgid "Participant not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:31
msgid "Person with ID %{id} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:46
msgid "Person with username %{username} not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:45
msgid "Picture with ID %{id} was not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:152 lib/graphql/resolvers/post.ex:185
msgid "Post ID is not a valid ID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:155 lib/graphql/resolvers/post.ex:188
msgid "Post doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:83
msgid "Profile invited doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:92
msgid "Profile is already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:125 lib/graphql/resolvers/post.ex:158
#: lib/graphql/resolvers/post.ex:191 lib/graphql/resolvers/resource.ex:86 lib/graphql/resolvers/resource.ex:123
#: lib/graphql/resolvers/resource.ex:152 lib/graphql/resolvers/resource.ex:181 lib/graphql/resolvers/todos.ex:60
#: lib/graphql/resolvers/todos.ex:84 lib/graphql/resolvers/todos.ex:102 lib/graphql/resolvers/todos.ex:174
#: lib/graphql/resolvers/todos.ex:197 lib/graphql/resolvers/todos.ex:225
msgid "Profile is not member of group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:139 lib/graphql/resolvers/person.ex:167
msgid "Profile not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:104 lib/graphql/resolvers/participant.ex:241
msgid "Provided moderator profile doesn't have permission on this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:38
msgid "Report not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:149 lib/graphql/resolvers/resource.ex:178
msgid "Resource doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:124
msgid "The event has already reached its maximum capacity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:267
msgid "This token is invalid"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:168 lib/graphql/resolvers/todos.ex:222
msgid "Todo doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/todos.ex:78 lib/graphql/resolvers/todos.ex:194
#: lib/graphql/resolvers/todos.ex:219
msgid "Todo list doesn't exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:72
msgid "Token does not exist"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:69
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:308
msgid "User not found"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:220
msgid "You already have a profile for this user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:134
msgid "You are already a participant of this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:185
msgid "You are not a member of the group the discussion belongs to"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:86
msgid "You are not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:137
msgid "You are not a moderator or admin for this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:41
msgid "You are not allowed to create a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:44
msgid "You are not allowed to create a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:103
msgid "You are not allowed to delete a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:81
msgid "You are not allowed to delete a feed token if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:63
msgid "You are not allowed to update a comment if not connected"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:167
#: lib/graphql/resolvers/participant.ex:196
msgid "You can't leave event because you're the only event creator participant"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:141
msgid "You can't set yourself to a lower member role for this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:91
msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
msgid "You cannot delete this event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/member.ex:89
msgid "You cannot invite to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/feed_token.ex:75
msgid "You don't have permission to delete this token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:51
msgid "You need to be logged-in and a moderator to list action logs"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:73
msgid "You need to be logged-in and a moderator to list persons"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:28
msgid "You need to be logged-in and a moderator to list reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:118
msgid "You need to be logged-in and a moderator to update a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:43
msgid "You need to be logged-in and a moderator to view a report"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:194
msgid "You need to be logged-in and an administrator to access admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:179
msgid "You need to be logged-in and an administrator to access dashboard statistics"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/admin.ex:222
msgid "You need to be logged-in and an administrator to save admin settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/discussion.ex:66
msgid "You need to be logged-in to access discussions"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:92
msgid "You need to be logged-in to access resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:118
msgid "You need to be logged-in to create a new identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:213
msgid "You need to be logged-in to create events"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:130
msgid "You need to be logged-in to create posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:81 lib/graphql/resolvers/report.ex:92
msgid "You need to be logged-in to create reports"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:128
msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
msgid "You need to be logged-in to delete an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:181
msgid "You need to be logged-in to delete an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:196
msgid "You need to be logged-in to delete posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:186
msgid "You need to be logged-in to delete resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:108
msgid "You need to be logged-in to join an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:207
msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
msgid "You need to be logged-in to update an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:147
msgid "You need to be logged-in to update an identity"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/post.ex:163
msgid "You need to be logged-in to update posts"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:157
msgid "You need to be logged-in to update resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:204
msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:84
msgid "You need to be logged-in to view current person"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/person.ex:95
msgid "You need to be logged-in to view your list of identities"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
msgid "You need to login to upload a picture"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:84
msgid "Reporter ID does not match the anonymous profile id"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/report.ex:59
msgid "Reporter profile is not owned by authenticated user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/resource.ex:120
msgid "Parent resource doesn't belong to this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/participant.ex:93
msgid "Profile ID provided is not the anonymous profile one"
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:109
msgid "The chosen password is too short."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:138
msgid "The registration token is already in use, this looks like an issue on our side."
msgstr ""
#, elixir-format
#: lib/mobilizon/users/user.ex:104
msgid "This email is already used."
msgstr ""

Some files were not shown because too many files have changed in this diff Show more