Merge tag '1.0.7' into osm-theme
This commit is contained in:
commit
8072175c51
|
@ -205,15 +205,13 @@ build-docker-tag:
|
||||||
|
|
||||||
package-app:
|
package-app:
|
||||||
stage: package
|
stage: package
|
||||||
before_script:
|
|
||||||
- apt update
|
|
||||||
- apt install -y --no-install-recommends build-essential git cmake
|
|
||||||
variables:
|
variables:
|
||||||
MIX_ENV: "prod"
|
MIX_ENV: "prod"
|
||||||
script:
|
script:
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
- cp docker/production/releases.exs ./config/
|
- cp docker/production/releases.exs ./config/
|
||||||
|
- mix deps.get
|
||||||
- mix phx.digest
|
- mix phx.digest
|
||||||
- mix release
|
- mix release
|
||||||
artifacts:
|
artifacts:
|
||||||
|
@ -240,18 +238,17 @@ release-upload:
|
||||||
when: on_success
|
when: on_success
|
||||||
paths:
|
paths:
|
||||||
- mobilizon_*.tar.gz
|
- mobilizon_*.tar.gz
|
||||||
|
# release-create:
|
||||||
|
# stage: deploy
|
||||||
|
# image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
|
# rules:
|
||||||
|
# - if: $CI_COMMIT_TAG
|
||||||
|
# dependencies: []
|
||||||
|
# cache: {}
|
||||||
|
# script: |
|
||||||
|
# APP_VERSION="${CI_COMMIT_TAG}"
|
||||||
|
# APP_ASSET="${CI_PROJECT_NAME}_${APP_VERSION}_${ARCH}.tar.gz"
|
||||||
|
|
||||||
release-create:
|
# release-cli create --name "$CI_PROJECT_TITLE v$CI_COMMIT_TAG" \
|
||||||
stage: deploy
|
# --tag-name "$CI_COMMIT_TAG" \
|
||||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
# --assets-link "{\"name\":\"${APP_ASSET}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${APP_VERSION}/${APP_ASSET}\"}"
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_TAG
|
|
||||||
dependencies: []
|
|
||||||
cache: {}
|
|
||||||
script: |
|
|
||||||
APP_VERSION="${CI_COMMIT_TAG}"
|
|
||||||
APP_ASSET="${CI_PROJECT_NAME}_${APP_VERSION}_${ARCH}.tar.gz"
|
|
||||||
|
|
||||||
release-cli create --name "$CI_PROJECT_TITLE v$CI_COMMIT_TAG" \
|
|
||||||
--tag-name "$CI_COMMIT_TAG" \
|
|
||||||
--assets-link "{\"name\":\"${APP_ASSET}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${APP_VERSION}/${APP_ASSET}\"}"
|
|
||||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -5,13 +5,35 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## 1.0.5 - 27-01-2020
|
## 1.0.7 - 27-02-2021
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed accessing group event unlogged
|
||||||
|
- Fixed broken redirection with Webfinger due to strict connect-src
|
||||||
|
- Fixed editing group discussions
|
||||||
|
- Fixed search form display
|
||||||
|
- Fixed wrong year in CHANGELOG.md
|
||||||
|
|
||||||
|
## 1.0.6 - 04-02-2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Handle frontend errors nicely when mounting components
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed displaying a remote event when organizer is a group
|
||||||
|
- Fixed sending events & posts to group followers
|
||||||
|
- Fixed redirection after deleting an event
|
||||||
|
|
||||||
|
## 1.0.5 - 27-01-2021
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed duplicate entries in search with empty search query
|
- Fixed duplicate entries in search with empty search query
|
||||||
|
|
||||||
## 1.0.4 - 26-01-2020
|
## 1.0.4 - 26-01-2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mobilizon",
|
"name": "mobilizon",
|
||||||
"version": "1.0.5",
|
"version": "1.0.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
</p>
|
</p>
|
||||||
</b-message>
|
</b-message>
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<error v-if="error" :error="error" />
|
||||||
|
|
||||||
|
<main v-else>
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<router-view />
|
<router-view />
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -57,6 +59,8 @@ import { ICurrentUser } from "./types/current-user.model";
|
||||||
components: {
|
components: {
|
||||||
Logo,
|
Logo,
|
||||||
NavBar,
|
NavBar,
|
||||||
|
error: () =>
|
||||||
|
import(/* webpackChunkName: "editor" */ "./components/Error.vue"),
|
||||||
"mobilizon-footer": Footer,
|
"mobilizon-footer": Footer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -65,12 +69,18 @@ export default class App extends Vue {
|
||||||
|
|
||||||
currentUser!: ICurrentUser;
|
currentUser!: ICurrentUser;
|
||||||
|
|
||||||
|
error: Error | null = null;
|
||||||
|
|
||||||
async created(): Promise<void> {
|
async created(): Promise<void> {
|
||||||
if (await this.initializeCurrentUser()) {
|
if (await this.initializeCurrentUser()) {
|
||||||
await initializeCurrentActor(this.$apollo.provider.defaultClient);
|
await initializeCurrentActor(this.$apollo.provider.defaultClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorCaptured(error: Error): void {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
private async initializeCurrentUser() {
|
private async initializeCurrentUser() {
|
||||||
const userId = localStorage.getItem(AUTH_USER_ID);
|
const userId = localStorage.getItem(AUTH_USER_ID);
|
||||||
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
|
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template>
|
||||||
|
<p>
|
||||||
|
<a :title="contact" v-if="configLink" :href="configLink.uri">{{
|
||||||
|
configLink.text
|
||||||
|
}}</a>
|
||||||
|
<span v-else-if="contact">{{ contact }}</span>
|
||||||
|
<span v-else>{{ $t("contact uninformed") }}</span>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class InstanceContactLink extends Vue {
|
||||||
|
@Prop({ required: true, type: String }) contact!: string;
|
||||||
|
|
||||||
|
get configLink(): { uri: string; text: string } | null {
|
||||||
|
if (!this.contact) return null;
|
||||||
|
if (this.isContactEmail) {
|
||||||
|
return {
|
||||||
|
uri: `mailto:${this.contact}`,
|
||||||
|
text: this.contact,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.isContactURL) {
|
||||||
|
return {
|
||||||
|
uri: this.contact,
|
||||||
|
text:
|
||||||
|
InstanceContactLink.urlToHostname(this.contact) ||
|
||||||
|
(this.$t("Contact") as string),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isContactEmail(): boolean {
|
||||||
|
return this.contact.includes("@");
|
||||||
|
}
|
||||||
|
|
||||||
|
get isContactURL(): boolean {
|
||||||
|
return this.contact.match(/^https?:\/\//g) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static urlToHostname(url: string): string | null {
|
||||||
|
try {
|
||||||
|
return new URL(url).hostname;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -296,11 +296,9 @@ export default class Comment extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
get commentFromOrganizer(): boolean {
|
get commentFromOrganizer(): boolean {
|
||||||
return (
|
const organizerId =
|
||||||
this.event.organizerActor !== undefined &&
|
this.event?.organizerActor?.id || this.event?.attributedTo?.id;
|
||||||
this.comment.actor != null &&
|
return organizerId !== undefined && this.comment?.actor?.id === organizerId;
|
||||||
this.comment.actor.id === this.event.organizerActor.id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get commentId(): string {
|
get commentId(): string {
|
||||||
|
|
|
@ -320,11 +320,9 @@ export default class CommentTree extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEventOrganiser(): boolean {
|
get isEventOrganiser(): boolean {
|
||||||
return (
|
const organizerId =
|
||||||
this.currentActor.id !== undefined &&
|
this.event?.organizerActor?.id || this.event?.attributedTo?.id;
|
||||||
this.event.organizerActor !== undefined &&
|
return organizerId !== undefined && this.currentActor?.id === organizerId;
|
||||||
this.currentActor.id === this.event.organizerActor.id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get areCommentsClosed(): boolean {
|
get areCommentsClosed(): boolean {
|
||||||
|
@ -335,7 +333,7 @@ export default class CommentTree extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAbleToComment(): boolean {
|
get isAbleToComment(): boolean {
|
||||||
if (this.currentActor && this.currentActor.id) {
|
if (this.currentActor?.id) {
|
||||||
return this.areCommentsClosed || this.isEventOrganiser;
|
return this.areCommentsClosed || this.isEventOrganiser;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
<template>
|
||||||
|
<div class="container section" id="error-wrapper">
|
||||||
|
<div class="column">
|
||||||
|
<section>
|
||||||
|
<div class="picture-wrapper">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
srcset="
|
||||||
|
/img/pics/error-480w.webp 1x,
|
||||||
|
/img/pics/error-1024w.webp 2x
|
||||||
|
"
|
||||||
|
type="image/webp"
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
srcset="/img/pics/error-480w.jpg 1x, /img/pics/error-1024w.jpg 2x"
|
||||||
|
type="image/jpeg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
:src="`/img/pics/error-480w.jpg`"
|
||||||
|
alt=""
|
||||||
|
width="480"
|
||||||
|
height="312"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
<b-message type="is-danger" class="is-size-5">
|
||||||
|
<h1>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"An error has occured. Sorry about that. You may try to reload the page."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</h1>
|
||||||
|
</b-message>
|
||||||
|
</section>
|
||||||
|
<b-loading v-if="$apollo.loading" :active.sync="$apollo.loading" />
|
||||||
|
<section v-else>
|
||||||
|
<h2 class="is-size-5">{{ $t("What can I do to help?") }}</h2>
|
||||||
|
<p class="content">
|
||||||
|
<i18n
|
||||||
|
tag="span"
|
||||||
|
path="{instanceName} is an instance of {mobilizon_link}, a free software built with the community."
|
||||||
|
>
|
||||||
|
<b slot="instanceName">{{ config.name }}</b>
|
||||||
|
<a slot="mobilizon_link" href="https://joinmobilizon.org">{{
|
||||||
|
$t("Mobilizon")
|
||||||
|
}}</a>
|
||||||
|
</i18n>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<div class="content">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://framacolibri.org/c/mobilizon/39"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t("Open a topic on our forum") }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://framagit.org/framasoft/mobilizon/-/issues/new?issuable_template=Bug"
|
||||||
|
target="_blank"
|
||||||
|
>{{
|
||||||
|
$t("Open an issue on our bug tracker (advanced users)")
|
||||||
|
}}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p class="content">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"Please add as many details as possible to help identify the problem."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary class="is-size-5">{{ $t("Technical details") }}</summary>
|
||||||
|
<p>{{ $t("Error message") }}</p>
|
||||||
|
<pre>{{ error }}</pre>
|
||||||
|
<p>{{ $t("Error stacktrace") }}</p>
|
||||||
|
<pre>{{ error.stack }}</pre>
|
||||||
|
</details>
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<b-tooltip
|
||||||
|
:label="tooltipConfig.label"
|
||||||
|
:type="tooltipConfig.type"
|
||||||
|
:active="copied !== false"
|
||||||
|
always
|
||||||
|
>
|
||||||
|
<b-button @click="copyErrorToClipboard">{{
|
||||||
|
$t("Copy details to clipboard")
|
||||||
|
}}</b-button>
|
||||||
|
</b-tooltip>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { CONTACT } from "@/graphql/config";
|
||||||
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
|
import InstanceContactLink from "@/components/About/InstanceContactLink.vue";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
apollo: {
|
||||||
|
config: {
|
||||||
|
query: CONTACT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
title: this.$t("Error") as string,
|
||||||
|
titleTemplate: "%s | Mobilizon",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
InstanceContactLink,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class ErrorComponent extends Vue {
|
||||||
|
@Prop({ required: true, type: Error }) error!: Error;
|
||||||
|
|
||||||
|
copied: "success" | "error" | false = false;
|
||||||
|
|
||||||
|
config!: { contact: string | null; name: string };
|
||||||
|
|
||||||
|
async copyErrorToClipboard(): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (window.isSecureContext && navigator.clipboard) {
|
||||||
|
await navigator.clipboard.writeText(this.fullErrorString);
|
||||||
|
} else {
|
||||||
|
this.fallbackCopyTextToClipboard(this.fullErrorString);
|
||||||
|
}
|
||||||
|
this.copied = "success";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.copied = false;
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
this.copied = "error";
|
||||||
|
console.error("Unable to copy to clipboard");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get fullErrorString(): string {
|
||||||
|
return `${this.error.name}: ${this.error.message}\n\n${this.error.stack}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tooltipConfig(): { label: string | null; type: string | null } {
|
||||||
|
if (this.copied === "success")
|
||||||
|
return {
|
||||||
|
label: this.$t("Error details copied!") as string,
|
||||||
|
type: "is-success",
|
||||||
|
};
|
||||||
|
if (this.copied === "error")
|
||||||
|
return {
|
||||||
|
label: this.$t("Unable to copy to clipboard") as string,
|
||||||
|
type: "is-danger",
|
||||||
|
};
|
||||||
|
return { label: null, type: "is-primary" };
|
||||||
|
}
|
||||||
|
|
||||||
|
private fallbackCopyTextToClipboard(text: string): void {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
|
||||||
|
// Avoid scrolling to bottom
|
||||||
|
textArea.style.top = "0";
|
||||||
|
textArea.style.left = "0";
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
|
||||||
|
document.execCommand("copy");
|
||||||
|
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
#error-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
background: $white;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picture-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
summary:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -112,6 +112,15 @@ export const ABOUT = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const CONTACT = gql`
|
||||||
|
query Contact {
|
||||||
|
config {
|
||||||
|
name
|
||||||
|
contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const RULES = gql`
|
export const RULES = gql`
|
||||||
query Rules {
|
query Rules {
|
||||||
config {
|
config {
|
||||||
|
|
|
@ -836,5 +836,19 @@
|
||||||
"No follower matches the filters": "No follower matches the filters",
|
"No follower matches the filters": "No follower matches the filters",
|
||||||
"@{username}'s follow request was rejected": "@{username}'s follow request was rejected",
|
"@{username}'s follow request was rejected": "@{username}'s follow request was rejected",
|
||||||
"Followers will receive new public events and posts.": "Followers will receive new public events and posts.",
|
"Followers will receive new public events and posts.": "Followers will receive new public events and posts.",
|
||||||
"Manually approve new followers": "Manually approve new followers"
|
"Manually approve new followers": "Manually approve new followers",
|
||||||
|
"An error has occured. Sorry about that. You may try to reload the page.": "An error has occured. Sorry about that. You may try to reload the page.",
|
||||||
|
"What can I do to help?": "What can I do to help?",
|
||||||
|
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):'",
|
||||||
|
"Please add as many details as possible to help identify the problem.": "Please add as many details as possible to help identify the problem.",
|
||||||
|
"Technical details": "Technical details",
|
||||||
|
"Error message": "Error message",
|
||||||
|
"Error stacktrace": "Error stacktrace",
|
||||||
|
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.",
|
||||||
|
"Error details copied!": "Error details copied!",
|
||||||
|
"Copy details to clipboard": "Copy details to clipboard",
|
||||||
|
"{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} is an instance of {mobilizon_link}, a free software built with the community.",
|
||||||
|
"Open a topic on our forum": "Open a topic on our forum",
|
||||||
|
"Open an issue on our bug tracker (advanced users)": "Open an issue on our bug tracker (advanced users)",
|
||||||
|
"Unable to copy to clipboard": "Unable to copy to clipboard"
|
||||||
}
|
}
|
||||||
|
|
|
@ -931,5 +931,19 @@
|
||||||
"No follower matches the filters": "Aucun⋅e abonné⋅e ne correspond aux filtres",
|
"No follower matches the filters": "Aucun⋅e abonné⋅e ne correspond aux filtres",
|
||||||
"@{username}'s follow request was rejected": "La demande de suivi de @{username} a été rejettée",
|
"@{username}'s follow request was rejected": "La demande de suivi de @{username} a été rejettée",
|
||||||
"Followers will receive new public events and posts.": "Les abonnée⋅s recevront les nouveaux événements et billets publics.",
|
"Followers will receive new public events and posts.": "Les abonnée⋅s recevront les nouveaux événements et billets publics.",
|
||||||
"Manually approve new followers": "Approuver les nouvelles demandes de suivi manuellement"
|
"Manually approve new followers": "Approuver les nouvelles demandes de suivi manuellement",
|
||||||
|
"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.",
|
||||||
|
"What can I do to help?": "Que puis-je faire pour aider ?",
|
||||||
|
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Nous améliorons ce logiciel grâce à vos retours. Pour nous avertir de ce problème, vous avez deux possibilités (les deux requièrent toutefois la création d'un compte) :",
|
||||||
|
"Please add as many details as possible to help identify the problem.": "Merci d'ajouter un maximum de détails afin d'aider à identifier le problème.",
|
||||||
|
"Technical details": "Détails techniques",
|
||||||
|
"Error message": "Message d'erreur",
|
||||||
|
"Error stacktrace": "Trace d'appels de l'erreur",
|
||||||
|
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Les détails techniques de l'erreur peuvent aider les développeur⋅ices à résoudre le problème plus facilement. Merci de les inclure dans vos retours.",
|
||||||
|
"Error details copied!": "Détails de l'erreur copiés !",
|
||||||
|
"Copy details to clipboard": "Copier les détails dans le presse-papiers",
|
||||||
|
"{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} est une instance de {mobilizon_link}, un logiciel libre construit de manière communautaire.",
|
||||||
|
"Open a topic on our forum": "Ouvrir un sujet sur notre forum",
|
||||||
|
"Open an issue on our bug tracker (advanced users)": "Ouvrir un ticket sur notre système de suivi des bugs (utilisateur⋅ices avancé⋅es)",
|
||||||
|
"Unable to copy to clipboard": "Impossible de copier dans le presse-papiers"
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,5 +157,10 @@ const router = new Router({
|
||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach(authGuardIfNeeded);
|
router.beforeEach(authGuardIfNeeded);
|
||||||
|
router.afterEach(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
router.app.$children[0].error = null;
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -25,16 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column contact">
|
<div class="column contact">
|
||||||
<h4>{{ $t("Contact") }}</h4>
|
<h4>{{ $t("Contact") }}</h4>
|
||||||
<p>
|
<instance-contact-link :contact="config.contact" />
|
||||||
<a
|
|
||||||
:title="config.contact"
|
|
||||||
v-if="generateConfigLink()"
|
|
||||||
:href="generateConfigLink().uri"
|
|
||||||
>{{ generateConfigLink().text }}</a
|
|
||||||
>
|
|
||||||
<span v-else-if="config.contact">{{ config.contact }}</span>
|
|
||||||
<span v-else>{{ $t("contact uninformed") }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -85,6 +76,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import { formatList } from "@/utils/i18n";
|
import { formatList } from "@/utils/i18n";
|
||||||
|
import InstanceContactLink from "@/components/About/InstanceContactLink.vue";
|
||||||
import { LANGUAGES_CODES } from "@/graphql/admin";
|
import { LANGUAGES_CODES } from "@/graphql/admin";
|
||||||
import { ILanguage } from "@/types/admin.model";
|
import { ILanguage } from "@/types/admin.model";
|
||||||
import { ABOUT } from "../../graphql/config";
|
import { ABOUT } from "../../graphql/config";
|
||||||
|
@ -109,6 +101,9 @@ import langs from "../../i18n/langs.json";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
InstanceContactLink,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class AboutInstance extends Vue {
|
export default class AboutInstance extends Vue {
|
||||||
config!: IConfig;
|
config!: IConfig;
|
||||||
|
@ -117,14 +112,6 @@ export default class AboutInstance extends Vue {
|
||||||
|
|
||||||
languages!: ILanguage[];
|
languages!: ILanguage[];
|
||||||
|
|
||||||
get isContactEmail(): boolean {
|
|
||||||
return this.config && this.config.contact.includes("@");
|
|
||||||
}
|
|
||||||
|
|
||||||
get isContactURL(): boolean {
|
|
||||||
return this.config && this.config.contact.match(/^https?:\/\//g) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get formattedLanguageList(): string {
|
get formattedLanguageList(): string {
|
||||||
if (this.languages) {
|
if (this.languages) {
|
||||||
const list = this.languages.map(({ name }) => name);
|
const list = this.languages.map(({ name }) => name);
|
||||||
|
@ -138,33 +125,6 @@ export default class AboutInstance extends Vue {
|
||||||
const languageMaps = langs as Record<string, any>;
|
const languageMaps = langs as Record<string, any>;
|
||||||
return languageMaps[code];
|
return languageMaps[code];
|
||||||
}
|
}
|
||||||
|
|
||||||
generateConfigLink(): { uri: string; text: string } | null {
|
|
||||||
if (!this.config.contact) return null;
|
|
||||||
if (this.isContactEmail) {
|
|
||||||
return {
|
|
||||||
uri: `mailto:${this.config.contact}`,
|
|
||||||
text: this.config.contact,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (this.isContactURL) {
|
|
||||||
return {
|
|
||||||
uri: this.config.contact,
|
|
||||||
text:
|
|
||||||
AboutInstance.urlToHostname(this.config.contact) ||
|
|
||||||
(this.$t("Contact") as string),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static urlToHostname(url: string): string | null {
|
|
||||||
try {
|
|
||||||
return new URL(url).hostname;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -579,7 +579,7 @@ export default class EditEvent extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDefaultActor() {
|
private getDefaultActor() {
|
||||||
if (this.event.organizerActor && this.event.organizerActor.id) {
|
if (this.event.organizerActor?.id) {
|
||||||
return this.event.organizerActor;
|
return this.event.organizerActor;
|
||||||
}
|
}
|
||||||
return this.currentActor;
|
return this.currentActor;
|
||||||
|
@ -725,7 +725,7 @@ export default class EditEvent extends Vue {
|
||||||
get isCurrentActorOrganizer(): boolean {
|
get isCurrentActorOrganizer(): boolean {
|
||||||
return !(
|
return !(
|
||||||
this.eventId &&
|
this.eventId &&
|
||||||
this.event.organizerActor &&
|
this.event.organizerActor?.id !== undefined &&
|
||||||
this.currentActor.id !== this.event.organizerActor.id
|
this.currentActor.id !== this.event.organizerActor.id
|
||||||
) as boolean;
|
) as boolean;
|
||||||
}
|
}
|
||||||
|
@ -822,19 +822,17 @@ export default class EditEvent extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
get attributedToEqualToOrganizerActor(): boolean {
|
get attributedToEqualToOrganizerActor(): boolean {
|
||||||
return (this.event.organizerActor &&
|
return (this.event.organizerActor?.id !== undefined &&
|
||||||
this.event.attributedTo &&
|
this.event.attributedTo?.id === this.event.organizerActor?.id) as boolean;
|
||||||
this.event.attributedTo.id === this.event.organizerActor.id) as boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build variables for Event GraphQL creation query
|
* Build variables for Event GraphQL creation query
|
||||||
*/
|
*/
|
||||||
private async buildVariables() {
|
private async buildVariables() {
|
||||||
this.event.organizerActor =
|
this.event.organizerActor = this.event.organizerActor?.id
|
||||||
this.event.organizerActor && this.event.organizerActor.id
|
? this.event.organizerActor
|
||||||
? this.event.organizerActor
|
: this.currentActor;
|
||||||
: this.currentActor;
|
|
||||||
let res = this.event.toEditJSON();
|
let res = this.event.toEditJSON();
|
||||||
if (this.event.organizerActor) {
|
if (this.event.organizerActor) {
|
||||||
res = Object.assign(res, {
|
res = Object.assign(res, {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<b-loading :active.sync="$apollo.loading" />
|
|
||||||
<transition appear name="fade" mode="out-in">
|
<transition appear name="fade" mode="out-in">
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -125,9 +124,9 @@
|
||||||
<b-icon icon="link" />
|
<b-icon icon="link" />
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!event.local">
|
<template v-if="!event.local && organizer">
|
||||||
<a :href="event.url">
|
<a :href="event.url">
|
||||||
<tag>{{ event.organizerActor.domain }}</tag>
|
<tag>{{ organizer.domain }}</tag>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<p>
|
<p>
|
||||||
|
@ -443,7 +442,7 @@
|
||||||
<report-modal
|
<report-modal
|
||||||
:on-confirm="reportEvent"
|
:on-confirm="reportEvent"
|
||||||
:title="$t('Report this event')"
|
:title="$t('Report this event')"
|
||||||
:outside-domain="domainForReport"
|
:outside-domain="organizerDomain"
|
||||||
@close="$refs.reportModal.close()"
|
@close="$refs.reportModal.close()"
|
||||||
/>
|
/>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
@ -942,7 +941,7 @@ export default class Event extends EventMixin {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$on("eventDeleted", () => {
|
this.$on("event-deleted", () => {
|
||||||
return this.$router.push({ name: RouteName.HOME });
|
return this.$router.push({ name: RouteName.HOME });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -959,7 +958,7 @@ export default class Event extends EventMixin {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.$refs.reportModal.close();
|
this.$refs.reportModal.close();
|
||||||
if (!this.event.organizerActor) return;
|
if (!this.organizer) return;
|
||||||
const eventTitle = this.event.title;
|
const eventTitle = this.event.title;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -967,7 +966,7 @@ export default class Event extends EventMixin {
|
||||||
mutation: CREATE_REPORT,
|
mutation: CREATE_REPORT,
|
||||||
variables: {
|
variables: {
|
||||||
eventId: this.event.id,
|
eventId: this.event.id,
|
||||||
reportedId: this.actorForReport ? this.actorForReport.id : null,
|
reportedId: this.organizer ? this.organizer.id : null,
|
||||||
content,
|
content,
|
||||||
forward,
|
forward,
|
||||||
},
|
},
|
||||||
|
@ -1240,7 +1239,7 @@ export default class Event extends EventMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get actorForReport(): IActor | null {
|
get organizer(): IActor | null {
|
||||||
if (this.event.attributedTo && this.event.attributedTo.id) {
|
if (this.event.attributedTo && this.event.attributedTo.id) {
|
||||||
return this.event.attributedTo;
|
return this.event.attributedTo;
|
||||||
}
|
}
|
||||||
|
@ -1250,9 +1249,9 @@ export default class Event extends EventMixin {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get domainForReport(): string | null {
|
get organizerDomain(): string | null {
|
||||||
if (this.actorForReport) {
|
if (this.organizer) {
|
||||||
return this.actorForReport.domain;
|
return this.organizer.domain;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
/>
|
/>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field :label="$t('Radius')" label-for="radius">
|
<b-field :label="$t('Radius')" label-for="radius">
|
||||||
<b-select v-model="radius" id="radius">
|
<b-select v-model="radius" id="radius" expanded>
|
||||||
<option
|
<option
|
||||||
v-for="(radiusOption, index) in radiusOptions"
|
v-for="(radiusOption, index) in radiusOptions"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
@ -42,7 +42,12 @@
|
||||||
</b-select>
|
</b-select>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field :label="$t('Date')" label-for="date">
|
<b-field :label="$t('Date')" label-for="date">
|
||||||
<b-select v-model="when" id="date" :disabled="activeTab !== 0">
|
<b-select
|
||||||
|
v-model="when"
|
||||||
|
id="date"
|
||||||
|
:disabled="activeTab !== 0"
|
||||||
|
expanded
|
||||||
|
>
|
||||||
<option
|
<option
|
||||||
v-for="(option, index) in options"
|
v-for="(option, index) in options"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
@ -461,5 +466,16 @@ form {
|
||||||
::v-deep .field label.label {
|
::v-deep .field label.label {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field.is-expanded:last-child > .field-body > .field.is-grouped {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
|
.field {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
&:first-child {
|
||||||
|
flex: 3 0 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
1313
js/yarn.lock
1313
js/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -710,6 +710,8 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||||
Relay.publish(activity)
|
Relay.publish(activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
recipients = Enum.uniq(recipients)
|
||||||
|
|
||||||
{recipients, followers} = convert_followers_in_recipients(recipients)
|
{recipients, followers} = convert_followers_in_recipients(recipients)
|
||||||
|
|
||||||
{recipients, members} = convert_members_in_recipients(recipients)
|
{recipients, members} = convert_members_in_recipients(recipients)
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
|
alias Mobilizon.Posts.Post
|
||||||
alias Mobilizon.Share
|
alias Mobilizon.Share
|
||||||
alias Mobilizon.Storage.Repo
|
alias Mobilizon.Storage.Repo
|
||||||
|
|
||||||
|
@ -64,10 +65,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
||||||
{mentions, []}
|
{mentions, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
# def get_addressed_actors(_, to) when is_list(to) do
|
|
||||||
# Actors.get(to)
|
|
||||||
# end
|
|
||||||
|
|
||||||
def get_addressed_actors(mentioned_users, _), do: mentioned_users
|
def get_addressed_actors(mentioned_users, _), do: mentioned_users
|
||||||
|
|
||||||
def calculate_to_and_cc_from_mentions(
|
def calculate_to_and_cc_from_mentions(
|
||||||
|
@ -79,9 +76,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_to_and_cc_from_mentions(%Comment{} = comment) do
|
def calculate_to_and_cc_from_mentions(%Comment{} = comment) do
|
||||||
with mentioned_actors <- Enum.map(comment.mentions, &process_mention/1),
|
with {to, cc} <-
|
||||||
addressed_actors <- get_addressed_actors(mentioned_actors, nil),
|
extract_actors_from_mentions(comment.mentions, comment.actor, comment.visibility),
|
||||||
{to, cc} <- get_to_and_cc(comment.actor, addressed_actors, comment.visibility),
|
|
||||||
{to, cc} <- {Enum.uniq(to ++ add_in_reply_to(comment.in_reply_to_comment)), cc},
|
{to, cc} <- {Enum.uniq(to ++ add_in_reply_to(comment.in_reply_to_comment)), cc},
|
||||||
{to, cc} <- {Enum.uniq(to ++ add_event_author(comment.event)), cc},
|
{to, cc} <- {Enum.uniq(to ++ add_event_author(comment.event)), cc},
|
||||||
{to, cc} <-
|
{to, cc} <-
|
||||||
|
@ -101,32 +97,45 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_to_and_cc_from_mentions(%Event{
|
def calculate_to_and_cc_from_mentions(
|
||||||
attributed_to: %Actor{members_url: members_url},
|
%Event{
|
||||||
visibility: visibility
|
attributed_to: %Actor{members_url: members_url},
|
||||||
}) do
|
visibility: visibility
|
||||||
|
} = event
|
||||||
|
) do
|
||||||
|
%{"to" => to, "cc" => cc} = extract_actors_from_event(event)
|
||||||
|
|
||||||
case visibility do
|
case visibility do
|
||||||
:public ->
|
:public ->
|
||||||
%{"to" => [members_url, @ap_public], "cc" => []}
|
%{"to" => [@ap_public, members_url] ++ to, "cc" => [] ++ cc}
|
||||||
|
|
||||||
:unlisted ->
|
:unlisted ->
|
||||||
%{"to" => [members_url], "cc" => [@ap_public]}
|
%{"to" => [members_url] ++ to, "cc" => [@ap_public] ++ cc}
|
||||||
|
|
||||||
:private ->
|
:private ->
|
||||||
|
# Private is restricted to only the members
|
||||||
%{"to" => [members_url], "cc" => []}
|
%{"to" => [members_url], "cc" => []}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_to_and_cc_from_mentions(%Event{} = event) do
|
def calculate_to_and_cc_from_mentions(%Event{} = event) do
|
||||||
with mentioned_actors <- Enum.map(event.mentions, &process_mention/1),
|
extract_actors_from_event(event)
|
||||||
addressed_actors <- get_addressed_actors(mentioned_actors, nil),
|
end
|
||||||
{to, cc} <- get_to_and_cc(event.organizer_actor, addressed_actors, event.visibility),
|
|
||||||
{to, cc} <-
|
def calculate_to_and_cc_from_mentions(%Post{
|
||||||
{to,
|
attributed_to: %Actor{members_url: members_url},
|
||||||
Enum.uniq(
|
visibility: visibility
|
||||||
cc ++ add_comments_authors(event.comments) ++ add_shares_actors_followers(event.url)
|
}) do
|
||||||
)} do
|
case visibility do
|
||||||
%{"to" => to, "cc" => cc}
|
:public ->
|
||||||
|
%{"to" => [@ap_public, members_url], "cc" => []}
|
||||||
|
|
||||||
|
:unlisted ->
|
||||||
|
%{"to" => [members_url], "cc" => [@ap_public]}
|
||||||
|
|
||||||
|
:private ->
|
||||||
|
# Private is restricted to only the members
|
||||||
|
%{"to" => [members_url], "cc" => []}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -211,4 +220,27 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
||||||
url
|
url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec extract_actors_from_mentions(list(), Actor.t(), atom()) :: {list(), list()}
|
||||||
|
defp extract_actors_from_mentions(mentions, actor, visibility) do
|
||||||
|
with mentioned_actors <- Enum.map(mentions, &process_mention/1),
|
||||||
|
addressed_actors <- get_addressed_actors(mentioned_actors, nil) do
|
||||||
|
get_to_and_cc(actor, addressed_actors, visibility)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_actors_from_event(%Event{} = event) do
|
||||||
|
with {to, cc} <-
|
||||||
|
extract_actors_from_mentions(event.mentions, event.organizer_actor, event.visibility),
|
||||||
|
{to, cc} <-
|
||||||
|
{to,
|
||||||
|
Enum.uniq(
|
||||||
|
cc ++ add_comments_authors(event.comments) ++ add_shares_actors_followers(event.url)
|
||||||
|
)} do
|
||||||
|
%{"to" => to, "cc" => cc}
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
%{"to" => [], "cc" => []}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
alias Mobilizon.{Actors, Posts, Tombstone}
|
alias Mobilizon.{Actors, Posts, Tombstone}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Federation.ActivityPub.Audience
|
||||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||||
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
||||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||||
|
@ -19,15 +20,11 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||||
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
|
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
|
||||||
Posts.create_post(args),
|
Posts.create_post(args),
|
||||||
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||||
%Actor{url: creator_url} = creator <- Actors.get_actor(creator_id),
|
%Actor{} = creator <- Actors.get_actor(creator_id),
|
||||||
post_as_data <-
|
post_as_data <-
|
||||||
Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
|
Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
|
||||||
audience <- %{
|
audience <-
|
||||||
"to" => [group.members_url],
|
Audience.calculate_to_and_cc_from_mentions(post) do
|
||||||
"cc" => [],
|
|
||||||
"actor" => creator_url,
|
|
||||||
"attributedTo" => [creator_url]
|
|
||||||
} do
|
|
||||||
create_data = make_create_data(post_as_data, Map.merge(audience, additional))
|
create_data = make_create_data(post_as_data, Map.merge(audience, additional))
|
||||||
|
|
||||||
{:ok, post, create_data}
|
{:ok, post, create_data}
|
||||||
|
@ -44,16 +41,12 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||||
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
|
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
|
||||||
Posts.update_post(post, args),
|
Posts.update_post(post, args),
|
||||||
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
|
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
|
||||||
{:ok, %Actor{url: group_url} = group} <- Actors.get_group_by_actor_id(group_id),
|
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||||
%Actor{url: creator_url} = creator <- Actors.get_actor(creator_id),
|
%Actor{} = creator <- Actors.get_actor(creator_id),
|
||||||
post_as_data <-
|
post_as_data <-
|
||||||
Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
|
Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
|
||||||
audience <- %{
|
audience <-
|
||||||
"to" => [group.members_url],
|
Audience.calculate_to_and_cc_from_mentions(post) do
|
||||||
"cc" => [],
|
|
||||||
"actor" => creator_url,
|
|
||||||
"attributedTo" => [group_url]
|
|
||||||
} do
|
|
||||||
update_data = make_update_data(post_as_data, Map.merge(audience, additional))
|
update_data = make_update_data(post_as_data, Map.merge(audience, additional))
|
||||||
|
|
||||||
{:ok, post, update_data}
|
{:ok, post, update_data}
|
||||||
|
|
|
@ -127,7 +127,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
||||||
def maybe_relay_if_group_activity(activity, attributed_to \\ nil)
|
def maybe_relay_if_group_activity(activity, attributed_to \\ nil)
|
||||||
|
|
||||||
def maybe_relay_if_group_activity(
|
def maybe_relay_if_group_activity(
|
||||||
%Activity{local: false, data: %{"object" => object}},
|
%Activity{data: %{"object" => object}},
|
||||||
_attributed_to
|
_attributed_to
|
||||||
)
|
)
|
||||||
when is_map(object) do
|
when is_map(object) do
|
||||||
|
@ -136,7 +136,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
||||||
|
|
||||||
# When doing a delete the object is just an AP ID string, so we pass the attributed_to actor as well
|
# When doing a delete the object is just an AP ID string, so we pass the attributed_to actor as well
|
||||||
def maybe_relay_if_group_activity(
|
def maybe_relay_if_group_activity(
|
||||||
%Activity{local: false, data: %{"object" => object}},
|
%Activity{data: %{"object" => object}},
|
||||||
%Actor{url: attributed_to_url}
|
%Actor{url: attributed_to_url}
|
||||||
)
|
)
|
||||||
when is_binary(object) do
|
when is_binary(object) do
|
||||||
|
@ -421,7 +421,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
||||||
["https://www.w3.org/ns/activitystreams#Public"]}
|
["https://www.w3.org/ns/activitystreams#Public"]}
|
||||||
else
|
else
|
||||||
if actor_type == :Group do
|
if actor_type == :Group do
|
||||||
{[actor.members_url], []}
|
{[actor.followers_url, actor.members_url], []}
|
||||||
else
|
else
|
||||||
{[actor.followers_url], []}
|
{[actor.followers_url], []}
|
||||||
end
|
end
|
||||||
|
|
|
@ -94,18 +94,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
to =
|
to =
|
||||||
if event.visibility == :public,
|
if event.visibility == :public,
|
||||||
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
else: [event.organizer_actor.followers_url]
|
else: [attributed_to_or_default(event).followers_url]
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Event",
|
"type" => "Event",
|
||||||
"to" => to,
|
"to" => to,
|
||||||
"cc" => [],
|
"cc" => [],
|
||||||
"attributedTo" =>
|
"attributedTo" => attributed_to_or_default(event).url,
|
||||||
if(is_nil(event.attributed_to) or not Ecto.assoc_loaded?(event.attributed_to),
|
|
||||||
do: nil,
|
|
||||||
else: event.attributed_to.url
|
|
||||||
) ||
|
|
||||||
event.organizer_actor.url,
|
|
||||||
"name" => event.title,
|
"name" => event.title,
|
||||||
"actor" =>
|
"actor" =>
|
||||||
if(Ecto.assoc_loaded?(event.organizer_actor), do: event.organizer_actor.url, else: nil),
|
if(Ecto.assoc_loaded?(event.organizer_actor), do: event.organizer_actor.url, else: nil),
|
||||||
|
@ -135,6 +130,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
|> maybe_add_inline_media(event)
|
|> maybe_add_inline_media(event)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec attributed_to_or_default(Event.t()) :: Actor.t()
|
||||||
|
defp attributed_to_or_default(event) do
|
||||||
|
if(is_nil(event.attributed_to) or not Ecto.assoc_loaded?(event.attributed_to),
|
||||||
|
do: nil,
|
||||||
|
else: event.attributed_to
|
||||||
|
) ||
|
||||||
|
event.organizer_actor
|
||||||
|
end
|
||||||
|
|
||||||
# Get only elements that we have in EventOptions
|
# Get only elements that we have in EventOptions
|
||||||
@spec get_options(map) :: map
|
@spec get_options(map) :: map
|
||||||
defp get_options(object) do
|
defp get_options(object) do
|
||||||
|
|
|
@ -34,10 +34,20 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
||||||
@impl Converter
|
@impl Converter
|
||||||
@spec model_to_as(Post.t()) :: map
|
@spec model_to_as(Post.t()) :: map
|
||||||
def model_to_as(
|
def model_to_as(
|
||||||
%Post{author: %Actor{url: actor_url}, attributed_to: %Actor{url: creator_url}} = post
|
%Post{
|
||||||
|
author: %Actor{url: actor_url},
|
||||||
|
attributed_to: %Actor{url: creator_url, followers_url: followers_url}
|
||||||
|
} = post
|
||||||
) do
|
) do
|
||||||
|
to =
|
||||||
|
if post.visibility == :public,
|
||||||
|
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
else: [followers_url]
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Article",
|
"type" => "Article",
|
||||||
|
"to" => to,
|
||||||
|
"cc" => [],
|
||||||
"actor" => actor_url,
|
"actor" => actor_url,
|
||||||
"id" => post.url,
|
"id" => post.url,
|
||||||
"name" => post.title,
|
"name" => post.title,
|
||||||
|
|
|
@ -43,7 +43,8 @@ defmodule Mobilizon.Discussions do
|
||||||
:replies,
|
:replies,
|
||||||
:tags,
|
:tags,
|
||||||
:mentions,
|
:mentions,
|
||||||
:discussion
|
:discussion,
|
||||||
|
:media
|
||||||
]
|
]
|
||||||
|
|
||||||
@discussion_preloads [
|
@discussion_preloads [
|
||||||
|
|
|
@ -56,7 +56,7 @@ defmodule Mobilizon.Posts.Post do
|
||||||
field(:title, :string)
|
field(:title, :string)
|
||||||
field(:url, :string)
|
field(:url, :string)
|
||||||
field(:publish_at, :utc_datetime)
|
field(:publish_at, :utc_datetime)
|
||||||
field(:visibility, PostVisibility, default_value: :public)
|
field(:visibility, PostVisibility, default: :public)
|
||||||
belongs_to(:author, Actor)
|
belongs_to(:author, Actor)
|
||||||
belongs_to(:attributed_to, Actor)
|
belongs_to(:attributed_to, Actor)
|
||||||
belongs_to(:picture, Media, on_replace: :update)
|
belongs_to(:picture, Media, on_replace: :update)
|
||||||
|
|
|
@ -52,8 +52,9 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
|
||||||
|
|
||||||
media_src = ["media-src 'self' "] ++ Config.get([:http_security, :csp_policy, :media_src])
|
media_src = ["media-src 'self' "] ++ Config.get([:http_security, :csp_policy, :media_src])
|
||||||
|
|
||||||
|
# Connect-src is available for any origin because of webfinger query to redirect to content
|
||||||
connect_src =
|
connect_src =
|
||||||
["connect-src 'self' blob: ", static_url, ?\s, websocket_url] ++
|
["connect-src 'self' * blob: ", static_url, ?\s, websocket_url] ++
|
||||||
Config.get([:http_security, :csp_policy, :connect_src])
|
Config.get([:http_security, :csp_policy, :connect_src])
|
||||||
|
|
||||||
script_src =
|
script_src =
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -1,7 +1,7 @@
|
||||||
defmodule Mobilizon.Mixfile do
|
defmodule Mobilizon.Mixfile do
|
||||||
use Mix.Project
|
use Mix.Project
|
||||||
|
|
||||||
@version "1.0.5"
|
@version "1.0.7"
|
||||||
|
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -1,5 +1,5 @@
|
||||||
%{
|
%{
|
||||||
"absinthe": {:hex, :absinthe, "1.6.0", "7cb42eebbb9cbf5077541d73c189e205ebe12caf1c78372fc5b9e706fc8ac298", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "99915841495522332b3af8ff10c9cbb51e256b28d9b19c0dfaac5f044b6bfb66"},
|
"absinthe": {:hex, :absinthe, "1.6.1", "07bd1636027595c8d00d250a5878e617c24ccb25c84a08e807d8d00cf124696c", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f0105f1de6176ca50789081f2465389cb7afa438d54bf5133aeb3549f8f629f6"},
|
||||||
"absinthe_phoenix": {:git, "https://github.com/absinthe-graphql/absinthe_phoenix.git", "67dc53db5b826ea12f37860bcce4334d4aaad028", [ref: "67dc53db5b826ea12f37860bcce4334d4aaad028"]},
|
"absinthe_phoenix": {:git, "https://github.com/absinthe-graphql/absinthe_phoenix.git", "67dc53db5b826ea12f37860bcce4334d4aaad028", [ref: "67dc53db5b826ea12f37860bcce4334d4aaad028"]},
|
||||||
"absinthe_plug": {:hex, :absinthe_plug, "1.5.4", "daff02d04be7c06d0114ef5b4361865a4dacbe8ddb325ce709b103253d4a014b", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "80360cd8ad541d87c75336f3abc59b894d474458f6a7f8e563781c01148860de"},
|
"absinthe_plug": {:hex, :absinthe_plug, "1.5.4", "daff02d04be7c06d0114ef5b4361865a4dacbe8ddb325ce709b103253d4a014b", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "80360cd8ad541d87c75336f3abc59b894d474458f6a7f8e563781c01148860de"},
|
||||||
"argon2_elixir": {:hex, :argon2_elixir, "2.4.0", "2a22ea06e979f524c53b42b598fc6ba38cdcbc977a155e33e057732cfb1fb311", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "4ea82e183cf8e7f66dab1f767fedcfe6a195e140357ef2b0423146b72e0a551d"},
|
"argon2_elixir": {:hex, :argon2_elixir, "2.4.0", "2a22ea06e979f524c53b42b598fc6ba38cdcbc977a155e33e057732cfb1fb311", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "4ea82e183cf8e7f66dab1f767fedcfe6a195e140357ef2b0423146b72e0a551d"},
|
||||||
|
|
Loading…
Reference in New Issue