Merge remote-tracking branch 'origin/main'

This commit is contained in:
778a69cd 2023-09-01 12:43:33 +02:00
commit 239ca025bb
64 changed files with 882 additions and 379 deletions

View file

@ -5,42 +5,90 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 3.2.0-beta.1 (2023-09-01)
### Features
* **cli:** allow the mobilizon.users.delete command to delete multiple users by email domain or ip ([bc50ab6](https://framagit.org/framasoft/mobilizon/commit/bc50ab66f3a44df220a7daa3cb1d917bd02487ba))
* **export:** add date of participant creation in participant exports ([fef60ed](https://framagit.org/framasoft/mobilizon/commit/fef60ed0f92fc4e09ee261ff03f1139aff2449c3)), closes [#1343](https://framagit.org/framasoft/mobilizon/issues/1343)
* **notifications:** add missing notifications when an user registers to an event ([da532c7](https://framagit.org/framasoft/mobilizon/commit/da532c7059bea5fcd47e2f42210e8ba842a11d63)), closes [#1344](https://framagit.org/framasoft/mobilizon/issues/1344)
* **reports:** allow reports to hold multiple events ([f2ac3e2](https://framagit.org/framasoft/mobilizon/commit/f2ac3e2e5d28f4257a5e2d4870d339fecf3a5f1b))
* **reports:** allow to suspend a profile or a user account directly from the report view ([69588db](https://framagit.org/framasoft/mobilizon/commit/69588dbf4ce2f80cc5829a841135042fa73eb4fe))
* **reports:** improve reportview and allow removing content + resolve report automatically ([b105c50](https://framagit.org/framasoft/mobilizon/commit/b105c508c03ce3cb96dd8342f96d3291aa197e22))
* **reports:** show suspended status next to reported profile ([b9a165a](https://framagit.org/framasoft/mobilizon/commit/b9a165a7fc565dc583cca81dd9c54570f73b4ca3))
### Bug Fixes
* add inets and ssl to extra_applications in test env ([af46bea](https://framagit.org/framasoft/mobilizon/commit/af46bea7f730f4479bb31518a9fa53de7302049a))
* **apps:** add missing app scopes ([7e98097](https://framagit.org/framasoft/mobilizon/commit/7e98097c710663609274200564fca9eff1ea4d20))
* **apps:** make sure we can set status for an application token ([1a6095d](https://framagit.org/framasoft/mobilizon/commit/1a6095d27aeb440379d27c3894c302f831214822))
* **backend:** fix config cache not being used everytime ([ed3cd58](https://framagit.org/framasoft/mobilizon/commit/ed3cd5858cd27a90d4724a95ee660bbc08e92e80))
* **backend:** handle email not being sent when resending registration instructions ([b2492a3](https://framagit.org/framasoft/mobilizon/commit/b2492a387086528598da36f11e53569c5bdb164c))
* create event time/date allignment ([3de90a3](https://framagit.org/framasoft/mobilizon/commit/3de90a3c73414105becdcb24899016178b1c6f02))
* **docker:** fix Qemu segfaulting on arm64 ([8e3f90f](https://framagit.org/framasoft/mobilizon/commit/8e3f90f7135e2a8a8ac46464420c9d57b2e02534)), closes [#1241](https://framagit.org/framasoft/mobilizon/issues/1241) [#1249](https://framagit.org/framasoft/mobilizon/issues/1249)
* **federation:** fix getting pictures from Gruppe actors ([7c5f8b2](https://framagit.org/framasoft/mobilizon/commit/7c5f8b24311253ef89c7e47cd7ce22ebe6cf2ec9))
* fix Elixir 1.15 depreciations ([da70427](https://framagit.org/framasoft/mobilizon/commit/da70427e3292be8943167bbad73d5a782a98c6b5))
* fix some typescript issues with pwa ([e351d3c](https://framagit.org/framasoft/mobilizon/commit/e351d3cb2f8183bb4335b3b21e154f46d9237a76))
* **front:** avoid crashing if we don't have configuration data in time when in guard ([7916261](https://framagit.org/framasoft/mobilizon/commit/7916261c5c8c680d064fba106619d733575bc39c))
* **front:** fix alignment of some input elements on event edition form ([50695fc](https://framagit.org/framasoft/mobilizon/commit/50695fcfd5e0dc6fd55185f4399d45ed1852f880))
* **front:** fix changing language not being saved to the user's settings ([010a5e4](https://framagit.org/framasoft/mobilizon/commit/010a5e426def0a0b7f2658234f3c9d6eec46a68e))
* **front:** fix comment not showing up when replying in a discussion ([cc8f02d](https://framagit.org/framasoft/mobilizon/commit/cc8f02d0a6354c49437e7ff1780912a71bed03f4))
* **front:** fix confirm anonymous participation ([f99267c](https://framagit.org/framasoft/mobilizon/commit/f99267c6115601fce6eadd6ee54893fde0d6fd84))
* **front:** fix discussion edition panel always showing up ([fee0e38](https://framagit.org/framasoft/mobilizon/commit/fee0e388af798f14d4da8cbd9f037137f6be9f85))
* **front:** fix display of participants list ([c6b83c4](https://framagit.org/framasoft/mobilizon/commit/c6b83c42d6fbb2e6a93175479ef1620913c6532f))
* **front:** fix map ([8f84ba1](https://framagit.org/framasoft/mobilizon/commit/8f84ba1d08ce8d2d266010ee3166106eed66116d)), closes [#1314](https://framagit.org/framasoft/mobilizon/issues/1314)
* **front:** fix missing type causing eslint error ([c76dba3](https://framagit.org/framasoft/mobilizon/commit/c76dba3dbfe4fb0ab9ed24f71a6f64681c643fca))
* **front:** fix selecting all participants in participant view ([beef3ff](https://framagit.org/framasoft/mobilizon/commit/beef3ff16d12f5d5710e302b739dd724ad4b0cb5))
* **front:** fix showing error message when app to approve doesn't exist ([12cbff1](https://framagit.org/framasoft/mobilizon/commit/12cbff154ae5cdd72a1a7e882cb99e943010222b))
* **front:** fix some alignment of some UI elements in mobile event view ([8c313b5](https://framagit.org/framasoft/mobilizon/commit/8c313b53977493792c113b5191443515f8aeae78))
* **front:** properly handle error when approving app ([086d208](https://framagit.org/framasoft/mobilizon/commit/086d208ee50ae1f9ecb30196e758fdc7687714ae))
* **front:** properly handle post not found ([8db31c9](https://framagit.org/framasoft/mobilizon/commit/8db31c99df668389db4c6651fa71a8c1420484cf))
* **front:** reduce horizontal padding on main element ([f3c218f](https://framagit.org/framasoft/mobilizon/commit/f3c218f841292a28ec6d1284a205e2c7fd7d8f6e))
* **lint:** fix lint after upgrades ([60aceb4](https://framagit.org/framasoft/mobilizon/commit/60aceb442ae49458e31a1f38d277eca7af248a36))
* **mail:** fix sending mail on OTP26 ([f54fff5](https://framagit.org/framasoft/mobilizon/commit/f54fff56fc5c94408b1fd16b1eb9dd0f91bc2dfd)), closes [#1341](https://framagit.org/framasoft/mobilizon/issues/1341)
* **push:** fix push subscriptions registration ([fdf87ea](https://framagit.org/framasoft/mobilizon/commit/fdf87ea991b1d406b28dbd0c8807908939070c8b))
* **pwa:** improvements to the PWA configuration ([04c5ac1](https://framagit.org/framasoft/mobilizon/commit/04c5ac11636a4ffb5d3ac0c510b028edfb7fc057))
* **reports:** make front-end handle nullified reported_id and reported_id ([afd2ffe](https://framagit.org/framasoft/mobilizon/commit/afd2ffe72294baedc9dd15dc89d57301831545cc))
* **reports:** remove on delete cascade for reports ([4f530ca](https://framagit.org/framasoft/mobilizon/commit/4f530cabcf1bcadc09399a728975d329f3c9fdbf))
## 3.1.3 (2023-06-21)
### Bug Fixes
* **groups:** fix unauthenticated access to groups because of missing read:group:members permission ([3714925](https://framagit.org/framasoft/mobilizon/commits/3714925896ad0415496352b9901ebec199afa0f2)), closes [#1311](https://framagit.org/framasoft/mobilizon/issues/1311)
* **groups:** fix unauthenticated access to groups because of missing read:group:members permission ([3714925](https://framagit.org/framasoft/mobilizon/commit/3714925896ad0415496352b9901ebec199afa0f2)), closes [#1311](https://framagit.org/framasoft/mobilizon/issues/1311)
## 3.1.2 (2023-06-21)
### Bug Fixes
* **activity settings:** fix saving activity settings ([6c1e1e9](https://framagit.org/framasoft/mobilizon/commits/6c1e1e98d81c7469f41beed17cfa1d4b718b5d13)), closes [#1251](https://framagit.org/framasoft/mobilizon/issues/1251)
* **apps:** fix pruning old application device activations ([dd00620](https://framagit.org/framasoft/mobilizon/commits/dd00620b9a54b2b1356855d280e03c82befe15e4))
* **backend:** filter out nil tags before starting looking for existing ones ([f04d2b9](https://framagit.org/framasoft/mobilizon/commits/f04d2b9225b80333f03a3cc9366df4a05af88a73))
* **deps:** fix compatibility with elixir-plug/mime 2.0.5 ([d63999c](https://framagit.org/framasoft/mobilizon/commits/d63999c081bcbb5923af17b71edbfd13a3720d7d))
* **discussions:** handle changeset errors when updating discussion ([ca06ec3](https://framagit.org/framasoft/mobilizon/commits/ca06ec397fbd6848e340dfae12c635736069a9f3))
* **exports:** properly handle export format not being handled ([a76b1ca](https://framagit.org/framasoft/mobilizon/commits/a76b1ca66d776fbe4566d7f23b38b087ae32530b))
* **federation:** allow federated usernames with capitals ([d502164](https://framagit.org/framasoft/mobilizon/commits/d5021647d753e6457e459b1f992da60876292428))
* **federation:** handle fetch_actor with a map ([552ab4c](https://framagit.org/framasoft/mobilizon/commits/552ab4c80b2f99095028ab3685c71ff9efdb94eb))
* **federation:** handle string values for tags when constructing mentions ([2729d5e](https://framagit.org/framasoft/mobilizon/commits/2729d5ed7acef7c20a4388f019152e80a9db163c))
* **federation:** ignore mentions from everything that's not a AP Person ([56f341e](https://framagit.org/framasoft/mobilizon/commits/56f341e960b7ae0a5fe78d7174f0e05d14add3f2))
* **federation:** only refresh instances once a day ([6745590](https://framagit.org/framasoft/mobilizon/commits/6745590e54dce236dc7a2319f9c49c4aa6858306))
* **federation:** prevent fetching own relay actor ([b981f91](https://framagit.org/framasoft/mobilizon/commits/b981f91cf748079847ae7a71b68f98b6914c951f))
* **federation:** restrict fetch_group first arg to binaries ([e8d34b4](https://framagit.org/framasoft/mobilizon/commits/e8d34b4ea9f06d16a5982da8e5ff5140852c985d))
* **federation:** rotate relay keys on startup if missing private keys ([5381eaa](https://framagit.org/framasoft/mobilizon/commits/5381eaae22248cdc6585d19c10be7fe2b7f5709f))
* **front:** add missing title to Participants View page ([a5a86a5](https://framagit.org/framasoft/mobilizon/commits/a5a86a5e1be08cf9123ee7ad0979974bc2be1cb4))
* **front:** fix displaying user activity settings checkboxes ([8e21c30](https://framagit.org/framasoft/mobilizon/commits/8e21c30f92f47dcb742d8f7df2aed59191158d80)), closes [#1251](https://framagit.org/framasoft/mobilizon/issues/1251)
* **front:** fix wrong key name for dialog.confirm() option ([c8f49e1](https://framagit.org/framasoft/mobilizon/commits/c8f49e1837d719cd737c3e1ae976f14b20345e2b))
* **front:** fix wrong value for timezone when it has no prefix ([2dd0e13](https://framagit.org/framasoft/mobilizon/commits/2dd0e13eba8bb5c04af45bae0de059deb93c2efa)), closes [#1275](https://framagit.org/framasoft/mobilizon/issues/1275)
* **group:** fix getting group members count ([f749518](https://framagit.org/framasoft/mobilizon/commits/f749518bf7a29a86da559bfe6aba6d7485e7cfeb)), closes [#1303](https://framagit.org/framasoft/mobilizon/issues/1303)
* **participant exports:** fix participants by returning the export type as well as the file path ([49b04c9](https://framagit.org/framasoft/mobilizon/commits/49b04c9b19517daa0a07656779d53001b39ab803))
* **participant:** handle re-confirming participation ([5cc5c99](https://framagit.org/framasoft/mobilizon/commits/5cc5c9943cbc9a53246dda98958e99d004f0dfa9))
* **activity settings:** fix saving activity settings ([6c1e1e9](https://framagit.org/framasoft/mobilizon/commit/6c1e1e98d81c7469f41beed17cfa1d4b718b5d13)), closes [#1251](https://framagit.org/framasoft/mobilizon/issues/1251)
* **apps:** fix pruning old application device activations ([dd00620](https://framagit.org/framasoft/mobilizon/commit/dd00620b9a54b2b1356855d280e03c82befe15e4))
* **backend:** filter out nil tags before starting looking for existing ones ([f04d2b9](https://framagit.org/framasoft/mobilizon/commit/f04d2b9225b80333f03a3cc9366df4a05af88a73))
* **deps:** fix compatibility with elixir-plug/mime 2.0.5 ([d63999c](https://framagit.org/framasoft/mobilizon/commit/d63999c081bcbb5923af17b71edbfd13a3720d7d))
* **discussions:** handle changeset errors when updating discussion ([ca06ec3](https://framagit.org/framasoft/mobilizon/commit/ca06ec397fbd6848e340dfae12c635736069a9f3))
* **exports:** properly handle export format not being handled ([a76b1ca](https://framagit.org/framasoft/mobilizon/commit/a76b1ca66d776fbe4566d7f23b38b087ae32530b))
* **federation:** allow federated usernames with capitals ([d502164](https://framagit.org/framasoft/mobilizon/commit/d5021647d753e6457e459b1f992da60876292428))
* **federation:** handle fetch_actor with a map ([552ab4c](https://framagit.org/framasoft/mobilizon/commit/552ab4c80b2f99095028ab3685c71ff9efdb94eb))
* **federation:** handle string values for tags when constructing mentions ([2729d5e](https://framagit.org/framasoft/mobilizon/commit/2729d5ed7acef7c20a4388f019152e80a9db163c))
* **federation:** ignore mentions from everything that's not a AP Person ([56f341e](https://framagit.org/framasoft/mobilizon/commit/56f341e960b7ae0a5fe78d7174f0e05d14add3f2))
* **federation:** only refresh instances once a day ([6745590](https://framagit.org/framasoft/mobilizon/commit/6745590e54dce236dc7a2319f9c49c4aa6858306))
* **federation:** prevent fetching own relay actor ([b981f91](https://framagit.org/framasoft/mobilizon/commit/b981f91cf748079847ae7a71b68f98b6914c951f))
* **federation:** restrict fetch_group first arg to binaries ([e8d34b4](https://framagit.org/framasoft/mobilizon/commit/e8d34b4ea9f06d16a5982da8e5ff5140852c985d))
* **federation:** rotate relay keys on startup if missing private keys ([5381eaa](https://framagit.org/framasoft/mobilizon/commit/5381eaae22248cdc6585d19c10be7fe2b7f5709f))
* **front:** add missing title to Participants View page ([a5a86a5](https://framagit.org/framasoft/mobilizon/commit/a5a86a5e1be08cf9123ee7ad0979974bc2be1cb4))
* **front:** fix displaying user activity settings checkboxes ([8e21c30](https://framagit.org/framasoft/mobilizon/commit/8e21c30f92f47dcb742d8f7df2aed59191158d80)), closes [#1251](https://framagit.org/framasoft/mobilizon/issues/1251)
* **front:** fix wrong key name for dialog.confirm() option ([c8f49e1](https://framagit.org/framasoft/mobilizon/commit/c8f49e1837d719cd737c3e1ae976f14b20345e2b))
* **front:** fix wrong value for timezone when it has no prefix ([2dd0e13](https://framagit.org/framasoft/mobilizon/commit/2dd0e13eba8bb5c04af45bae0de059deb93c2efa)), closes [#1275](https://framagit.org/framasoft/mobilizon/issues/1275)
* **group:** fix getting group members count ([f749518](https://framagit.org/framasoft/mobilizon/commit/f749518bf7a29a86da559bfe6aba6d7485e7cfeb)), closes [#1303](https://framagit.org/framasoft/mobilizon/issues/1303)
* **participant exports:** fix participants by returning the export type as well as the file path ([49b04c9](https://framagit.org/framasoft/mobilizon/commit/49b04c9b19517daa0a07656779d53001b39ab803))
* **participant:** handle re-confirming participation ([5cc5c99](https://framagit.org/framasoft/mobilizon/commit/5cc5c9943cbc9a53246dda98958e99d004f0dfa9))
### Features
* **graphql:** validate timezone id as a GraphQL Scalar ([845bb6a](https://framagit.org/framasoft/mobilizon/commits/845bb6ac90081ef8cb4cff8d6ec3d11bfc19857c)), closes [#1299](https://framagit.org/framasoft/mobilizon/issues/1299)
* **graphql:** validate timezone id as a GraphQL Scalar ([845bb6a](https://framagit.org/framasoft/mobilizon/commit/845bb6ac90081ef8cb4cff8d6ec3d11bfc19857c)), closes [#1299](https://framagit.org/framasoft/mobilizon/issues/1299)
## 3.1.1 (2023-06-02)

View file

@ -1,6 +1,6 @@
{
"name": "mobilizon",
"version": "3.1.3",
"version": "3.2.0-beta.1",
"private": true,
"scripts": {
"dev": "vite",

View file

@ -4,7 +4,7 @@
:class="{
reply: comment.inReplyToComment,
'bg-mbz-purple-50 dark:bg-mbz-purple-500': comment.isAnnouncement,
'bg-mbz-bluegreen-50 dark:bg-mbz-bluegreen-600': commentSelected,
'!bg-mbz-bluegreen-50 dark:!bg-mbz-bluegreen-600': commentSelected,
'shadow-none': !rootComment,
}"
>
@ -62,6 +62,7 @@
class="cursor-pointer flex hover:bg-zinc-300 dark:hover:bg-zinc-600 rounded p-1"
v-if="
currentActor?.id &&
!readOnly &&
event.options.commentModeration !== CommentModeration.CLOSED &&
!comment.deletedAt
"
@ -70,7 +71,7 @@
<Reply />
<span>{{ t("Reply") }}</span>
</button>
<o-dropdown aria-role="list">
<o-dropdown aria-role="list" v-show="!readOnly">
<template #trigger>
<button
class="cursor-pointer flex hover:bg-zinc-300 dark:hover:bg-zinc-600 rounded p-1"
@ -221,7 +222,7 @@ import {
ref,
nextTick,
} from "vue";
import { useRoute } from "vue-router";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Delete from "vue-material-design-icons/Delete.vue";
@ -235,6 +236,9 @@ import ReportModal from "@/components/Report/ReportModal.vue";
import { useCreateReport } from "@/composition/apollo/report";
import { Snackbar } from "@/plugins/snackbar";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import RouteName from "@/router/name";
const router = useRouter();
const Editor = defineAsyncComponent(
() => import("@/components/TextEditor.vue")
@ -246,10 +250,13 @@ const props = withDefaults(
event: IEvent;
currentActor: IPerson;
rootComment?: boolean;
readOnly: boolean;
}>(),
{ rootComment: true }
{ rootComment: true, readOnly: false }
);
const event = computed(() => props.event);
const emit = defineEmits<{
(e: "create-comment", comment: IComment): void;
(e: "delete-comment", comment: IComment): void;
@ -319,7 +326,12 @@ const commentId = computed((): string => {
const commentURL = computed((): string => {
if (!props.comment.local && props.comment.url) return props.comment.url;
return `#${commentId.value}`;
return (
router.resolve({
name: RouteName.EVENT,
params: { uuid: event.value.uuid },
}).href + `#${commentId.value}`
);
});
const reportModal = (): void => {
@ -352,7 +364,6 @@ const reportComment = async (
): Promise<void> => {
if (!props.comment.actor) return;
createReportMutation({
eventId: props.event.id,
reportedId: props.comment.actor?.id ?? "",
commentsIds: [props.comment.id ?? ""],
content,

View file

@ -648,7 +648,7 @@ const reportEvent = async (
if (!organizer.value) return;
createReportMutation({
eventId: event.value?.id ?? "",
eventsIds: [event.value?.id ?? ""],
reportedId: organizer.value?.id ?? "",
content,
forward,

View file

@ -5,7 +5,7 @@
>
<div class="flex justify-between gap-1 border-b p-2">
<div class="flex gap-1">
<figure class="" v-if="report.reported.avatar">
<figure class="" v-if="report.reported?.avatar">
<img
alt=""
:src="report.reported.avatar.url"
@ -16,14 +16,20 @@
</figure>
<AccountCircle v-else :size="24" />
<div class="">
<p class="" v-if="report.reported.name">{{ report.reported.name }}</p>
<p class="text-zinc-700 dark:text-zinc-100 text-sm">
<p class="" v-if="report.reported?.name">
{{ report.reported.name }}
</p>
<p
class="text-zinc-700 dark:text-zinc-100 text-sm"
v-else-if="report.reported?.preferredUsername"
>
@{{ usernameWithDomain(report.reported) }}
</p>
<p v-else>{{ t("Unknown actor") }}</p>
</div>
</div>
<div>
<p v-if="report.reported.suspended" class="text-red-700 font-bold">
<p v-if="report.reported?.suspended" class="text-red-700 font-bold">
{{ t("Suspended") }}
</p>
</div>
@ -31,7 +37,7 @@
<div class="p-2">
<div class="">
<span v-if="report.reporter.type === ActorType.APPLICATION">
<span v-if="report.reporter?.type === ActorType.APPLICATION">
{{
t("Reported by someone on {domain}", {
domain: report.reporter.domain,
@ -40,19 +46,22 @@
</span>
<span
v-if="
report.reporter.preferredUsername === 'anonymous' &&
!report.reporter.domain
report.reporter?.preferredUsername === 'anonymous' &&
!report.reporter?.domain
"
>
{{ t("Reported by someone anonymously") }}
</span>
<span v-else>
<span v-else-if="report.reporter?.preferredUsername">
{{
t("Reported by {reporter}", {
reporter: usernameWithDomain(report.reporter),
})
}}
</span>
<span v-else>
{{ t("Reported by an unknown actor") }}
</span>
</div>
<div class="" v-if="report.content" v-html="report.content" />
</div>

View file

@ -5,7 +5,7 @@ export function useCreateReport() {
return useMutation<
{ createReport: { id: string } },
{
eventId?: string;
eventsIds?: string[];
reportedId: string;
content?: string;
commentsIds?: string[];

View file

@ -20,7 +20,7 @@ export const REPORTS = gql`
...ActorFragment
suspended
}
event {
events {
id
uuid
title
@ -42,11 +42,19 @@ const REPORT_FRAGMENT = gql`
id
reported {
...ActorFragment
suspended
... on Person {
user {
id
disabled
}
}
}
reporter {
...ActorFragment
suspended
}
event {
events {
id
uuid
title
@ -69,6 +77,14 @@ const REPORT_FRAGMENT = gql`
actor {
...ActorFragment
}
updatedAt
deletedAt
uuid
event {
id
uuid
title
}
}
notes {
id
@ -97,14 +113,14 @@ export const REPORT = gql`
export const CREATE_REPORT = gql`
mutation CreateReport(
$eventId: ID
$eventsIds: [ID]
$reportedId: ID!
$content: String
$commentsIds: [ID]
$forward: Boolean
) {
createReport(
eventId: $eventId
eventsIds: $eventsIds
reportedId: $reportedId
content: $content
commentsIds: $commentsIds

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "حذف",
"Delete Comment": "احذف التعليق",
"Delete Event": "حذف الفعالية",
"Delete account": "حذف الحساب",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "Выдаліць",
"Delete Comment": "Выдаліць каментарый",
"Delete Event": "Выдаліць падзею",
"Delete account": "Выдаліць рахунак",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "মুছো",
"Delete Comment": "",
"Delete Event": "",
"Delete account": "",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Política de privacitat per defecte de Mobilizon",
"Default Mobilizon terms": "Condicions per defecte de Mobilizon",
"Delete": "Esborra-ho",
"Delete Comment": "Esborra el comentari",
"Delete Event": "Esborra l'activitat",
"Delete account": "Eliminar el compte",
"Delete conversation": "Esborra la conversa",
"Delete discussion": "ESborra la discussió",

View file

@ -291,8 +291,6 @@
"Default Mobilizon privacy policy": "Výchozí zásady ochrany osobních údajů Mobilizon",
"Default Mobilizon terms": "Výchozí podmínky Mobilizon",
"Delete": "Smazat",
"Delete Comment": "Smazat komentář",
"Delete Event": "Smazat událost",
"Delete account": "Smazat účet",
"Delete comments": "Mazání komentářů",
"Delete conversation": "Smazat konverzaci",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "Dileu",
"Delete Comment": "Dileu Sylw",
"Delete Event": "Dileu Digwyddiad",
"Delete account": "Dileu cyfrif",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -76,8 +76,6 @@
"Date parameters": "Datoparametre",
"Default": "Standard",
"Delete": "Slet",
"Delete Comment": "Slet kommentar",
"Delete Event": "Slet begivenhed",
"Delete account": "Slet konto",
"Delete event": "Slet begivenhed",
"Delete everything": "Slet alt",

View file

@ -280,8 +280,6 @@
"Default Mobilizon privacy policy": "Standard-Datenschutzerklärung von Mobilizon",
"Default Mobilizon terms": "Standardbedingungen von Mobilizon",
"Delete": "Löschen",
"Delete Comment": "Kommentar löschen",
"Delete Event": "Veranstaltung löschen",
"Delete account": "Konto löschen",
"Delete comments": "Kommentare löschen",
"Delete conversation": "Lösche Konversation",

View file

@ -78,8 +78,6 @@
"Date parameters": "Date parameters",
"Date": "Date",
"Default": "Default",
"Delete Comment": "Delete Comment",
"Delete Event": "Delete Event",
"Delete account": "Delete account",
"Delete event": "Delete event",
"Delete everything": "Delete everything",
@ -1582,5 +1580,30 @@
"This application will be allowed to list your suggested group events": "This application will be allowed to list your suggested group events",
"{profile} joined the the event {event}.": "{profile} joined the the event {event}.",
"You joined the event {event}.": "You joined the event {event}.",
"An anonymous profile joined the event {event}.": "An anonymous profile joined the event {event}."
"An anonymous profile joined the event {event}.": "An anonymous profile joined the event {event}.",
"Delete event and resolve report": "Delete event and resolve report",
"No content found": "No content found",
"Maybe the content was removed by the author or a moderator": "Maybe the content was removed by the author or a moderator",
"This will also resolve the report.": "This will also resolve the report.",
"Are you sure you want to <b>delete</b> this event? <b>This action cannot be undone</b>. You may want to engage the discussion with the event creator and ask them to edit their event instead.": "Are you sure you want to <b>delete</b> this event? <b>This action cannot be undone</b>. You may want to engage the discussion with the event creator and ask them to edit their event instead.",
"Are you sure you want to <b>delete</b> this comment? <b>This action cannot be undone</b>.": "Are you sure you want to <b>delete</b> this comment? <b>This action cannot be undone</b>.",
"Delete comment and resolve report": "Delete comment and resolve report",
"Delete comment": "Delete comment",
"Event deleted and report resolved": "Event deleted and report resolved",
"Event deleted": "Event deleted",
"Comment deleted and report resolved": "Comment deleted and report resolved",
"Comment under event {eventTitle}": "Comment under event {eventTitle}",
"Do you really want to suspend this profile? All of the profiles content will be deleted.": "Do you really want to suspend this profile? All of the profiles content will be deleted.",
"There will be no way to restore the profile's data!": "There will be no way to restore the profile's data!",
"Suspend the profile": "Suspend the profile",
"The following user's profiles will be deleted, with all their data:": "The following user's profiles will be deleted, with all their data:",
"Do you really want to suspend the account « {emailAccount} » ?": "Do you really want to suspend the account « {emailAccount} » ?",
"There will be no way to restore the user's data!": "There will be no way to restore the user's data!",
"User suspended and report resolved": "User suspended and report resolved",
"Profile suspended and report resolved": "Profile suspended and report resolved",
"{profileName} (suspended)": "{profileName} (suspended)",
"Reported by an unknown actor": "Reported by an unknown actor",
"Reported at": "Reported at",
"Updated at": "Updated at",
"Suspend the profile?": "Suspend the profile?"
}

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "",
"Delete Comment": "",
"Delete Event": "",
"Delete account": "",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -260,8 +260,6 @@
"Default Mobilizon privacy policy": "Política de privacidad predeterminada de Mobilizon",
"Default Mobilizon terms": "Términos predeterminados de Mobilizon",
"Delete": "Eliminar",
"Delete Comment": "Eliminar comentario",
"Delete Event": "Eliminar evento",
"Delete account": "Eliminar cuenta",
"Delete conversation": "Borrar la conversación",
"Delete discussion": "Eliminar discusión",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "",
"Delete Comment": "",
"Delete Event": "",
"Delete account": "",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "حذف",
"Delete Comment": "حذف نظر",
"Delete Event": "حذف رویداد",
"Delete account": "حذف حساب",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Mobilizonin oletustietosuojakäytäntö",
"Default Mobilizon terms": "Mobilizonin oletusehdot",
"Delete": "Poista",
"Delete Comment": "Poista kommentti",
"Delete Event": "Poista tapahtuma",
"Delete account": "Poista tili",
"Delete conversation": "Poista keskustelu",
"Delete discussion": "Poista keskustelu",

View file

@ -47,6 +47,7 @@
"Accept": "Accepter",
"Accept follow": "Accepter le suivi",
"Accepted": "Accepté",
"Access drafts events": "Accéder aux événements brouillons",
"Access followed groups": "Accéder à la liste des groupes suivis",
"Access group activities": "Accéder aux activités des groupes",
"Access group discussions": "Accéder aux discussions des groupes",
@ -54,6 +55,7 @@
"Access group followers": "Accéder à la liste des abonnés des groupes",
"Access group members": "Accéder à la liste des membres des groupes",
"Access group memberships": "Accéder à la liste de vos adhésions à des groupes",
"Access group suggested events": "Accéder aux événements des groupes suggérés",
"Access group todo-lists": "Accéder aux listes de tâches des groupes",
"Access organized events": "Accéder à la liste de vos événements organisés",
"Access participations": "Accéder à la liste de vos participations",
@ -99,6 +101,7 @@
"Allow all comments from users with accounts": "Autoriser tous les commentaires d'utilisateur·rice·s avec des comptes",
"Allow registrations": "Autoriser les inscriptions",
"An URL to an external ticketing platform": "Une URL vers une plateforme de billetterie externe",
"An anonymous profile joined the event {event}.": "Un profil anonyme a rejoint l'événement {event}.",
"An error has occured while refreshing the page.": "Une erreur est survenue lors du rafraîchissement de la page.",
"An error has occured. Sorry about that. You may try to reload the page.": "Une erreur est survenue. Nous en sommes désolé⋅es. Vous pouvez essayer de rafraîchir la page.",
"An ethical alternative": "Une alternative éthique",
@ -126,6 +129,7 @@
"Anyone can request being a member, but an administrator needs to approve the membership.": "N'importe qui peut demander à être membre, mais un⋅e administrateur⋅ice devra approuver leur adhésion.",
"Anyone wanting to be a member from your group will be able to from your group page.": "N'importe qui voulant devenir membre pourra le faire depuis votre page de groupe.",
"Application": "Application",
"Application authorized": "Application autorisée",
"Application not found": "Application non trouvée",
"Application was revoked": "L'application a été révoquée",
"Apply filters": "Appliquer les filtres",
@ -133,7 +137,9 @@
"Apps": "Applications",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Êtes-vous vraiment certain⋅e de vouloir supprimer votre compte ? Vous allez tout perdre. Identités, paramètres, événements créés, messages et participations disparaîtront pour toujours.",
"Are you sure you want to <b>completely delete</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Êtes-vous certain·e de vouloir <b>complètement supprimer</b> ce groupe ? Tous les membres - y compris ceux·elles sur d'autres instances - seront notifié·e·s et supprimé·e·s du groupe, et <b>toutes les données associées au groupe (événements, billets, discussions, todos…) seront irrémédiablement détruites</b>.",
"Are you sure you want to <b>delete</b> this comment? <b>This action cannot be undone</b>.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> ce commentaire ? <b>Cette action ne peut pas être annulée.</b>",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> ce commentaire ? Cette action ne peut pas être annulée.",
"Are you sure you want to <b>delete</b> this event? <b>This action cannot be undone</b>. You may want to engage the discussion with the event creator and ask them to edit their event instead.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> cet événement ? Cette action n'est pas réversible. Vous voulez peut-être engager la discussion avec le créateur de l'événement et lui demander de modifier son événement à la place.",
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> cet événement ? Cette action n'est pas réversible. Vous voulez peut-être engager la discussion avec le créateur de l'événement ou bien modifier son événement à la place.",
"Are you sure you want to <b>suspend</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Êtes-vous certain·e de vouloir <b>suspendre</b> ce groupe ? Tous les membres - y compris ceux·elles sur d'autres instances - seront notifié·e·s et supprimé·e·s du groupe, et <b>toutes les données associées au groupe (événements, billets, discussions, todos…) seront irrémédiablement détruites</b>.",
"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.": "Êtes-vous certain·e de vouloir <b>suspendre</b> ce groupe ? Comme ce groupe provient de l'instance {instance}, cela supprimera seulement les membres locaux et supprimera les données locales, et rejettera également toutes les données futures.",
@ -203,6 +209,7 @@
"Change timezone": "Changer de fuseau horaire",
"Change user email": "Modifier l'email de l'utilisateur⋅ice",
"Change user role": "Changer le role de l'utilisateur",
"Check your device to continue. You may now close this window.": "Vérifiez votre appareil pour continuer. Vous pouvez maintenant fermer cette fenêtre.",
"Check your inbox (and your junk mail folder).": "Vérifiez votre boîte de réception (et votre dossier des indésirables).",
"Choose the source of the instance's Privacy Policy": "Choisissez la source de la politique de confidentialité de l'instance",
"Choose the source of the instance's Terms": "Choisissez la source des conditions d'utilisation de l'instance",
@ -221,8 +228,10 @@
"Closed": "Fermé",
"Comment body": "Corps du commentaire",
"Comment deleted": "Commentaire supprimé",
"Comment deleted and report resolved": "Commentaire supprimé et signalement résolu",
"Comment from {'@'}{username} reported": "Commentaire de {'@'}{username} signalé",
"Comment text can't be empty": "Le texte du commentaire ne peut être vide",
"Comment under event {eventTitle}": "Commentaire sous l'événement {eventTitle}",
"Comments": "Commentaires",
"Comments are closed for everybody else.": "Les commentaires sont fermés pour tou·te·s les autres.",
"Confirm": "Confirmer",
@ -291,13 +300,14 @@
"Default Mobilizon privacy policy": "Politique de confidentialité par défaut de Mobilizon",
"Default Mobilizon terms": "Conditions d'utilisation par défaut de Mobilizon",
"Delete": "Supprimer",
"Delete Comment": "Supprimer le commentaire",
"Delete Event": "Supprimer l'événement",
"Delete account": "Suppression du compte",
"Delete comment": "Supprimer le commentaire",
"Delete comment and resolve report": "Supprimer le commentaire et résoudre le signalement",
"Delete comments": "Supprimer des commentaires",
"Delete conversation": "Supprimer la conversation",
"Delete discussion": "Supprimer la discussion",
"Delete event": "Supprimer un événement",
"Delete event and resolve report": "Supprimer l'événement et résoudre le signalement",
"Delete events": "Supprimer des événements",
"Delete everything": "Tout supprimer",
"Delete feed tokens": "Supprimer les jetons de flux",
@ -332,7 +342,9 @@
"Displayed on homepage and meta tags. Describe what Mobilizon is and what makes this instance special in a single paragraph.": "Affichée sur la page d'accueil et dans les balises meta. Décrivez ce qu'est Mobilizon et ce qui rend spécifique cette instance en un seul paragraphe.",
"Distance": "Distance",
"Do not receive any mail": "Ne pas recevoir d'e-mail",
"Do you really want to suspend the account « {emailAccount} » ?": "Voulez-vous vraiment suspendre le compte « {emailAccount} » ?",
"Do you really want to suspend this account? All of the user's profiles will be deleted.": "Voulez-vous vraiment suspendre ce compte ? Tous les profils de cet⋅te utilisateur⋅ice seront supprimés.",
"Do you really want to suspend this profile? All of the profiles content will be deleted.": "Voulez-vous vraiment suspendre ce profil ? Tout le contenu du profil sera supprimé.",
"Do you wish to {create_event} or {explore_events}?": "Voulez-vous {create_event} ou {explore_events} ?",
"Do you wish to {create_group} or {explore_groups}?": "Voulez-vous {create_group} ou {explore_groups} ?",
"Does the event needs to be confirmed later or is it cancelled?": "Est-ce que l'événement doit être confirmé plus tard ou bien est-il annulé ?",
@ -388,6 +400,8 @@
"Event cancelled": "Événement annulé",
"Event creation": "Création d'événement",
"Event date": "Date de l'événement",
"Event deleted": "Événement supprimé",
"Event deleted and report resolved": "Événement supprimé et signalement résolu",
"Event description body": "Corps de la description de l'événement",
"Event edition": "Modification d'événement",
"Event list": "Liste d'événements",
@ -622,6 +636,7 @@
"Manually invite new members": "Inviter des nouveaux·elles membres manuellement",
"Map": "Carte",
"Mark as resolved": "Marquer comme résolu",
"Maybe the content was removed by the author or a moderator": "Peut-être que le contenu a été supprimé par l'auteur·ice ou un·e modérateur·ice",
"Member": "Membre",
"Members": "Membres",
"Members-only post": "Billet reservé aux membres",
@ -683,6 +698,7 @@
"No closed reports yet": "Aucun signalement fermé pour le moment",
"No comment": "Pas de commentaire",
"No comments yet": "Pas encore de commentaires",
"No content found": "Aucun contenu trouvé",
"No discussions yet": "Pas encore de discussions",
"No end date": "Pas de date de fin",
"No event found at this address": "Aucun événement trouvé à cette addresse",
@ -798,6 +814,7 @@
"Partially accessible with a wheelchair": "Partiellement accessible avec un fauteuil roulant",
"Participant": "Participant⋅e",
"Participants": "Participant⋅e⋅s",
"Participants to {eventTitle}": "Participant·es à {eventTitle}",
"Participate": "Participer",
"Participate using your email address": "Participer en utilisant votre adresse email",
"Participation approval": "Validation des participations",
@ -853,6 +870,7 @@
"Private feeds": "Flux privés",
"Profile": "Profil",
"Profile feeds": "Flux du profil",
"Profile suspended and report resolved": "Profil suspendu et signalement résolu",
"Profiles": "Profils",
"Profiles and federation": "Profils et fédération",
"Promote": "Promouvoir",
@ -875,6 +893,11 @@
"RSS/Atom Feed": "Flux RSS/Atom",
"Radius": "Rayon",
"Read all of your account's data": "Lire toutes les données de votre compte",
"Read user activity settings": "Lire les paramètres d'activité utilisateur·ice",
"Read user media": "Lire les médias utilisateur·ice",
"Read user memberships": "Accéder aux adhésions de l'utilisateur·ice",
"Read user participations": "Accéder aux participations de l'utilisateur·ice",
"Read user settings": "Lire les paramètres utilisateur·ice",
"Recap every week": "Récapitulatif hebdomadaire",
"Receive one email for each activity": "Recevoir un e-mail à chaque activité",
"Receive one email per request": "Recevoir un e-mail par demande",
@ -918,7 +941,9 @@
"Report this group": "Signaler ce groupe",
"Report this post": "Signaler ce billet",
"Reported": "Signalée",
"Reported at": "Signalé à",
"Reported by": "Signalée par",
"Reported by an unknown actor": "Signalé par un·e acteur·ice inconnu·e",
"Reported by someone anonymously": "Signalé par quelqu'un anonymement",
"Reported by someone on {domain}": "Signalé par quelqu'un depuis {domain}",
"Reported by {reporter}": "Signalé par {reporter}",
@ -1007,6 +1032,8 @@
"Suspend group": "Suspendre le groupe",
"Suspend the account": "Suspendre le compte",
"Suspend the account?": "Suspendre le compte ?",
"Suspend the profile": "Suspendre le profil",
"Suspend the profile?": "Suspendre le profil ?",
"Suspended": "Suspendu·e",
"Tag search": "Recherche par tag",
"Task lists": "Listes de tâches",
@ -1053,6 +1080,7 @@
"The event {event} was deleted by {profile}.": "L'événement {event} a été supprimé par {profile}.",
"The event {event} was updated by {profile}.": "L'événement {event} à été mis à jour par {profile}.",
"The events you created are not shown here.": "Les événements que vous avez créé ne s'affichent pas ici.",
"The following user's profiles will be deleted, with all their data:": "Les profils suivants de l'utilisateur·ice seront supprimés, avec toutes leurs données :",
"The geolocation prompt was denied.": "La demande de localisation a été refusée.",
"The group can now be joined by anyone, but new members need to be approved by an administrator.": "Le groupe peut maintenant être rejoint par n'importe qui, mais les nouvelles et nouveaux membres doivent être approuvées par un⋅e modérateur⋅ice.",
"The group can now be joined by anyone.": "Le groupe peut maintenant être rejoint par n'importe qui.",
@ -1087,6 +1115,8 @@
"There are {participants} participants.": "Il n'y a qu'un⋅e participant⋅e. | Il y a {participants} participant⋅es.",
"There is no activity yet. Start doing some things to see activity appear here.": "Il n'y a pas encore d'activité. Commencez par effectuer des actions pour voir des éléments s'afficher ici.",
"There will be no way to recover your data.": "Il n'y aura aucun moyen de récupérer vos données.",
"There will be no way to restore the profile's data!": "Il n'y aura aucun moyen de restorer les données du profil !",
"There will be no way to restore the user's data!": "Il n'y aura aucun moyen de restorer les données de l'utilisateur·ice !",
"There's no discussions yet": "Il n'y a pas encore de discussions",
"These apps can access your account through the API. If you see here apps that you don't recognize, that don't work as expected or that you don't use anymore, you can revoke their access.": "Ces applications peuvent accéder à votre compte via l'API. Si vous voyez ici des applications que vous ne reconnaissez pas, qui ne fonctionnent pas comme prévu ou que vous n'utilisez plus, vous pouvez révoquer leur accès.",
"These events may interest you": "Ces événements peuvent vous intéresser",
@ -1100,6 +1130,8 @@
"This application will be able to access all of your informations and post content. Make sure you only approve applications you trust.": "Cette application sera capable d'accéder à toutes vos informations et poster du contenu. Assurez-vous d'approuver uniquement des applications en lesquelles vous avez confiance.",
"This application will be allowed to access all of the groups you're a member of": "Cette application pourra accéder à tous les groupes dont vous êtes membres",
"This application will be allowed to access group activities in all of the groups you're a member of": "This application will be allowed to access group activities in all of the groups you're a member of",
"This application will be allowed to access your user activity settings": "Cette application sera autorisée a accéder à vos paramètres utilisateur·ice d'activité",
"This application will be allowed to access your user settings": "Cette application sera autorisée a accéder à vos paramètres utilisateur·ice",
"This application will be allowed to create feed tokens": "This application will be allowed to create feed tokens",
"This application will be allowed to create group discussions": "This application will be allowed to create group discussions",
"This application will be allowed to create new profiles for your account": "This application will be allowed to create new profiles for your account",
@ -1118,9 +1150,12 @@
"This application will be allowed to list and view the events you're participating to": "This application will be allowed to list and view the events you're participating to",
"This application will be allowed to list and view the groups you're a member of": "This application will be allowed to list and view the groups you're a member of",
"This application will be allowed to list and view the groups you're following": "This application will be allowed to list and view the groups you're following",
"This application will be allowed to list and view your draft events": "Cetta application sera autorisée à lister et accéder à vos événements brouillons",
"This application will be allowed to list and view your organized events": "This application will be allowed to list and view your organized events",
"This application will be allowed to list group followers in all of the groups you're a member of": "This application will be allowed to list group followers in all of the groups you're a member of",
"This application will be allowed to list group members in all of the groups you're a member of": "This application will be allowed to list group members in all of the groups you're a member of",
"This application will be allowed to list the media you've uploaded": "Cette application sera autorisée a lister les médias que vous avez téléversé",
"This application will be allowed to list your suggested group events": "Cetta application sera autorisée à lister les événements de vos groupes qui vous sont suggérés",
"This application will be allowed to manage events participations": "Cette application sera autorisée à gérer vos participations à des événements",
"This application will be allowed to manage group members in all of the groups you're a member of": "This application will be allowed to manage group members in all of the groups you're a member of",
"This application will be allowed to manage your account activity settings": "Cette application sera autorisée à gérer vos paramètres d'activité",
@ -1167,6 +1202,7 @@
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Ce site nest pas modéré et les données que vous y rentrerez seront automatiquement détruites tous les jours à 00:01 (heure de Paris).",
"This week": "Cette semaine",
"This weekend": "Ce week-end",
"This will also resolve the report.": "Cela résoudra également le signalement.",
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "Cela supprimera / anonymisera tout le contenu (événements, commentaires, messages, participations…) créés avec cette identité.",
"Time in your timezone ({timezone})": "Heure dans votre fuseau horaire ({timezone})",
"Times in your timezone ({timezone})": "Heures dans votre fuseau horaire ({timezone})",
@ -1233,12 +1269,14 @@
"Update post": "Mettre à jour le billet",
"Update profiles": "Mettre à jour des profils",
"Updated": "Mis à jour",
"Updated at": "Mis à jour à",
"Upload media": "Téléverser des médias",
"Uploaded media size": "Taille des médias téléversés",
"Uploaded media total size": "Taille totale des médias téléversés",
"Use my location": "Utiliser ma position",
"User": "Utilisateur·rice",
"User settings": "Paramètres utilisateur⋅ices",
"User suspended and report resolved": "Utilisateur suspendu et signalement résolu",
"Username": "Identifiant",
"Users": "Utilisateur⋅rice⋅s",
"Validating account": "Validation du compte",
@ -1355,6 +1393,7 @@
"You have one event tomorrow.": "Vous n'avez pas d'événement demain | Vous avez un événement demain. | Vous avez {count} événements demain",
"You haven't interacted with other instances yet.": "Vous n'avez interagi avec encore aucune autre instance.",
"You invited {member}.": "Vous avez invité {member}.",
"You joined the event {event}.": "Vous avez rejoint l'événement {event}.",
"You may also:": "Vous pouvez aussi :",
"You may clear all participation information for this device with the buttons below.": "Vous pouvez effacer toutes les informations de participation pour cet appareil avec les boutons ci-dessous.",
"You may now close this page or {return_to_the_homepage}.": "Vous pouvez maintenant fermer cette page ou {return_to_the_homepage}.",
@ -1522,6 +1561,7 @@
"{number} posts": "Aucun billet|Un billet|{number} billets",
"{number} seats left": "{number} places restantes",
"{old_group_name} was renamed to {group}.": "{old_group_name} a été renommé en {group}.",
"{profileName} (suspended)": "{profileName} (suspendu·e)",
"{profile} (by default)": "{profile} (par défault)",
"{profile} added the member {member}.": "{profile} a ajouté le ou la membre {member}.",
"{profile} approved {member}'s membership.": "{profile} a approuvé la demande d'adhésion de {member}.",
@ -1537,6 +1577,7 @@
"{profile} demoted {member} to moderator.": "{profile} a rétrogradé {member} en tant que modérateur⋅ice.",
"{profile} demoted {member} to simple member.": "{profile} a rétrogradé {member} en tant que simple membre.",
"{profile} excluded member {member}.": "{profile} a exclu le ou la membre {member}.",
"{profile} joined the the event {event}.": "{profile} a rejoint l'événement {event}.",
"{profile} moved the folder {resource} into {new_path}.": "{profile} a déplacé le dossier {resource} dans {new_path}.",
"{profile} moved the folder {resource} to the root folder.": "{profile} a déplacé le dossier {resource} dans le dossier racine.",
"{profile} moved the resource {resource} into {new_path}.": "{profile} a déplacé la ressource {resource} dans {new_path}.",
@ -1560,23 +1601,5 @@
"{username} was invited to {group}": "{username} a été invité à {group}",
"{user}'s follow request was accepted": "La demande de suivi de {user} a été acceptée",
"{user}'s follow request was rejected": "La demande de suivi de {user} a été rejetée",
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
"Application authorized": "Application autorisée",
"Check your device to continue. You may now close this window.": "Vérifiez votre appareil pour continuer. Vous pouvez maintenant fermer cette fenêtre.",
"Participants to {eventTitle}": "Participant·es à {eventTitle}",
"Read user media": "Lire les médias utilisateur·ice",
"This application will be allowed to list the media you've uploaded": "Cette application sera autorisée a lister les médias que vous avez téléversé",
"Read user settings": "Lire les paramètres utilisateur·ice",
"This application will be allowed to access your user settings": "Cette application sera autorisée a accéder à vos paramètres utilisateur·ice",
"Read user activity settings": "Lire les paramètres d'activité utilisateur·ice",
"This application will be allowed to access your user activity settings": "Cette application sera autorisée a accéder à vos paramètres utilisateur·ice d'activité",
"Read user participations": "Accéder aux participations de l'utilisateur·ice",
"Read user memberships": "Accéder aux adhésions de l'utilisateur·ice",
"Access drafts events": "Accéder aux événements brouillons",
"This application will be allowed to list and view your draft events": "Cetta application sera autorisée à lister et accéder à vos événements brouillons",
"Access group suggested events": "Accéder aux événements des groupes suggérés",
"This application will be allowed to list your suggested group events": "Cetta application sera autorisée à lister les événements de vos groupes qui vous sont suggérés",
"{profile} joined the the event {event}.": "{profile} a rejoint l'événement {event}.",
"You joined the event {event}.": "Vous avez rejoint l'événement {event}.",
"An anonymous profile joined the event {event}.": "Un profil anonyme a rejoint l'événement {event}."
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
}

View file

@ -247,8 +247,6 @@
"Default Mobilizon privacy policy": "Poileasaidh prìobhaideachd Mhobilizon bhunaiteach",
"Default Mobilizon terms": "Teirmichean Mhobilizon bunaiteach",
"Delete": "Sguab às",
"Delete Comment": "Sguab às am beachd",
"Delete Event": "Sguab às an tachartas",
"Delete account": "Sguab às an cunntas",
"Delete conversation": "Sguab às an còmhradh",
"Delete discussion": "Sguab às an deasbad",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Política de privacidade por omisión para Mobilizon",
"Default Mobilizon terms": "Termos Mobilizon por omisión",
"Delete": "Eliminar",
"Delete Comment": "Borrar comentario",
"Delete Event": "Borrar evento",
"Delete account": "Borrar conta",
"Delete conversation": "Eliminar conversa",
"Delete discussion": "Eliminar debate",

View file

@ -78,8 +78,6 @@
"Date parameters": "מאפייני תאריך",
"Default": "ברירת מחדל",
"Delete": "מחיקה",
"Delete Comment": "מחיקת תגובה",
"Delete Event": "מחיקת אירוע",
"Delete account": "מחיקת חשבון",
"Delete event": "מחיקת אירוע",
"Delete everything": "מחיקת הכל",

View file

@ -81,6 +81,7 @@
"Admin settings successfully saved.": "Administratorske postavke su uspješno spremljene.",
"Administration": "Administrator",
"Administrator": "Administrator",
"All": "Sve",
"All activities": "Sve aktivnosti",
"All good, let's continue!": "Sve je u redu. Nastavimo!",
"All the places have already been taken": "Sva mjesta su već zauzeta",
@ -133,12 +134,12 @@
"Atom feed for events and posts": "Atom feed za događaje i objave",
"Attending": "Prisustvovanje",
"Avatar": "Avatar",
"Back to group list": "",
"Back to group list": "Natrag na popis grupa",
"Back to homepage": "Natrag na početnu web-stranicu",
"Back to previous page": "Natrag na prethodnu stranicu",
"Back to profile list": "",
"Back to profile list": "Natrag na popis profila",
"Back to top": "Natrag na vrh stranice",
"Back to user list": "",
"Back to user list": "Natrag na popis korisnika",
"Banner": "Natpis",
"Before you can login, you need to click on the link inside it to validate your account.": "Morate ovjeriti svoj račun na dobivenoj poveznici kako biste se mogli prijaviti.",
"Begins on": "Započinje na",
@ -262,8 +263,6 @@
"Default Mobilizon privacy policy": "Zadana Mobilizon politika privatnosti",
"Default Mobilizon terms": "Zadani Mobilizon uvjeti",
"Delete": "Izbriši",
"Delete Comment": "Izbriši komentar",
"Delete Event": "Izbriši događaj",
"Delete account": "Izbriši račun",
"Delete conversation": "Izbriši razgovor",
"Delete discussion": "Izbriši razgovor",
@ -387,6 +386,9 @@
"Find or add an element": "Pronađi ili dodaj jedan element",
"First steps": "Prvi koraci",
"Follow": "Prati",
"Follow a new instance": "Prati novu instancu",
"Follow instance": "Prati instancu",
"Follow status": "Prati stanje",
"Follower": "Pratilac",
"Followers": "Pratioci",
"Followers will receive new public events and posts.": "Pratioci će primati obavijesti o novim javnim događajima i objave.",
@ -472,9 +474,9 @@
"If you have opted for manual validation of participants, Mobilizon will send you an email to inform you of new participations to be processed. You can choose the frequency of these notifications below.": "Ako je odabrano ručno odobrenje sudionika, Mobilizon će ti poslati e-mail o novim sudionicima koji se moraju obraditi. Dolje možeš odabrati učestalost ovih obavijesti.",
"If you want, you may send a message to the event organizer here.": "Ako želite, ovdje možete poslati poruku organizatoru događaja.",
"Ignore": "Zanemari",
"In person": "",
"In person": "Osobno",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "U sljedećem kontekstu, aplikacija je softver pružen od Mobilizon tima ili 3. partije, koji vam omogućuje povezivanje sa instancom.",
"In the past": "",
"In the past": "U prošlosti",
"In this instance's network": "U mreži ove instance",
"Increase": "Povećaj",
"Instance": "Instanca",
@ -624,11 +626,12 @@
"No follower matches the filters": "Nijedan pratilac ne odgovara filtrima",
"No group found": "Bez rezultata",
"No group matches the filters": "Nijedna grupa ne odgovara filterima",
"No group member found": "",
"No group member found": "Nije pronađen nijedan član grupe",
"No groups found": "Bez rezultata",
"No groups found for {search}": "Nema grupa za {search}",
"No information": "Nema informacija",
"No instance follows your instance yet.": "Nijedna instanca još ne prati vašu instancu.",
"No instance found.": "Nije pronađena nijedna instanca.",
"No instance to approve|Approve instance|Approve {number} instances": "Nema instance za odobriti|Odobri instancu|Odobri {number} instanca",
"No instance to reject|Reject instance|Reject {number} instances": "Nema instance za odbiti|Odbij instancu|Odbij {number} instanca",
"No instance to remove|Remove instance|Remove {number} instances": "Nema instanca za ukloniti|Ukolni instancu|Ukloni {number} instanca",
@ -913,6 +916,7 @@
"Starts on…": "Započinje…",
"Status": "Stanje",
"Statuses": "Stanja",
"Stop following instance": "Prekini pratiti instancu",
"Street": "Ulica",
"Submit": "Podnesi",
"Submit to Akismet": "Pošalji Akismetu",
@ -975,9 +979,9 @@
"The group's physical address was changed.": "Promijenjena je fizička adresa grupe.",
"The group's short description was changed.": "Promijenjen je kratki opis grupe.",
"The instance administrator is the person or entity that runs this Mobilizon instance.": "Administrator instance je osoba ili oni koji vode ovu Mobilizon instancu.",
"The member was approved": "",
"The member was approved": "Član je odobren",
"The member was removed from the group {group}": "Član je izbačen iz grupe {group}",
"The membership request from {profile} was rejected": "",
"The membership request from {profile} was rejected": "Zahtjev za članstvo od {profile} je odbijen",
"The only way for your group to get new members is if an admininistrator invites them.": "Jedini način da vaša grupa dobije nove članove je da ih pozove adminitrator grupe.",
"The organiser has chosen to close comments.": "Organizatori su odlučili zatvoriti komentare.",
"The page you're looking for doesn't exist.": "Stranica koju tražite ne postoji.",
@ -1008,7 +1012,7 @@
"This group doesn't have a description yet.": "Ova grupa nema opis.",
"This group is accessible only through it's link. Be careful where you post this link.": "Ova je grupa dostupna samo putem njene poveznice. Pazi gdje objavljuješ ovu poveznicu.",
"This group is invite-only": "Ovoj grupi se ne možete pridružiti bez pozivnice",
"This group was not found": "",
"This group was not found": "Ova grupa nije pronađena",
"This identifier is unique to your profile. It allows others to find you.": "Ovaj identifikator jer jedinstven vašem profilu. Omogućuje drugima da vas nađu.",
"This information is saved only on your computer. Click for details": "Ove informacije su spremljene na vaše računalo. Kliknite za detalje",
"This instance hasn't got push notifications enabled.": "Ova instanca nema aktivirane automatske obavijesti.",
@ -1020,10 +1024,10 @@
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Ova objava je dostupna samo članovima. Imaš pristup u svrhu moderiranja budući da si moderator instance.",
"This post is accessible only through it's link. Be careful where you post this link.": "Ova je objava dostupna samo putem njene poveznice. Pazi gdje objavljuješ ovu poveznicu.",
"This profile is from another instance, the informations shown here may be incomplete.": "Ovaj profil je iz jedne druge instance, ovdje prikazane informacije mogu biti nepotpune.",
"This profile was not found": "",
"This profile was not found": "Ovaj profil nije pronađen",
"This setting will be used to display the website and send you emails in the correct language.": "Ova postavka će se koristiti za prikazivanje web-stranice i slanje e-maila u ispravnom jeziku.",
"This user doesn't have any profiles": "Korisnik nema nijedan profil",
"This user was not found": "",
"This user was not found": "Ovaj korisnik nije pronađen",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Ova web-stranica nije moderirana te će se svi podatci koje upišete automatski izbirisati svaki dan u 00:01 (Pariška vremenska zona).",
"This week": "Ovaj tjedan",
"This weekend": "Ovaj vikend",
@ -1160,7 +1164,7 @@
"Yesterday": "Jučer",
"You accepted the invitation to join the group.": "Prihvatili ste poziv u grupu.",
"You added the member {member}.": "Dodali ste člana {member}.",
"You approved {member}'s membership.": "",
"You approved {member}'s membership.": "Odobrio/la si članstvo za {member}.",
"You archived the discussion {discussion}.": "Arhivirali ste razgovor {discussion}.",
"You are not an administrator for this group.": "Niste administrator ove grupe.",
"You are not part of any group.": "Niste član nijedne grupe.",
@ -1192,7 +1196,7 @@
"You don't follow any instances yet.": "Ne pratite nijednu instancu.",
"You don't have any upcoming events. Maybe try another filter?": "Nemaš nijedan nadolazeći događaj. Želiš li probati jedan drugi filtar?",
"You excluded member {member}.": "Isključili ste člana {member}.",
"You have attended {count} events in the past.": "",
"You have attended {count} events in the past.": "Broj događaja kojima si prisustvovao/la u prošlosti: 0.|Broj događaja kojima si prisustvovao/la u prošlosti: 1.|Broj događaja kojima si prisustvovao/la u prošlosti: {count}.",
"You have been invited by {invitedBy} to the following group:": "{invitedBy} su vas pozvali u grupu:",
"You have been removed from this group's members.": "Izbačeni ste iz ove grupe.",
"You have cancelled your participation": "Otkazali ste svoje sudjelovanje",
@ -1212,7 +1216,7 @@
"You promoted the member {member} to an unknown role.": "Promovirali ste člana {member} u nepoznatu ulogu.",
"You promoted {member} to administrator.": "Promovirali ste {member} u administratora.",
"You promoted {member} to moderator.": "Promovirali ste člana {member} u moderatora.",
"You rejected {member}'s membership request.": "",
"You rejected {member}'s membership request.": "Odbio/la si zahtjev za članstvo za {member}.",
"You renamed the discussion from {old_discussion} to {discussion}.": "Preimenovali ste razgovor iz {old_discussion} u {discussion}.",
"You renamed the folder from {old_resource_title} to {resource}.": "Preimenovali ste mapu iz {old_resource_title} u {resource}.",
"You renamed the resource from {old_resource_title} to {resource}.": "preimenovali ste resurs iz {old_resource_title} u {resource}.",
@ -1252,7 +1256,7 @@
"Your email is being changed": "Vaša e-mail adresa se mijenja",
"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.": "Vaša e-mail adresa će se koristiti samo za potvrđivanje da ste stvarna osoba i za slanje eventualnih novosti za ovaj događaj. NEĆE SE proslijediti drugim instancama ili organizatorima događaja.",
"Your federated identity": "Vaš federalizirani identitet",
"Your membership was approved by {profile}.": "",
"Your membership was approved by {profile}.": "Tvoje članstvo je odobrio/la {profile}.",
"Your participation has been confirmed": "Vaše sudjelovanje je potvrđeno",
"Your participation has been rejected": "Vaše sudjelovanje je odbijeno",
"Your participation has been requested": "Poslan je zahtjev za vaše sudjelovanje",
@ -1276,6 +1280,7 @@
"[This comment has been deleted]": "[Ovaj je komentar izbrisan]",
"[deleted]": "[izbrisano]",
"a non-existent report": "nepostojeći izvještaj",
"access the corresponding account": "pristupi odgovarajućem računu",
"access to the group's private content as well": "",
"and {number} groups": "dodaj {number} grupa",
"any distance": "bilo koja udaljenost",
@ -1358,7 +1363,7 @@
"{old_group_name} was renamed to {group}.": "{old_group_name} je preimenovano u {group}.",
"{profile} (by default)": "{profile} (po zadanom)",
"{profile} added the member {member}.": "{profile} su dodali člana {member}.",
"{profile} approved {member}'s membership.": "",
"{profile} approved {member}'s membership.": "{profile} je odobrio/la članstvo za {member}.",
"{profile} archived the discussion {discussion}.": "{profile} su arhivirali razgovor {discussion}.",
"{profile} created the discussion {discussion}.": "{profile} su stvorili razgovor {discussion}.",
"{profile} created the folder {resource}.": "{profile} su stvorili mapu {resource}.",
@ -1380,7 +1385,7 @@
"{profile} promoted {member} to an unknown role.": "{profile} su promovirali {member} u nepoznatu ulogu.",
"{profile} promoted {member} to moderator.": "{profile} su promovirali {member} u moderatora.",
"{profile} quit the group.": "{profile} su izašli iz grupe.",
"{profile} rejected {member}'s membership request.": "",
"{profile} rejected {member}'s membership request.": "{profile} je odbio/la zahtjev za članstvo za {member}.",
"{profile} renamed the discussion from {old_discussion} to {discussion}.": "{profile} su preimenovali razgovor iz {old_discussion} u {discussion}.",
"{profile} renamed the folder from {old_resource_title} to {resource}.": "{profile} su preimenovali mapu iz {old_resource_title} u {resource}.",
"{profile} renamed the resource from {old_resource_title} to {resource}.": "{profile} su preimenovali resurs iz {old_resource_title} u {resource}.",

View file

@ -264,8 +264,6 @@
"Default Mobilizon privacy policy": "Alapértelmezett Mobilizon adatvédelmi irányelv",
"Default Mobilizon terms": "Alapértelmezett Mobilizon használati feltételek",
"Delete": "Törlés",
"Delete Comment": "Hozzászólás törlése",
"Delete Event": "Esemény törlése",
"Delete account": "Fiók törlése",
"Delete conversation": "Beszélgetés törlése",
"Delete discussion": "Megbeszélés törlése",

View file

@ -237,8 +237,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "Persyaratan default Mobilizon",
"Delete": "Hapus",
"Delete Comment": "Hapus Komentar",
"Delete Event": "Hapus Acara",
"Delete account": "Hapus Akun",
"Delete conversation": "Hapus percakapan",
"Delete discussion": "",

View file

@ -264,8 +264,6 @@
"Default Mobilizon privacy policy": "Informativa sulla riservatezza predefinita di Mobilizon",
"Default Mobilizon terms": "Termini di Mobilizon predefiniti",
"Delete": "Elimina",
"Delete Comment": "Elimina commento",
"Delete Event": "Elimina evento",
"Delete account": "Elimina account",
"Delete conversation": "Elimina la conversazione",
"Delete discussion": "Elimina discussione",

View file

@ -243,8 +243,6 @@
"Default Mobilizon privacy policy": "デフォルトのMobilizionのプライバシーポリシー",
"Default Mobilizon terms": "デフォルトのMobilizionの利用規約",
"Delete": "削除",
"Delete Comment": "コメントを削除",
"Delete Event": "イベントを削除",
"Delete account": "アカウントを削除",
"Delete conversation": "会話を削除する",
"Delete discussion": "議論を削除する",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "Kkes",
"Delete Comment": "Kkes awennit",
"Delete Event": "Kkes tadyant",
"Delete account": "Kkes amiḍan",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "",
"Delete Comment": "",
"Delete Event": "",
"Delete account": "",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -237,8 +237,6 @@
"Default Mobilizon privacy policy": "Standaard Mobilizon privacy beleid",
"Default Mobilizon terms": "Standaard Mobilizon voorwaarden",
"Delete": "Verwijder",
"Delete Comment": "Reactie verwijderen",
"Delete Event": "Evenement verwijderen",
"Delete account": "Account verwijderen",
"Delete conversation": "Verwijder gesprek",
"Delete discussion": "Discussie verwijderen",

View file

@ -264,8 +264,6 @@
"Default Mobilizon privacy policy": "Standard personvernvilkår for Mobilizon",
"Default Mobilizon terms": "Standardvilkår for Mobilizon",
"Delete": "Slett",
"Delete Comment": "Slett kommentaren",
"Delete Event": "Slett hendinga",
"Delete account": "Slett kontoen",
"Delete conversation": "Slett samtala",
"Delete discussion": "Slett ordskiftet",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Politica de confidencialitat per defaut de Mobilizon",
"Default Mobilizon terms": "Condicions dutilizacion per de defaut de Mobilizon",
"Delete": "Suprimir",
"Delete Comment": "Suprimir lo comentari",
"Delete Event": "Suprimir leveniment",
"Delete account": "Supression del compte",
"Delete conversation": "Suprimir la conversacion",
"Delete discussion": "Suprimir la discussion",

View file

@ -228,6 +228,7 @@
"Comment deleted": "Usunięto komentarz",
"Comment from {'@'}{username} reported": "Komentarz {'@'}{username} zgłoszony",
"Comment text can't be empty": "Tekst komentarza nie morze być pusty",
"Comment under event {eventTitle}": "Komentarz pod wydarzeniem {eventTitle}",
"Comments": "Komentarze",
"Comments are closed for everybody else.": "Komentarze są zamknięte dla innych.",
"Confirm": "Potwierdź",
@ -296,9 +297,8 @@
"Default Mobilizon privacy policy": "Domyślna polityka prywatności Mobilizon",
"Default Mobilizon terms": "Domyślne warunki użytkowania Mobilizon",
"Delete": "Usuń",
"Delete Comment": "Usuń komentarz",
"Delete Event": "Usuń wydarzenie",
"Delete account": "Usuń konto",
"Delete comment": "Usuń komentarz",
"Delete comments": "Usuwanie komentarzy",
"Delete conversation": "Usuń konwersację",
"Delete discussion": "Usunięcie dyskusji",
@ -393,6 +393,7 @@
"Event cancelled": "Anulowano wydarzenie",
"Event creation": "Utworzenie wydarzenia",
"Event date": "Data wydarzenia",
"Event deleted": "Usunięte zdarzenie",
"Event description body": "Treść opisu wydarzenia",
"Event edition": "Edycja wydarzenia",
"Event list": "Lista wydarzeń",
@ -688,6 +689,7 @@
"No closed reports yet": "Nie ma jeszcze zamkniętych zgłoszeń",
"No comment": "Brak komentarzy",
"No comments yet": "Nie ma jeszcze komentarzy",
"No content found": "Nie znaleziono zawartości",
"No discussions yet": "Brak dyskusji (jeszcze)",
"No end date": "Brak daty zakończenia",
"No event found at this address": "Pod tym adresem nie znaleziono żadnych wydarzeń",
@ -929,6 +931,7 @@
"Report this group": "Zgłoś tę grupę",
"Report this post": "Zgłoś ten wpis",
"Reported": "Zgłoszono",
"Reported at": "Zgłoszono o",
"Reported by": "Zgłoszono przez",
"Reported by someone anonymously": "Zgłoszone przez kogoś anonimowo",
"Reported by someone on {domain}": "Zgłoszono przez osobę z {domain}",
@ -1018,6 +1021,8 @@
"Suspend group": "Zawieś grupę",
"Suspend the account": "Zawieś konto",
"Suspend the account?": "Zawiesić konto?",
"Suspend the profile": "Zawieś profil",
"Suspend the profile?": "Zawiesić profil?",
"Suspended": "Zawieszony",
"Tag search": "Wyszukiwanie po tagach",
"Task lists": "Listy zadań",
@ -1251,6 +1256,7 @@
"Update post": "Aktualizuj wpis",
"Update profiles": "Aktualizacja profili",
"Updated": "Zaktualizowano",
"Updated at": "Zaktualizowano o",
"Upload media": "Przesyłanie multimediów",
"Uploaded media size": "Rozmiar przesłanych multimediów",
"Uploaded media total size": "Całkowity rozmiar przesłanych multimediów",
@ -1543,6 +1549,7 @@
"{number} posts": "Brak wpisów|Jeden wpis|{number} wpisów / wpisy",
"{number} seats left": "{number} wolnych miejsc",
"{old_group_name} was renamed to {group}.": "Nazwa grupy została zmieniona z {old_group_name} na {group}.",
"{profileName} (suspended)": "{profileName} (zawieszony)",
"{profile} (by default)": "{profile} (domyślnie)",
"{profile} added the member {member}.": "{profile} dodał(a)(o) członka / członkinię {member}.",
"{profile} approved {member}'s membership.": "{profile} zatwierdził(a) członkostwo {member}.",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "",
"Default Mobilizon terms": "",
"Delete": "",
"Delete Comment": "",
"Delete Event": "",
"Delete account": "",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Políticas padrões de privacidade do Mobilizon",
"Default Mobilizon terms": "Condições gerais padrões do Mobilizon",
"Delete": "Apagar",
"Delete Comment": "Apagar comentário",
"Delete Event": "Apagar evento",
"Delete account": "Apagar conta",
"Delete conversation": "Apagar conversa",
"Delete discussion": "",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Политика конфиденциальности Mobilizon по умолчанию",
"Default Mobilizon terms": "Условия использования Mobilizon по умолчанию",
"Delete": "Удалить",
"Delete Comment": "Удалить комментарий",
"Delete Event": "Удалить мероприятие",
"Delete account": "Удалить учётную запись",
"Delete conversation": "Удалить беседу",
"Delete discussion": "Удалить обсуждение",

View file

@ -234,8 +234,6 @@
"Default Mobilizon privacy policy": "Privzeti pravilnik o zasebnosti za Mobilizon",
"Default Mobilizon terms": "Privzeti pogoji za Mobilizon",
"Delete": "Izbriši",
"Delete Comment": "Izbriši komentar",
"Delete Event": "Izbriši dogodek",
"Delete account": "Izbriši račun",
"Delete conversation": "Izbriši pogovor",
"Delete discussion": "Izbriši razpravo",

View file

@ -235,8 +235,6 @@
"Default Mobilizon privacy policy": "Standard-integritetsvillkor för Mobilizon",
"Default Mobilizon terms": "Standardvillkor för Mobilizon",
"Delete": "Radera",
"Delete Comment": "Radera kommentar",
"Delete Event": "Radera evenemang",
"Delete account": "Radera konto",
"Delete conversation": "Ta bort konversationen",
"Delete discussion": "",

View file

@ -264,8 +264,6 @@
"Default Mobilizon privacy policy": "默认的Mobilizon隐私政策",
"Default Mobilizon terms": "缺省的Mobilizon条款",
"Delete": "删除",
"Delete Comment": "删除评论",
"Delete Event": "删除活动",
"Delete account": "删除账户",
"Delete conversation": "删除对话",
"Delete discussion": "删除讨论",

View file

@ -244,8 +244,6 @@
"Default Mobilizon privacy policy": "預設的 Mobilizon 隱私政策",
"Default Mobilizon terms": "預設的 Mobilizon 條款",
"Delete": "",
"Delete Comment": "刪除留言",
"Delete Event": "",
"Delete account": "",
"Delete conversation": "",
"Delete discussion": "",

View file

@ -14,9 +14,9 @@ export interface IReportNote extends IActionLogObject {
}
export interface IReport extends IActionLogObject {
id: string;
reported: IActor;
reported: IActor | undefined;
reporter: IPerson;
event?: IEvent;
events?: IEvent[];
comments: IComment[];
content: string;
notes: IReportNote[];

View file

@ -65,7 +65,7 @@
<section class="w-full">
<table class="table w-full">
<tbody>
<tr v-if="report.reported.type === ActorType.GROUP">
<tr v-if="report.reported?.type === ActorType.GROUP">
<td>{{ t("Reported group") }}</td>
<td>
<router-link
@ -84,33 +84,70 @@
</router-link>
</td>
</tr>
<tr v-else>
<tr v-else-if="report.reported?.type === ActorType.PERSON">
<td>
{{ t("Reported identity") }}
</td>
<td>
<td class="flex items-center justify-between pr-6">
<router-link
:to="{
name: RouteName.ADMIN_PROFILE,
params: { id: report.reported.id },
}"
class="inline-flex gap-1"
>
<img
v-if="report.reported.avatar"
class="image"
class="image rounded-full"
:src="report.reported.avatar.url"
alt=""
/>
{{ displayNameAndUsername(report.reported) }}
<template v-if="report.reported.suspended">
<i18n-t keypath="{profileName} (suspended)">
<template #profileName>
{{ displayNameAndUsername(report.reported) }}
</template>
</i18n-t>
</template>
<template v-else>{{
displayNameAndUsername(report.reported)
}}</template>
</router-link>
<o-button
v-if="report.reported.domain && !report.reported.suspended"
variant="danger"
@click="suspendProfile(report.reported.id as string)"
icon-left="delete"
size="small"
>{{ t("Suspend the profile") }}</o-button
>
<o-button
v-else-if="
(report.reported as IPerson).user &&
!((report.reported as IPerson).user as IUser).disabled
"
variant="danger"
@click="suspendUser((report.reported as IPerson).user as IUser)"
icon-left="delete"
size="small"
>{{ t("Suspend the account") }}</o-button
>
</td>
</tr>
<tr v-else>
<td>
{{ t("Reported identity") }}
</td>
<td>
{{ t("Unknown actor") }}
</td>
</tr>
<tr>
<td>{{ t("Reported by") }}</td>
<td v-if="report.reporter.type === ActorType.APPLICATION">
<td v-if="report.reporter?.type === ActorType.APPLICATION">
{{ report.reporter.domain }}
</td>
<td v-else>
<td v-else-if="report.reporter?.type === ActorType.PERSON">
<router-link
:to="{
name: RouteName.ADMIN_PROFILE,
@ -119,20 +156,23 @@
>
<img
v-if="report.reporter.avatar"
class="image"
class="image rounded-full"
:src="report.reporter.avatar.url"
alt=""
/>
{{ displayNameAndUsername(report.reporter) }}
</router-link>
</td>
<td v-else>
{{ t("Unknown actor") }}
</td>
</tr>
<tr>
<td>{{ t("Reported") }}</td>
<td>{{ t("Reported at") }}</td>
<td>{{ formatDateTimeString(report.insertedAt) }}</td>
</tr>
<tr v-if="report.updatedAt !== report.insertedAt">
<td>{{ t("Updated") }}</td>
<td>{{ t("Updated at") }}</td>
<td>{{ formatDateTimeString(report.updatedAt) }}</td>
</tr>
<tr>
@ -150,26 +190,6 @@
<span v-else>{{ t("Unknown") }}</span>
</td>
</tr>
<tr v-if="report.event && report.comments.length > 0">
<td>{{ t("Event") }}</td>
<td class="flex gap-2 items-center">
<router-link
class="underline"
:to="{
name: RouteName.EVENT,
params: { uuid: report.event.uuid },
}"
>
{{ report.event.title }}
</router-link>
<o-button
variant="danger"
@click="confirmEventDelete()"
icon-left="delete"
>{{ t("Delete") }}</o-button
>
</td>
</tr>
</tbody>
</table>
</section>
@ -178,7 +198,7 @@
<h2 class="mb-1">{{ t("Report reason") }}</h2>
<div class="">
<div class="flex gap-1">
<figure class="" v-if="report.reported.avatar">
<figure class="" v-if="report.reported?.avatar">
<img
alt=""
:src="report.reported.avatar.url"
@ -188,12 +208,13 @@
/>
</figure>
<AccountCircle v-else :size="36" />
<div class="">
<p class="" v-if="report.reported.name">
<div class="" v-if="report.reported">
<p class="" v-if="report.reported?.name">
{{ report.reported.name }}
</p>
<p class="">@{{ usernameWithDomain(report.reported) }}</p>
</div>
<p v-else>{{ t("Unknown actor") }}</p>
</div>
<div
class="prose dark:prose-invert"
@ -206,17 +227,27 @@
<section
class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3"
v-if="report.event && report.comments.length === 0"
v-if="
report.events &&
report.events?.length > 0 &&
report.comments.length === 0
"
>
<h2 class="mb-1">{{ t("Reported content") }}</h2>
<EventCard :event="report.event" mode="row" class="my-2 max-w-4xl" />
<o-button
variant="danger"
@click="confirmEventDelete()"
icon-left="delete"
size="small"
>{{ t("Delete") }}</o-button
>
<ul>
<li v-for="event in report.events" :key="event.id">
<EventCard :event="event" mode="row" class="my-2 max-w-4xl" />
<o-button
variant="danger"
@click="confirmEventDelete(event)"
icon-left="delete"
><template v-if="isOnlyReportedContent">{{
t("Delete event and resolve report")
}}</template
><template v-else>{{ t("Delete event") }}</template></o-button
>
</li>
</ul>
</section>
<section
@ -226,41 +257,55 @@
<h2 class="mb-1">{{ t("Reported content") }}</h2>
<ul v-for="comment in report.comments" :key="comment.id">
<li>
<div class="" v-if="comment">
<article>
<div class="flex gap-1">
<figure class="" v-if="comment.actor?.avatar">
<img
alt=""
:src="comment.actor.avatar?.url"
class="rounded-full"
width="36"
height="36"
/>
</figure>
<AccountCircle v-else :size="36" />
<div>
<div v-if="comment.actor">
<p>{{ comment.actor.name }}</p>
<p>@{{ comment.actor.preferredUsername }}</p>
</div>
<span v-else>{{ t("Unknown actor") }}</span>
</div>
</div>
<div class="prose dark:prose-invert" v-html="comment.text" />
<o-button
variant="danger"
@click="confirmCommentDelete(comment)"
icon-left="delete"
size="small"
>{{ t("Delete") }}</o-button
<i18n-t keypath="Comment under event {eventTitle}" tag="p">
<template #eventTitle>
<router-link
:to="{
name: RouteName.EVENT,
params: { uuid: comment.event?.uuid },
}"
>
</article>
</div>
<b>{{ comment.event?.title }}</b>
</router-link>
</template>
</i18n-t>
<EventComment
:root-comment="true"
:comment="comment"
:event="comment.event as IEvent"
:current-actor="currentActor as IPerson"
:readOnly="true"
/>
<o-button
v-if="!comment.deletedAt"
variant="danger"
@click="confirmCommentDelete(comment)"
icon-left="delete"
><template v-if="isOnlyReportedContent">{{
t("Delete comment and resolve report")
}}</template
><template v-else>{{ t("Delete comment") }}</template></o-button
>
</li>
</ul>
</section>
<section
class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3"
v-if="
report.events &&
report.events?.length === 0 &&
report.comments.length === 0
"
>
<EmptyContent inline center icon="alert-circle">
{{ t("No content found") }}
<template #desc>
{{ t("Maybe the content was removed by the author or a moderator") }}
</template>
</EmptyContent>
</section>
<section class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3">
<h2 class="mb-1">{{ t("Notes") }}</h2>
<div
@ -315,7 +360,11 @@
<script lang="ts" setup>
import { CREATE_REPORT_NOTE, REPORT, UPDATE_REPORT } from "@/graphql/report";
import { IReport, IReportNote } from "@/types/report.model";
import { displayNameAndUsername, usernameWithDomain } from "@/types/actor";
import {
IPerson,
displayNameAndUsername,
usernameWithDomain,
} from "@/types/actor";
import { DELETE_EVENT } from "@/graphql/event";
import uniq from "lodash/uniq";
import { nl2br } from "@/utils/html";
@ -325,7 +374,7 @@ import { ActorType, AntiSpamFeedback, ReportStatusEnum } from "@/types/enums";
import RouteName from "@/router/name";
import { GraphQLError } from "graphql";
import { ApolloCache, FetchResult } from "@apollo/client/core";
import { useMutation, useQuery } from "@vue/apollo-composable";
import { useLazyQuery, useMutation, useQuery } from "@vue/apollo-composable";
import { useCurrentActorClient } from "@/composition/apollo/actor";
import { useHead } from "@vueuse/head";
import { useI18n } from "vue-i18n";
@ -337,6 +386,13 @@ import { Dialog } from "@/plugins/dialog";
import { Notifier } from "@/plugins/notifier";
import EventCard from "@/components/Event/EventCard.vue";
import { useFeatures } from "@/composition/apollo/config";
import { IEvent } from "@/types/event.model";
import EmptyContent from "@/components/Utils/EmptyContent.vue";
import EventComment from "@/components/Comment/EventComment.vue";
import { SUSPEND_PROFILE } from "@/graphql/actor";
import { GET_USER, SUSPEND_USER } from "@/graphql/user";
import { IUser } from "@/types/current-user.model";
import { waitApolloQuery } from "@/vue-apollo";
const router = useRouter();
@ -374,6 +430,14 @@ const errors = ref<string[]>([]);
const noteContent = ref("");
const reportedContent = computed(() => {
return [...(report.value?.events ?? []), ...(report.value?.comments ?? [])];
});
const isOnlyReportedContent = computed(
() => reportedContent.value.length === 1
);
const {
mutate: createReportNoteMutation,
onDone: createReportNoteMutationDone,
@ -419,26 +483,39 @@ createReportNoteMutationError((error) => {
const dialog = inject<Dialog>("dialog");
const confirmEventDelete = (): void => {
const addResolveReportPart = computed(() => {
if (isOnlyReportedContent.value) {
return "<p>" + t("This will also resolve the report.") + "</p>";
}
return "";
});
const confirmEventDelete = (event: IEvent): void => {
dialog?.confirm({
title: t("Deleting event"),
message: t(
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead."
),
confirmText: t("Delete Event"),
message:
t(
"Are you sure you want to <b>delete</b> this event? <b>This action cannot be undone</b>. You may want to engage the discussion with the event creator and ask them to edit their event instead."
) + addResolveReportPart.value,
confirmText: isOnlyReportedContent.value
? t("Delete event and resolve report")
: t("Delete event"),
variant: "danger",
hasIcon: true,
onConfirm: () => deleteEvent(),
onConfirm: () => deleteEvent(event),
});
};
const confirmCommentDelete = (comment: IComment): void => {
dialog?.confirm({
title: t("Deleting comment"),
message: t(
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone."
),
confirmText: t("Delete Comment"),
message:
t(
"Are you sure you want to <b>delete</b> this comment? <b>This action cannot be undone</b>."
) + addResolveReportPart.value,
confirmText: isOnlyReportedContent.value
? t("Delete comment and resolve report")
: t("Delete comment"),
variant: "danger",
hasIcon: true,
onConfirm: () => deleteCommentMutation({ commentId: comment.id }),
@ -449,35 +526,105 @@ const {
mutate: deleteEventMutation,
onDone: deleteEventMutationDone,
onError: deleteEventMutationError,
} = useMutation<{ deleteEvent: { id: string } }>(DELETE_EVENT);
} = useMutation<{ deleteEvent: { id: string } }>(DELETE_EVENT, () => ({
update: (
store: ApolloCache<{ deleteEvent: { id: string } }>,
{ data }: FetchResult
) => {
if (data == null) return;
const reportCachedData = store.readQuery<{ report: IReport }>({
query: REPORT,
variables: { id: report.value?.id },
});
if (reportCachedData == null) return;
const { report: cachedReport } = reportCachedData;
if (cachedReport === null) {
console.error(
"Cannot update report events cache, because of null value."
);
return;
}
const updatedReport = {
...cachedReport,
events: cachedReport.events?.filter(
(cachedEvent) => cachedEvent.id !== data.deleteEvent.id
),
};
deleteEventMutationDone(() => {
const eventTitle = report.value?.event?.title;
notifier?.success(
t("Event {eventTitle} deleted", {
eventTitle,
})
);
store.writeQuery({
query: REPORT,
variables: { id: report.value?.id },
data: { report: updatedReport },
});
},
}));
deleteEventMutationDone(async () => {
if (reportedContent.value.length === 0) {
await updateReport(ReportStatusEnum.RESOLVED);
notifier?.success(t("Event deleted and report resolved"));
} else {
notifier?.success(t("Event deleted"));
}
});
deleteEventMutationError((error) => {
console.error(error);
});
const deleteEvent = async (): Promise<void> => {
if (!report.value?.event?.id) return;
const deleteEvent = async (event: IEvent): Promise<void> => {
if (!event?.id) return;
deleteEventMutation({ eventId: report.value.event.id });
deleteEventMutation(
{ eventId: event.id },
{ context: { eventTitle: event.title } }
);
};
const {
mutate: deleteCommentMutation,
onDone: deleteCommentMutationDone,
onError: deleteCommentMutationError,
} = useMutation<{ deleteComment: { id: string } }>(DELETE_COMMENT);
} = useMutation<{ deleteComment: { id: string } }>(DELETE_COMMENT, () => ({
update: (
store: ApolloCache<{ deleteComment: { id: string } }>,
{ data }: FetchResult
) => {
if (data == null) return;
const reportCachedData = store.readQuery<{ report: IReport }>({
query: REPORT,
variables: { id: report.value?.id },
});
if (reportCachedData == null) return;
const { report: cachedReport } = reportCachedData;
if (cachedReport === null) {
console.error(
"Cannot update report comments cache, because of null value."
);
return;
}
const updatedReport = {
...cachedReport,
comments: cachedReport.comments.filter(
(cachedComment) => cachedComment.id !== data.deleteComment.id
),
};
deleteCommentMutationDone(() => {
notifier?.success(t("Comment deleted") as string);
store.writeQuery({
query: REPORT,
variables: { id: report.value?.id },
data: { report: updatedReport },
});
},
}));
deleteCommentMutationDone(async () => {
if (reportedContent.value.length === 0) {
await updateReport(ReportStatusEnum.RESOLVED);
notifier?.success(t("Comment deleted and report resolved"));
} else {
notifier?.success(t("Comment deleted"));
}
});
deleteCommentMutationError((error) => {
@ -524,8 +671,8 @@ const {
},
}));
onUpdateReportMutation(() => {
router.push({ name: RouteName.REPORTS });
onUpdateReportMutation(async () => {
await router.push({ name: RouteName.REPORTS });
});
onUpdateReportError((error) => {
@ -561,6 +708,105 @@ const reportToAntispam = (spam: boolean) => {
},
});
};
const { mutate: doSuspendProfile, onDone: onSuspendProfileDone } = useMutation<
{
suspendProfile: { id: string };
},
{ id: string }
>(SUSPEND_PROFILE);
const { mutate: doSuspendUser, onDone: onSuspendUserDone } = useMutation<
{ suspendProfile: { id: string } },
{ userId: string }
>(SUSPEND_USER);
const userLazyQuery = useLazyQuery<{ user: IUser }, { id: string }>(GET_USER);
const suspendProfile = async (actorId: string): Promise<void> => {
dialog?.confirm({
title: t("Suspend the profile?"),
message:
t(
"Do you really want to suspend this profile? All of the profiles content will be deleted."
) +
`<p><b>` +
t("There will be no way to restore the profile's data!") +
`</b></p>`,
confirmText: t("Suspend the profile"),
cancelText: t("Cancel"),
variant: "danger",
onConfirm: async () => {
doSuspendProfile({
id: actorId,
});
return router.push({ name: RouteName.USERS });
},
});
};
const userSuspendedProfilesMessages = (user: IUser) => {
return (
t("The following user's profiles will be deleted, with all their data:") +
`<ul class="list-disc pl-3">` +
user.actors
.map((person) => `<li>${displayNameAndUsername(person)}</li>`)
.join("") +
`</ul><b>`
);
};
const cachedReportedUser = ref<IUser | undefined>();
const suspendUser = async (user: IUser): Promise<void> => {
try {
if (!cachedReportedUser.value) {
userLazyQuery.load(GET_USER, { id: user.id });
const userLazyQueryResult = await waitApolloQuery<
{ user: IUser },
{ id: string }
>(userLazyQuery);
console.debug("data", userLazyQueryResult);
cachedReportedUser.value = userLazyQueryResult.data.user;
}
dialog?.confirm({
title: t("Suspend the account?"),
message:
t("Do you really want to suspend the account « {emailAccount} » ?", {
emailAccount: cachedReportedUser.value.email,
}) +
" " +
userSuspendedProfilesMessages(cachedReportedUser.value) +
"<b>" +
t("There will be no way to restore the user's data!") +
`</b>`,
confirmText: t("Suspend the account"),
cancelText: t("Cancel"),
variant: "danger",
onConfirm: async () => {
doSuspendUser({
userId: user.id,
});
return router.push({ name: RouteName.USERS });
},
});
} catch (e) {
console.error(e);
}
};
onSuspendUserDone(async () => {
await router.push({ name: RouteName.REPORTS });
notifier?.success(t("User suspended and report resolved"));
});
onSuspendProfileDone(async () => {
await router.push({ name: RouteName.REPORTS });
notifier?.success(t("Profile suspended and report resolved"));
});
</script>
<style lang="scss" scoped>
tbody td img.image,

View file

@ -1,7 +1,13 @@
import { ApolloClient, NormalizedCacheObject } from "@apollo/client/core";
import {
ApolloClient,
ApolloQueryResult,
NormalizedCacheObject,
OperationVariables,
} from "@apollo/client/core";
import buildCurrentUserResolver from "@/apollo/user";
import { cache } from "./apollo/memory";
import { fullLink } from "./apollo/link";
import { UseQueryReturn } from "@vue/apollo-composable";
export const apolloClient = new ApolloClient<NormalizedCacheObject>({
cache,
@ -9,3 +15,24 @@ export const apolloClient = new ApolloClient<NormalizedCacheObject>({
connectToDevTools: true,
resolvers: buildCurrentUserResolver(cache),
});
export function waitApolloQuery<
TResult = any,
TVariables extends OperationVariables = OperationVariables,
>({
onResult,
onError,
}: UseQueryReturn<TResult, TVariables>): Promise<ApolloQueryResult<TResult>> {
return new Promise((res, rej) => {
const { off: offResult } = onResult((result) => {
if (result.loading === false) {
offResult();
res(result);
}
});
const { off: offError } = onError((error) => {
offError();
rej(error);
});
});
}

View file

@ -2,7 +2,6 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
@moduledoc false
alias Mobilizon.{Actors, Discussions, Events, Reports}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Reports.Report
@ -26,15 +25,19 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
%Actor{} = reported_actor = Actors.get_actor!(args.reported_id)
content = HTML.strip_tags(args.content)
event_id = Map.get(args, :event_id)
event =
if is_nil(event_id) do
nil
else
{:ok, %Event{} = event} = Events.get_event(event_id)
event
end
events =
args
|> Map.get(:events_ids, [])
|> Enum.map(fn event_id ->
case Events.get_event(event_id) do
{:ok, event} -> event
{:error, :event_not_found} -> nil
end
end)
|> Enum.filter(fn event ->
is_struct(event) and
Enum.member?([event.organizer_actor_id, event.attributed_to_id], reported_actor.id)
end)
comments =
Discussions.list_comments_by_actor_and_ids(
@ -46,7 +49,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
reporter: reporter_actor,
reported: reported_actor,
content: content,
event: event,
events: events,
comments: comments
})
end

View file

@ -9,11 +9,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions
alias Mobilizon.Events
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Reports.Report
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
@ -38,7 +38,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
"uri" => params["uri"],
"content" => params["content"],
"reported_id" => params["reported"].id,
"event_id" => (!is_nil(params["event"]) && params["event"].id) || nil,
"events" => params["events"],
"comments" => params["comments"]
}
end
@ -50,9 +50,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
@impl Converter
@spec model_to_as(Report.t()) :: map
def model_to_as(%Report{} = report) do
object = [report.reported.url] ++ Enum.map(report.comments, fn comment -> comment.url end)
object = if report.event, do: object ++ [report.event.url], else: object
object =
[report.reported.url] ++
Enum.map(report.comments, fn comment -> comment.url end) ++
Enum.map(report.events, & &1.url)
%{
"type" => "Flag",
@ -68,14 +69,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
with {:ok, %Actor{} = reporter} <-
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
%Actor{} = reported <- find_reported(objects),
event <- find_event(objects),
comments <- find_comments(objects, reported, event) do
%{events: events, comments: comments} <- find_events_and_comments(objects) do
%{
"reporter" => reporter,
"uri" => object["id"],
"content" => object["content"],
"reported" => reported,
"event" => event,
"events" => events,
"comments" => comments
}
end
@ -94,26 +94,19 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
end)
end
# Remove the reported actor and the event from the object list.
@spec find_comments(list(String.t()), Actor.t() | nil, Event.t() | nil) :: list(Comment.t())
defp find_comments(objects, reported, event) do
defp find_events_and_comments(objects) do
objects
|> Enum.filter(fn url ->
!((!is_nil(reported) && url == reported.url) || (!is_nil(event) && event.url == url))
end)
|> Enum.map(&Discussions.get_comment_from_url/1)
|> Enum.filter(& &1)
end
|> Enum.map(&ActivityPub.fetch_object_from_url/1)
|> Enum.reduce(%{comments: [], events: []}, fn res, acc ->
case res do
{:ok, %Event{} = event} ->
Map.put(acc, :events, [event | acc.events])
@spec find_event(list(String.t())) :: Event.t() | nil
defp find_event(objects) do
Enum.reduce_while(objects, nil, fn url, _ ->
case Events.get_event_by_url(url) do
%Event{} = event ->
{:halt, event}
{:ok, %Comment{} = comment} ->
Map.put(acc, :comments, [comment | acc.comments])
_ ->
{:cont, nil}
acc
end
end)
end

View file

@ -19,7 +19,7 @@ defmodule Mobilizon.GraphQL.Schema.ReportType do
field(:uri, :string, description: "The URI of the report", meta: [private: true])
field(:reported, :actor, description: "The actor that is being reported")
field(:reporter, :actor, description: "The actor that created the report")
field(:event, :event, description: "The event that is being reported")
field(:events, list_of(:event), description: "The event that is being reported")
field(:comments, list_of(:comment), description: "The comments that are reported")
field(:notes, list_of(:report_note),
@ -100,11 +100,15 @@ defmodule Mobilizon.GraphQL.Schema.ReportType do
field :create_report, type: :report do
arg(:content, :string, description: "The message sent with the report")
arg(:reported_id, non_null(:id), description: "The actor's ID that is being reported")
arg(:event_id, :id, default_value: nil, description: "The event ID that is being reported")
arg(:events_ids, list_of(:id),
default_value: [],
description: "The list of event IDs that are being reported"
)
arg(:comments_ids, list_of(:id),
default_value: [],
description: "The comment ID that is being reported"
description: "The comment IDs that are being reported"
)
arg(:forward, :boolean,

View file

@ -22,13 +22,13 @@ defmodule Mobilizon.Reports.Report do
reported: Actor.t(),
reporter: Actor.t(),
manager: Actor.t(),
event: Event.t(),
events: [Event.t()],
comments: [Comment.t()],
notes: [Note.t()]
}
@required_attrs [:url, :reported_id, :reporter_id]
@optional_attrs [:content, :status, :manager_id, :event_id, :local]
@optional_attrs [:content, :status, :manager_id, :local]
@attrs @required_attrs ++ @optional_attrs
@timestamps_opts [type: :utc_datetime]
@ -46,8 +46,8 @@ defmodule Mobilizon.Reports.Report do
belongs_to(:reporter, Actor)
# The actor who last acted on this report
belongs_to(:manager, Actor)
# The eventual Event inside the report
belongs_to(:event, Event)
# The eventual Events inside the report
many_to_many(:events, Event, join_through: "reports_events", on_replace: :delete)
# The eventual Comments inside the report
many_to_many(:comments, Comment, join_through: "reports_comments", on_replace: :delete)
# The notes associated to the report
@ -62,6 +62,7 @@ defmodule Mobilizon.Reports.Report do
report
|> cast(attrs, @attrs)
|> maybe_generate_url()
|> maybe_put_events(attrs)
|> maybe_put_comments(attrs)
|> validate_required(@required_attrs)
end
@ -72,6 +73,12 @@ defmodule Mobilizon.Reports.Report do
defp maybe_put_comments(%Ecto.Changeset{} = changeset, _), do: changeset
defp maybe_put_events(%Ecto.Changeset{} = changeset, %{events: events}) do
put_assoc(changeset, :events, events)
end
defp maybe_put_events(%Ecto.Changeset{} = changeset, _), do: changeset
@spec maybe_generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp maybe_generate_url(%Ecto.Changeset{} = changeset) do
with res when res in [:error, {:data, nil}] <- fetch_field(changeset, :url),

View file

@ -21,7 +21,7 @@ defmodule Mobilizon.Reports do
def get_report(id) do
Report
|> Repo.get(id)
|> Repo.preload([:reported, :reporter, :manager, :event, :comments, :notes])
|> Repo.preload([:reported, :reporter, :manager, :events, :comments, :notes])
end
@doc """
@ -33,7 +33,7 @@ defmodule Mobilizon.Reports do
%Report{}
|> Report.changeset(attrs)
|> Repo.insert() do
{:ok, Repo.preload(report, [:event, :reported, :reporter, :comments])}
{:ok, Repo.preload(report, [:events, :reported, :reporter, :comments])}
end
end
@ -102,7 +102,7 @@ defmodule Mobilizon.Reports do
@spec list_reports_query(atom()) :: Ecto.Query.t()
defp list_reports_query(status) do
Report
|> preload([:reported, :reporter, :manager, :event, :comments, :notes])
|> preload([:reported, :reporter, :manager, :events, :comments, :notes])
|> where([r], r.status == ^status)
end

View file

@ -0,0 +1,70 @@
defmodule Mobilizon.Storage.Views.Instances do
@moduledoc """
SQL code for PostgreSQL instances materialized view
"""
def create_materialized_view do
"""
CREATE MATERIALIZED VIEW instances AS
SELECT
a.domain,
COUNT(DISTINCT(p.id)) AS person_count,
COUNT(DISTINCT(g.id)) AS group_count,
COUNT(DISTINCT(e.id)) AS event_count,
COUNT(f1.id) AS followers_count,
COUNT(f2.id) AS followings_count,
COUNT(r.id) AS reports_count,
SUM(COALESCE((m.file->>'size')::int, 0)) AS media_size
FROM actors a
LEFT JOIN actors p ON a.id = p.id AND p.type = 'Person'
LEFT JOIN actors g ON a.id = g.id AND g.type = 'Group'
LEFT JOIN events e ON a.id = e.organizer_actor_id
LEFT JOIN followers f1 ON a.id = f1.actor_id
LEFT JOIN followers f2 ON a.id = f2.target_actor_id
LEFT JOIN reports r ON r.reported_id = a.id
LEFT JOIN medias m ON m.actor_id = a.id
WHERE a.domain IS NOT NULL
GROUP BY a.domain;
"""
end
def refresh_instances do
"""
CREATE OR REPLACE FUNCTION refresh_instances()
RETURNS trigger AS $$
BEGIN
REFRESH MATERIALIZED VIEW instances;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
"""
end
def drop_trigger do
"""
DROP TRIGGER IF EXISTS refresh_instances_trigger ON actors;
"""
end
def create_trigger do
"""
CREATE TRIGGER refresh_instances_trigger
AFTER INSERT OR UPDATE OR DELETE
ON actors
FOR EACH STATEMENT
EXECUTE PROCEDURE refresh_instances();
"""
end
def drop_refresh_instances do
"""
DROP FUNCTION IF EXISTS refresh_instances() CASCADE;
"""
end
def drop_view do
"""
DROP MATERIALIZED VIEW IF EXISTS instances;
"""
end
end

View file

@ -139,7 +139,7 @@ defmodule Mobilizon.Service.AntiSpam.Akismet do
end
end
defp report_to_akismet_comment(%Report{event: %Event{id: event_id}}) do
defp report_to_akismet_comment(%Report{events: [%Event{id: event_id} | _]}) do
with %Event{description: body, organizer_actor: %Actor{} = actor} <-
Events.get_event_with_preload!(event_id),
{email, preferred_username, ip} <- actor_details(actor) do

View file

@ -104,7 +104,7 @@
</td>
</tr>
<% end %>
<%= if Map.has_key?(@report, :event) and @report.event do %>
<%= if Map.has_key?(@report, :events) and length(@report.events) > 0 do %>
<tr>
<td
bgcolor="#ffffff"
@ -112,16 +112,19 @@
style="padding: 20px 30px 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;"
>
<p style="margin: 0;">
<h3><%= gettext("Event") %></h3>
<a
href={"#{"#{Mobilizon.Web.Endpoint.url()}/events/#{@report.event.uuid}"}"}
target="_blank"
>
<%= gettext("%{title} by %{creator}",
title: @report.event.title,
creator: Mobilizon.Actors.Actor.preferred_username_and_domain(@report.reported)
) %>
</a>
<h3><%= gettext("Flagged events") %></h3>
<%= for event <- @report.events do %>
<a
href={"#{"#{Mobilizon.Web.Endpoint.url()}/events/#{event.uuid}"}"}
target="_blank"
>
<%= gettext("%{title} by %{creator}",
title: event.title,
creator:
Mobilizon.Actors.Actor.preferred_username_and_domain(@report.reported)
) %>
</a>
<% end %>
</p>
<table
cellspacing="0"

View file

@ -7,9 +7,11 @@
<%= gettext "Profile %{profile} was reported", profile: Mobilizon.Actors.Actor.display_name_and_username(@report.reported) %>
<% end %>
<% end %>
<%= if Map.has_key?(@report, :event) and @report.event do %>
<%= gettext "Event" %>
<%= @report.event.title %>
<%= if Map.has_key?(@report, :event) && length(@report.events) > 0 do %>
<%= gettext "Events" %>
<%= for event <- @report.events do %>
<%= event.title %>
<% end %>
<% end %>
<%= if Map.has_key?(@report, :comments) && length(@report.comments) > 0 do %>
<%= gettext "Comments" %>

View file

@ -1,7 +1,7 @@
defmodule Mobilizon.Mixfile do
use Mix.Project
@version "3.1.3"
@version "3.2.0-beta.1"
def project do
[

View file

@ -1,51 +1,15 @@
defmodule Mobilizon.Storage.Repo.Migrations.AddInstanceMaterializedView do
use Ecto.Migration
alias Mobilizon.Storage.Views.Instances
def up do
execute("""
CREATE MATERIALIZED VIEW instances AS
SELECT
a.domain,
COUNT(DISTINCT(p.id)) AS person_count,
COUNT(DISTINCT(g.id)) AS group_count,
COUNT(DISTINCT(e.id)) AS event_count,
COUNT(f1.id) AS followers_count,
COUNT(f2.id) AS followings_count,
COUNT(r.id) AS reports_count,
SUM(COALESCE((m.file->>'size')::int, 0)) AS media_size
FROM actors a
LEFT JOIN actors p ON a.id = p.id AND p.type = 'Person'
LEFT JOIN actors g ON a.id = g.id AND g.type = 'Group'
LEFT JOIN events e ON a.id = e.organizer_actor_id
LEFT JOIN followers f1 ON a.id = f1.actor_id
LEFT JOIN followers f2 ON a.id = f2.target_actor_id
LEFT JOIN reports r ON r.reported_id = a.id
LEFT JOIN medias m ON m.actor_id = a.id
WHERE a.domain IS NOT NULL
GROUP BY a.domain;
""")
execute(Instances.create_materialized_view())
execute("""
CREATE OR REPLACE FUNCTION refresh_instances()
RETURNS trigger AS $$
BEGIN
REFRESH MATERIALIZED VIEW instances;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
""")
execute(Instances.refresh_instances())
execute("""
DROP TRIGGER IF EXISTS refresh_instances_trigger ON actors;
""")
execute(Instances.drop_trigger())
execute("""
CREATE TRIGGER refresh_instances_trigger
AFTER INSERT OR UPDATE OR DELETE
ON actors
FOR EACH STATEMENT
EXECUTE PROCEDURE refresh_instances();
""")
execute(Instances.create_trigger())
create_if_not_exists(unique_index("instances", [:domain]))
end
@ -53,12 +17,8 @@ defmodule Mobilizon.Storage.Repo.Migrations.AddInstanceMaterializedView do
def down do
drop_if_exists(unique_index("instances", [:domain]))
execute("""
DROP FUNCTION IF EXISTS refresh_instances() CASCADE;
""")
execute(Instances.drop_refresh_instances())
execute("""
DROP MATERIALIZED VIEW IF EXISTS instances;
""")
execute(Instances.drop_view())
end
end

View file

@ -0,0 +1,14 @@
defmodule Mobilizon.Storage.Repo.Migrations.AllowMultipleEventsToBeReported do
use Ecto.Migration
def up do
create table(:reports_events, primary_key: false) do
add(:report_id, references(:reports, on_delete: :delete_all), null: false)
add(:event_id, references(:events, on_delete: :delete_all), null: false)
end
end
def down do
drop table(:reports_events)
end
end

View file

@ -0,0 +1,31 @@
defmodule Mobilizon.Storage.Repo.Migrations.BackfillReportEventsWithOldEvents do
use Ecto.Migration
def up do
process_reports_with_events()
end
def down do
IO.puts("Doing nothing, migration can't be reverted")
end
defp process_reports_with_events do
%Postgrex.Result{rows: rows} =
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"SELECT id, event_id FROM reports WHERE event_id IS NOT NULL"
)
Enum.map(rows, &migrate_event_row/1)
end
defp migrate_event_row([report_id, event_id]) when not is_nil(event_id) do
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"INSERT INTO reports_events VALUES ($1, $2)",
[report_id, event_id]
)
end
defp migrate_event_row(_), do: :ok
end

View file

@ -0,0 +1,15 @@
defmodule Mobilizon.Storage.Repo.Migrations.RemoveObsoleteEventIdOnReports do
use Ecto.Migration
def up do
alter table(:reports) do
remove_if_exists :event_id, :integer
end
end
def down do
alter table(:reports) do
add(:event_id, references(:events, on_delete: :delete_all), null: true)
end
end
end

View file

@ -0,0 +1,48 @@
defmodule Mobilizon.Storage.Repo.Migrations.RemoveOnDeleteCascadeForReports do
use Ecto.Migration
alias Mobilizon.Storage.Views.Instances
def up do
execute(Instances.drop_view())
drop(constraint(:reports, "reports_reported_id_fkey"))
drop(constraint(:reports, "reports_reporter_id_fkey"))
drop(constraint(:reports, "reports_manager_id_fkey"))
alter table(:reports) do
modify(:reported_id, references(:actors, on_delete: :nilify_all), null: true)
modify(:reporter_id, references(:actors, on_delete: :nilify_all), null: true)
modify(:manager_id, references(:actors, on_delete: :nilify_all), null: true)
end
drop(constraint(:report_notes, "report_notes_moderator_id_fkey"))
alter table(:report_notes) do
modify(:moderator_id, references(:actors, on_delete: :nilify_all), null: true)
end
execute(Instances.create_materialized_view())
end
def down do
execute(Instances.drop_view())
drop(constraint(:reports, "reports_reported_id_fkey"))
drop(constraint(:reports, "reports_reporter_id_fkey"))
drop(constraint(:reports, "reports_manager_id_fkey"))
alter table(:reports) do
modify(:reported_id, references(:actors, on_delete: :delete_all), null: false)
modify(:reporter_id, references(:actors, on_delete: :delete_all), null: false)
modify(:manager_id, references(:actors, on_delete: :delete_all), null: true)
end
drop(constraint(:report_notes, "report_notes_moderator_id_fkey"))
alter table(:report_notes) do
modify(:moderator_id, references(:actors, on_delete: :delete_all), null: false)
end
execute(Instances.create_materialized_view())
end
end

View file

@ -30,7 +30,7 @@ defmodule Mobilizon.GraphQL.API.ReportTest do
reporter_id: reporter_id,
reported_id: reported_id,
content: comment,
event_id: event_id,
events_ids: [event_id],
comments_ids: [],
forward: false
})
@ -64,7 +64,7 @@ defmodule Mobilizon.GraphQL.API.ReportTest do
reporter_id: reporter_id,
reported_id: reported_id,
content: comment,
event_id: nil,
events_ids: [],
comments_ids: [comment_1_id, comment_2_id]
})
@ -100,7 +100,7 @@ defmodule Mobilizon.GraphQL.API.ReportTest do
reporter_id: reporter_id,
reported_id: reported_id,
content: comment,
event_id: nil,
events_ids: [],
comments_ids: [comment_1_id, comment_2_id],
forward: true
})
@ -131,7 +131,7 @@ defmodule Mobilizon.GraphQL.API.ReportTest do
reporter_id: reporter_id,
reported_id: reported_id,
content: "This is not a nice thing",
event_id: nil,
events_ids: [],
comments_ids: [comment_1_id],
forward: true
})
@ -157,7 +157,7 @@ defmodule Mobilizon.GraphQL.API.ReportTest do
reporter_id: reporter_id,
reported_id: reported_id,
content: "This is not a nice thing",
event_id: nil,
events_ids: [],
comments_ids: [comment_1_id],
forward: true
})

View file

@ -15,17 +15,17 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
describe "Resolver: Report a content" do
@create_report_mutation """
mutation CreateReport($reportedId: ID!, $eventId: ID, $content: String) {
mutation CreateReport($reportedId: ID!, $eventsIds: [ID], $content: String) {
createReport(
reportedId: $reportedId,
eventId: $eventId,
eventsIds: $eventsIds,
content: $content
) {
content,
reporter {
id
},
event {
events {
id
},
status
@ -55,7 +55,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
query: @create_report_mutation,
variables: %{
reportedId: reported.id,
eventId: event.id,
eventsIds: [event.id],
content: "This is an issue"
}
)
@ -63,7 +63,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
assert res["errors"] == nil
assert res["data"]["createReport"]["content"] == "This is an issue"
assert res["data"]["createReport"]["status"] == "OPEN"
assert res["data"]["createReport"]["event"]["id"] == to_string(event.id)
assert res["data"]["createReport"]["events"] |> hd |> Map.get("id") == to_string(event.id)
assert res["data"]["createReport"]["reporter"]["id"] ==
to_string(reporter.id)
@ -122,7 +122,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
reporter {
id
},
event {
events {
id
},
status
@ -280,7 +280,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
reporter {
preferredUsername
},
event {
events {
title
},
comments {
@ -312,7 +312,9 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
reporter.preferred_username
assert json_response(res, 200)["data"]["report"]["content"] == report.content
assert json_response(res, 200)["data"]["report"]["event"]["title"] == report.event.title
assert json_response(res, 200)["data"]["report"]["events"] |> hd |> Map.get("title") ==
report.events |> hd |> Map.get(:title)
assert json_response(res, 200)["data"]["report"]["comments"] |> hd |> Map.get("text") ==
report.comments |> hd |> Map.get(:text)

View file

@ -322,7 +322,7 @@ defmodule Mobilizon.Factory do
url: "http://mobilizon.test/report/deae1020-54b8-47df-9eea-d8c0e943e57f/activity",
reported: build(:actor),
reporter: build(:actor),
event: build(:event),
events: build_list(1, :event),
comments: build_list(1, :comment)
}
end