alle änderungen für fomobremen.info

This commit is contained in:
unkonkret 2023-03-08 19:03:44 +00:00
parent 98229e8165
commit 711843a451
75 changed files with 9085 additions and 393 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,015 B

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><path style="opacity:1;fill:#fea72b;fill-opacity:1;stroke:none;stroke-opacity:1" d="M-5.801-6.164h72.69v72.871h-72.69z"/><g data-name="Calque 2"><g data-name="header"><path d="M26.58 27.06q0 8-4.26 12.3a12.21 12.21 0 0 1-9 3.42 12.21 12.21 0 0 1-9-3.42Q0 35.1 0 27.06q0-8.04 4.26-12.3a12.21 12.21 0 0 1 9-3.42 12.21 12.21 0 0 1 9 3.42q4.32 4.24 4.32 12.3zM13.29 17q-5.67 0-5.67 10.06t5.67 10.08q5.71 0 5.71-10.08T13.29 17z" style="fill:#3a384c;fill-opacity:1" transform="translate(14.627 5.256) scale(1.15671)"/><path d="M9 6.78a7.37 7.37 0 0 1-.6-3 7.37 7.37 0 0 1 .6-3A8.09 8.09 0 0 1 12.83 0a7.05 7.05 0 0 1 3.69.84 7.37 7.37 0 0 1 .6 3 7.37 7.37 0 0 1-.6 3 7.46 7.46 0 0 1-3.87.84A6.49 6.49 0 0 1 9 6.78z" style="fill:#fff" transform="translate(14.627 5.256) scale(1.15671)"/></g></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><path style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#3c3c3c;stroke-opacity:0" d="m -5.801,-6.164 h 72.69 v 72.871 h -72.69 z"/><g data-name="icon"><g data-name="header"><path d="M 22.046275,57.767824 H 21.898116 L 15.171689,53.856422 Q 14.964267,53.737894 14.964267,53.50084 L 14.845739,16.223989 q 0.02963,-0.207422 0.207423,-0.32595 22.964673,-13.2750625 23.083201,-13.2750625 0.118527,0 6.874586,3.9410343 l 0.05926,0.059264 0.05926,0.029632 v 0.029632 h 0.02963 l 0.05926,0.1185274 0.02963,4.5040395 q -0.02963,0.237054 -0.207423,0.355582 L 26.165101,22.535571 v 3.407661 q 11.911998,-6.874586 12.030525,-6.874586 0.0889,0 3.496557,1.985333 3.407661,1.955701 3.407661,1.985333 h 0.02963 v 0.02963 q 0.02963,0.02963 0.02963,0.05926 0.02963,0 0.02963,0 0.02963,0 0.02963,0.02963 l 0.02963,0.148159 h 0.02963 v 4.444776 q 0,0.207423 -0.207423,0.32595 l -18.845848,10.904516 0.02963,16.208614 q 0,0.207423 -0.207423,0.32595 -3.852138,2.25202 -4.000298,2.25202 z M 26.194733,33.677141 44.06273,23.365262 38.195626,19.927969 26.194733,26.861819 Z m -4.563303,22.994305 -0.0889,-36.298999 -5.867103,-3.437294 0.0889,36.328632 z M 21.927748,19.661282 44.003466,6.9195928 38.136363,3.5119316 16.060645,16.253621 Z" style="stroke-opacity:1;fill:#ffffff;stroke:#ffffff;stroke-width:0.782;stroke-dasharray:none"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 857 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><path style="opacity:1;fill:#fea72b;fill-opacity:1;stroke:none;stroke-opacity:1" d="M-5.801-6.164h72.69v72.871h-72.69z"/><g data-name="Calque 2"><g data-name="header"><path d="M26.58 27.06q0 8-4.26 12.3a12.21 12.21 0 0 1-9 3.42 12.21 12.21 0 0 1-9-3.42Q0 35.1 0 27.06q0-8.04 4.26-12.3a12.21 12.21 0 0 1 9-3.42 12.21 12.21 0 0 1 9 3.42q4.32 4.24 4.32 12.3zM13.29 17q-5.67 0-5.67 10.06t5.67 10.08q5.71 0 5.71-10.08T13.29 17z" style="fill:#3a384c;fill-opacity:1" transform="translate(14.627 5.256) scale(1.15671)"/><path d="M9 6.78a7.37 7.37 0 0 1-.6-3 7.37 7.37 0 0 1 .6-3A8.09 8.09 0 0 1 12.83 0a7.05 7.05 0 0 1 3.69.84 7.37 7.37 0 0 1 .6 3 7.37 7.37 0 0 1-.6 3 7.46 7.46 0 0 1-3.87.84A6.49 6.49 0 0 1 9 6.78z" style="fill:#fff" transform="translate(14.627 5.256) scale(1.15671)"/></g></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><path style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#3c3c3c;stroke-opacity:0" d="m -5.801,-6.164 h 72.69 v 72.871 h -72.69 z"/><g data-name="icon"><g data-name="header"><path d="M 22.046275,57.767824 H 21.898116 L 15.171689,53.856422 Q 14.964267,53.737894 14.964267,53.50084 L 14.845739,16.223989 q 0.02963,-0.207422 0.207423,-0.32595 22.964673,-13.2750625 23.083201,-13.2750625 0.118527,0 6.874586,3.9410343 l 0.05926,0.059264 0.05926,0.029632 v 0.029632 h 0.02963 l 0.05926,0.1185274 0.02963,4.5040395 q -0.02963,0.237054 -0.207423,0.355582 L 26.165101,22.535571 v 3.407661 q 11.911998,-6.874586 12.030525,-6.874586 0.0889,0 3.496557,1.985333 3.407661,1.955701 3.407661,1.985333 h 0.02963 v 0.02963 q 0.02963,0.02963 0.02963,0.05926 0.02963,0 0.02963,0 0.02963,0 0.02963,0.02963 l 0.02963,0.148159 h 0.02963 v 4.444776 q 0,0.207423 -0.207423,0.32595 l -18.845848,10.904516 0.02963,16.208614 q 0,0.207423 -0.207423,0.32595 -3.852138,2.25202 -4.000298,2.25202 z M 26.194733,33.677141 44.06273,23.365262 38.195626,19.927969 26.194733,26.861819 Z m -4.563303,22.994305 -0.0889,-36.298999 -5.867103,-3.437294 0.0889,36.328632 z M 21.927748,19.661282 44.003466,6.9195928 38.136363,3.5119316 16.060645,16.253621 Z" style="stroke-opacity:1;fill:#ffffff;stroke:#ffffff;stroke-width:0.782;stroke-dasharray:none"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 857 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -120,7 +120,7 @@ a.list-item {
h2 { h2 {
display: inline; display: inline;
background: $secondary; background: #ffffff;
padding: 2px 7.5px; padding: 2px 7.5px;
text-transform: uppercase; text-transform: uppercase;
font-size: 1.25rem; font-size: 1.25rem;

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="truncate" class="ellipsis"
:title=" :title="
isDescriptionDifferentFromLocality isDescriptionDifferentFromLocality
? `${physicalAddress.description}, ${physicalAddress.locality}` ? `${physicalAddress.description}, ${physicalAddress.locality}`
@ -8,8 +8,10 @@
" "
> >
<b-icon icon="map-marker" /> <b-icon icon="map-marker" />
<span v-if="physicalAddress.locality"> <span v-if="isDescriptionDifferentFromLocality">
{{ physicalAddress.locality }} <b>{{ physicalAddress.locality }}</b
>,
{{ physicalAddress.description }}
</span> </span>
<span v-else> <span v-else>
{{ physicalAddress.description }} {{ physicalAddress.description }}
@ -34,3 +36,12 @@ export default class InlineAddress extends Vue {
} }
} }
</script> </script>
<style lang="scss" scoped>
.ellipsis {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
background-color: #9e9ef2;
}
</style>

View file

@ -19,6 +19,7 @@
> >
<div class="datetime-container-header" /> <div class="datetime-container-header" />
<div class="datetime-container-content"> <div class="datetime-container-content">
<time :datetime="dateObj.toISOString()" class="wkday">{{ wkday }}</time>
<time :datetime="dateObj.toISOString()" class="day">{{ day }}</time> <time :datetime="dateObj.toISOString()" class="day">{{ day }}</time>
<time :datetime="dateObj.toISOString()" class="month">{{ month }}</time> <time :datetime="dateObj.toISOString()" class="month">{{ month }}</time>
</div> </div>
@ -39,6 +40,10 @@ export default class DateCalendarIcon extends Vue {
return new Date(this.$props.date); return new Date(this.$props.date);
} }
get wkday(): string {
return this.dateObj.toLocaleString(undefined, { weekday: "short" });
}
get month(): string { get month(): string {
return this.dateObj.toLocaleString(undefined, { month: "short" }); return this.dateObj.toLocaleString(undefined, { month: "short" });
} }
@ -62,17 +67,17 @@ div.datetime-container {
overflow-y: hidden; overflow-y: hidden;
overflow-x: hidden; overflow-x: hidden;
align-items: stretch; align-items: stretch;
width: calc(40px * var(--small)); width: calc(55px * var(--small));
box-shadow: 0 0 12px rgba(0, 0, 0, 0.2); box-shadow: 0 0 12px rgba(0, 0, 0, 0.2);
height: calc(40px * var(--small)); height: calc(55px * var(--small));
background: #fff; background: #fff;
.datetime-container-header { .datetime-container-header {
height: calc(10px * var(--small)); height: calc(12px * var(--small));
background: #f3425f; background: #f3425f;
} }
.datetime-container-content { .datetime-container-content {
height: calc(30px * var(--small)); height: calc(43px * var(--small));
} }
time { time {
@ -80,6 +85,13 @@ div.datetime-container {
font-weight: 600; font-weight: 600;
color: $violet-3; color: $violet-3;
&.wkday {
padding: 2px 0;
font-size: 12px;
line-height: 12px;
text-transform: uppercase;
}
&.month { &.month {
padding: 2px 0; padding: 2px 0;
font-size: 12px; font-size: 12px;

View file

@ -0,0 +1,96 @@
<docs>
### Example
```vue
<DateCalendarIcon date="2019-10-05T18:41:11.720Z" />
```
```vue
<DateCalendarIcon
:date="new Date()"
/>
```
</docs>
<template>
<div
class="datetime-container"
:class="{ small }"
:style="`--small: ${smallStyle}`"
>
<div class="datetime-container-header" />
<div class="datetime-container-content">
<time :datetime="dateObj.toISOString()" class="day">{{ day }}</time>
<time :datetime="dateObj.toISOString()" class="month">{{ month }}</time>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class DateCalendarIcon extends Vue {
/**
* `date` can be a string or an actual date object.
*/
@Prop({ required: true }) date!: string;
@Prop({ required: false, default: false }) small!: boolean;
get dateObj(): Date {
return new Date(this.$props.date);
}
get month(): string {
return this.dateObj.toLocaleString(undefined, { month: "short" });
}
get day(): string {
return this.dateObj.toLocaleString(undefined, { day: "numeric" });
}
get smallStyle(): string {
return this.small ? "1.2" : "2";
}
}
</script>
<style lang="scss" scoped>
div.datetime-container {
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
overflow-y: hidden;
overflow-x: hidden;
align-items: stretch;
width: calc(40px * var(--small));
box-shadow: 0 0 12px rgba(0, 0, 0, 0.2);
height: calc(40px * var(--small));
background: #fff;
.datetime-container-header {
height: calc(10px * var(--small));
background: #f3425f;
}
.datetime-container-content {
height: calc(30px * var(--small));
}
time {
display: block;
font-weight: 600;
color: $violet-3;
&.month {
padding: 2px 0;
font-size: 12px;
line-height: 12px;
text-transform: uppercase;
}
&.day {
font-size: calc(1rem * var(--small));
line-height: calc(1rem * var(--small));
}
}
}
</style>

View file

@ -0,0 +1,108 @@
<docs>
### Example
```vue
<DateCalendarIcon date="2019-10-05T18:41:11.720Z" />
```
```vue
<DateCalendarIcon
:date="new Date()"
/>
```
</docs>
<template>
<div
class="datetime-container"
:class="{ small }"
:style="`--small: ${smallStyle}`"
>
<div class="datetime-container-header" />
<div class="datetime-container-content">
<time :datetime="dateObj.toISOString()" class="wkday">{{ wkday }}</time>
<time :datetime="dateObj.toISOString()" class="day">{{ day }}</time>
<time :datetime="dateObj.toISOString()" class="month">{{ month }}</time>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class DateCalendarIcon extends Vue {
/**
* `date` can be a string or an actual date object.
*/
@Prop({ required: true }) date!: string;
@Prop({ required: false, default: false }) small!: boolean;
get dateObj(): Date {
return new Date(this.$props.date);
}
get wkday(): string {
return this.dateObj.toLocaleString(undefined, { weekday: "short" });
}
get month(): string {
return this.dateObj.toLocaleString(undefined, { month: "short" });
}
get day(): string {
return this.dateObj.toLocaleString(undefined, { day: "numeric" });
}
get smallStyle(): string {
return this.small ? "1.2" : "2";
}
}
</script>
<style lang="scss" scoped>
div.datetime-container {
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
overflow-y: hidden;
overflow-x: hidden;
align-items: stretch;
width: calc(55px * var(--small));
box-shadow: 0 0 12px rgba(0, 0, 0, 0.2);
height: calc(55px * var(--small));
background: #fff;
.datetime-container-header {
height: calc(12px * var(--small));
background: #f3425f;
}
.datetime-container-content {
height: calc(43px * var(--small));
}
time {
display: block;
font-weight: 600;
color: $violet-3;
&.wkday {
padding: 2px 0;
font-size: 12px;
line-height: 12px;
text-transform: uppercase;
}
&.month {
padding: 2px 0;
font-size: 12px;
line-height: 12px;
text-transform: uppercase;
}
&.day {
font-size: calc(1rem * var(--small));
line-height: calc(1rem * var(--small));
}
}
}
</style>

View file

@ -67,6 +67,7 @@
<inline-address <inline-address
dir="auto" dir="auto"
v-if="event.physicalAddress" v-if="event.physicalAddress"
class="event-subtitle"
:physical-address="event.physicalAddress" :physical-address="event.physicalAddress"
/> />
<div <div
@ -148,8 +149,8 @@ export default class EventCard extends Vue {
a.card { a.card {
display: block; display: block;
background: $secondary; background: #ffffff;
color: #3c376e; color: #000000;
&:hover { &:hover {
// box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1); // box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
@ -160,7 +161,7 @@ a.card {
} }
border-radius: 5px; border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
&:after { &:after {
@ -250,7 +251,7 @@ a.card {
font-size: 18px; font-size: 18px;
line-height: 24px; line-height: 24px;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 3; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
font-weight: bold; font-weight: bold;
@ -262,10 +263,22 @@ a.card {
.event-subtitle { .event-subtitle {
font-size: 0.85rem; font-size: 0.85rem;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
} }
.organizer-name { .organizer-name {
font-size: 14px; font-size: 14px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.event-organizer {
background-color: #21bef3;
} }
} }
} }

View file

@ -0,0 +1,272 @@
<template>
<router-link
class="card"
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
>
<div class="card-image">
<figure class="image is-16by9">
<lazy-image-wrapper
:picture="event.picture"
style="height: 100%; position: absolute; top: 0; left: 0; width: 100%"
/>
<div
class="tag-container"
v-if="event.tags || event.status !== EventStatus.CONFIRMED"
>
<b-tag type="is-info" v-if="event.status === EventStatus.TENTATIVE">
{{ $t("Tentative") }}
</b-tag>
<b-tag type="is-danger" v-if="event.status === EventStatus.CANCELLED">
{{ $t("Cancelled") }}
</b-tag>
<router-link
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
v-for="tag in (event.tags || []).slice(0, 3)"
:key="tag.slug"
>
<b-tag type="is-light" dir="auto">{{ tag.title }}</b-tag>
</router-link>
</div>
</figure>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<date-calendar-icon
:small="true"
v-if="!mergedOptions.hideDate"
:date="event.beginsOn"
/>
</div>
<div class="media-content">
<h3
class="event-title"
:title="event.title"
dir="auto"
:lang="event.language"
>
{{ event.title }}
</h3>
<div class="content-end">
<div class="event-organizer" dir="auto">
<figure
class="image is-24x24"
v-if="organizer(event) && organizer(event).avatar"
>
<img
class="is-rounded"
:src="organizer(event).avatar.url"
alt=""
/>
</figure>
<b-icon v-else icon="account-circle" />
<span class="organizer-name">
{{ organizerDisplayName(event) }}
</span>
</div>
<inline-address
dir="auto"
v-if="event.physicalAddress"
:physical-address="event.physicalAddress"
/>
<div
class="event-subtitle"
dir="auto"
v-else-if="event.options && event.options.isOnline"
>
<b-icon icon="video" />
<span>{{ $t("Online") }}</span>
</div>
</div>
</div>
</div>
</div>
</router-link>
</template>
<script lang="ts">
import {
IEvent,
IEventCardOptions,
organizerDisplayName,
organizer,
} from "@/types/event.model";
import { Component, Prop, Vue } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import { Actor, Person } from "@/types/actor";
import { EventStatus, ParticipantRole } from "@/types/enums";
import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component({
components: {
DateCalendarIcon,
LazyImageWrapper,
InlineAddress,
},
})
export default class EventCard extends Vue {
@Prop({ required: true }) event!: IEvent;
@Prop({ required: false }) options!: IEventCardOptions;
ParticipantRole = ParticipantRole;
EventStatus = EventStatus;
RouteName = RouteName;
organizerDisplayName = organizerDisplayName;
organizer = organizer;
defaultOptions: IEventCardOptions = {
hideDate: false,
loggedPerson: false,
hideDetails: false,
organizerActor: null,
memberofGroup: false,
};
get mergedOptions(): IEventCardOptions {
return { ...this.defaultOptions, ...this.options };
}
get actor(): Actor {
return Object.assign(
new Person(),
this.event.organizerActor || this.mergedOptions.organizerActor
);
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
a.card {
display: block;
background: $secondary;
color: #3c376e;
&:hover {
// box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
transform: scale(1.01, 1.01);
&:after {
opacity: 1;
}
}
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
&:after {
content: "";
border-radius: 5px;
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
opacity: 0;
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
}
div.tag-container {
position: absolute;
top: 10px;
right: 0;
@include margin-right(-3px);
z-index: 10;
max-width: 40%;
a {
text-decoration: none;
}
span.tag {
margin: 5px auto;
text-overflow: ellipsis;
overflow: hidden;
display: block;
font-size: 0.9em;
line-height: 1.75em;
&:not(.is-info, .is-danger) {
background-color: #e6e4f4;
color: $violet-3;
}
&.is-info {
color: $violet-3;
}
}
}
div.card-image {
background: $secondary;
figure.image {
background-size: cover;
background-position: center;
}
}
.card-content {
height: 100%;
padding: 0.5rem;
& > .media {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
& > .media-left {
margin-top: -15px;
height: 0;
display: flex;
align-items: flex-end;
align-self: flex-start;
margin-bottom: 15px;
@include margin-left(0);
}
& > .media-content {
flex: 1;
width: 100%;
overflow-x: inherit;
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
.event-title {
font-size: 18px;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: bold;
}
.content-end {
padding-top: 8px;
}
.event-subtitle {
font-size: 0.85rem;
}
.organizer-name {
font-size: 14px;
}
}
}
</style>

View file

@ -34,7 +34,7 @@ export default class EventMetadataBlock extends Vue {
h2 { h2 {
font-size: 1.8rem; font-size: 1.8rem;
font-weight: 500; font-weight: 500;
color: $violet; color: rgb(0, 0, 0);
} }
div.eventMetadataBlock { div.eventMetadataBlock {

View file

@ -0,0 +1,62 @@
<template>
<div>
<h2>{{ title }}</h2>
<div class="eventMetadataBlock">
<!-- Custom icons -->
<span
class="icon is-medium"
v-if="icon && icon.substring(0, 7) === 'mz:icon'"
>
<img
:src="`/img/${icon.substring(8)}_monochrome.svg`"
width="32"
height="32"
/>
</span>
<b-icon v-else-if="icon" :icon="icon" size="is-medium" />
<div class="content-wrapper" :class="{ 'padding-left': icon }">
<slot></slot>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class EventMetadataBlock extends Vue {
@Prop({ required: false, type: String }) icon!: string;
@Prop({ required: true, type: String }) title!: string;
}
</script>
<style lang="scss" scoped>
h2 {
font-size: 1.8rem;
font-weight: 500;
color: $violet;
}
div.eventMetadataBlock {
display: flex;
align-items: center;
margin-bottom: 1.75rem;
.content-wrapper {
overflow: hidden;
width: 100%;
max-width: calc(100vw - 32px - 20px);
&.padding-left {
padding: 0 20px;
a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
</style>

View file

@ -1,22 +1,31 @@
<template> <template>
<div> <div>
<event-metadata-block <event-metadata-block
v-if="!event.options.isOnline"
:title="$t('Location')" :title="$t('Location')"
:icon="physicalAddress ? physicalAddress.poiInfos.poiIcon.icon : 'earth'" :icon="physicalAddress ? physicalAddress.poiInfos.poiIcon.icon : 'earth'"
> >
<div class="address-wrapper"> <div class="address-wrapper">
<span v-if="!physicalAddress">{{ $t("No address defined") }}</span> <span v-if="!physicalAddress">{{ $t("No address defined") }}</span>
<div class="address" v-if="physicalAddress"> <div class="address" v-if="physicalAddress">
<address-info :address="physicalAddress" /> <div>
<b-button <address>
type="is-text" <p
class="map-show-button" class="addressDescription"
@click="$emit('showMapModal', true)" :title="physicalAddress.poiInfos.name"
v-if="physicalAddress.geom" >
{{ physicalAddress.poiInfos.name }}
</p>
<p class="has-text-grey-dark">
{{ physicalAddress.poiInfos.alternativeName }}
</p>
</address>
</div>
<span
class="map-show-button"
@click="showMap = !showMap"
v-if="physicalAddress.geom"
>{{ $t("Show map") }}</span
> >
{{ $t("Show map") }}
</b-button>
</div> </div>
</div> </div>
</event-metadata-block> </event-metadata-block>
@ -25,8 +34,6 @@
:beginsOn="event.beginsOn" :beginsOn="event.beginsOn"
:show-start-time="event.options.showStartTime" :show-start-time="event.options.showStartTime"
:show-end-time="event.options.showEndTime" :show-end-time="event.options.showEndTime"
:timezone="event.options.timezone"
:userTimezone="userTimezone"
:endsOn="event.endsOn" :endsOn="event.endsOn"
/> />
</event-metadata-block> </event-metadata-block>
@ -34,9 +41,14 @@
class="metadata-organized-by" class="metadata-organized-by"
:title="$t('Organized by')" :title="$t('Organized by')"
> >
<popover-actor-card
:actor="event.organizerActor"
v-if="!event.attributedTo"
>
<actor-card :actor="event.organizerActor" />
</popover-actor-card>
<router-link <router-link
v-if="event.attributedTo" v-if="event.attributedTo"
class="hover:underline"
:to="{ :to="{
name: RouteName.GROUP, name: RouteName.GROUP,
params: { params: {
@ -44,21 +56,23 @@
}, },
}" }"
> >
<actor-card <popover-actor-card
:actor="event.attributedTo"
v-if=" v-if="
!event.attributedTo || !event.options.hideOrganizerWhenGroupEvent !event.attributedTo || !event.options.hideOrganizerWhenGroupEvent
" "
:actor="event.attributedTo" >
:inline="true" <actor-card :actor="event.attributedTo" />
/> </popover-actor-card>
</router-link> </router-link>
<actor-card v-else :actor="event.organizerActor" :inline="true" />
<actor-card <popover-actor-card
:inline="true"
:actor="contact" :actor="contact"
v-for="contact in event.contacts" v-for="contact in event.contacts"
:key="contact.id" :key="contact.id"
/> >
<actor-card :actor="contact" />
</popover-actor-card>
</event-metadata-block> </event-metadata-block>
<event-metadata-block <event-metadata-block
v-if="event.onlineAddress && urlToHostname(event.onlineAddress)" v-if="event.onlineAddress && urlToHostname(event.onlineAddress)"
@ -67,7 +81,6 @@
> >
<a <a
target="_blank" target="_blank"
class="hover:underline"
rel="noopener noreferrer ugc" rel="noopener noreferrer ugc"
:href="event.onlineAddress" :href="event.onlineAddress"
:title=" :title="
@ -127,12 +140,109 @@
> >
<span v-else>{{ extra.value }}</span> <span v-else>{{ extra.value }}</span>
</event-metadata-block> </event-metadata-block>
<div v-if="event.picture">
<h2>{{ $t("Headline picture") }}</h2>
<div style="position: relative" @click="showImage = true">
<img :src="event.picture.url" style="width: 100%" />
</div>
</div>
<b-modal v-if="event.picture" :active.sync="showImage" has-modal-card>
<div>
<header class="modal-card-head">{{ $t("Headline picture") }}</header>
<section
class="modal-card"
style="width: auto; -webkit-overflow-scrolling: touch; overflow: auto"
>
<img :src="event.picture.url" />
</section>
<footer class="modal-card-foot"></footer>
</div>
</b-modal>
<b-modal
class="map-modal"
v-if="physicalAddress && physicalAddress.geom"
:active.sync="showMap"
has-modal-card
full-screen
>
<div class="modal-card">
<header class="modal-card-head">
<button type="button" class="delete" @click="showMap = false" />
</header>
<div class="modal-card-body">
<section class="map">
<map-leaflet
:coords="physicalAddress.geom"
:marker="{
text: physicalAddress.fullName,
icon: physicalAddress.poiInfos.poiIcon.icon,
}"
/>
</section>
<section class="columns is-centered map-footer">
<div class="column is-half has-text-centered">
<p class="address">
<i class="mdi mdi-map-marker"></i>
{{ physicalAddress.fullName }}
</p>
<p class="getting-there">{{ $t("Getting there") }}</p>
<div
class="buttons"
v-if="
addressLinkToRouteByCar ||
addressLinkToRouteByBike ||
addressLinkToRouteByFeet
"
>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByFeet"
:href="addressLinkToRouteByFeet"
>
<i class="mdi mdi-walk"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByBike"
:href="addressLinkToRouteByBike"
>
<i class="mdi mdi-bike"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByTransit"
:href="addressLinkToRouteByTransit"
>
<i class="mdi mdi-bus"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByCar"
:href="addressLinkToRouteByCar"
>
<i class="mdi mdi-car"></i>
</a>
</div>
</div>
</section>
</div>
</div>
</b-modal>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Address } from "@/types/address.model"; import { Address } from "@/types/address.model";
import { IConfig } from "@/types/config.model"; import { IConfig } from "@/types/config.model";
import { EventMetadataKeyType, EventMetadataType } from "@/types/enums"; import {
EventMetadataKeyType,
EventMetadataType,
RoutingTransportationType,
RoutingType,
} from "@/types/enums";
import { IEvent } from "@/types/event.model"; import { IEvent } from "@/types/event.model";
import { PropType } from "vue"; import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
@ -142,13 +252,11 @@ import EventMetadataBlock from "./EventMetadataBlock.vue";
import EventFullDate from "./EventFullDate.vue"; import EventFullDate from "./EventFullDate.vue";
import PopoverActorCard from "../Account/PopoverActorCard.vue"; import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActorCard from "../../components/Account/ActorCard.vue"; import ActorCard from "../../components/Account/ActorCard.vue";
import AddressInfo from "../../components/Address/AddressInfo.vue";
import { import {
IEventMetadata, IEventMetadata,
IEventMetadataDescription, IEventMetadataDescription,
} from "@/types/event-metadata"; } from "@/types/event-metadata";
import { eventMetaDataList } from "../../services/EventMetadata"; import { eventMetaDataList } from "../../services/EventMetadata";
import { IUser } from "@/types/current-user.model";
@Component({ @Component({
components: { components: {
@ -156,14 +264,17 @@ import { IUser } from "@/types/current-user.model";
EventFullDate, EventFullDate,
PopoverActorCard, PopoverActorCard,
ActorCard, ActorCard,
AddressInfo, "map-leaflet": () =>
import(/* webpackChunkName: "map" */ "../../components/Map.vue"),
}, },
}) })
export default class EventMetadataSidebar extends Vue { export default class EventMetadataSidebar extends Vue {
@Prop({ type: Object as PropType<IEvent>, required: true }) event!: IEvent; @Prop({ type: Object as PropType<IEvent>, required: true }) event!: IEvent;
@Prop({ type: Object as PropType<IConfig>, required: true }) config!: IConfig; @Prop({ type: Object as PropType<IConfig>, required: true }) config!: IConfig;
@Prop({ required: true }) user!: IUser | undefined;
@Prop({ required: false, default: false }) showMap!: boolean; showImage = false;
showMap = false;
RouteName = RouteName; RouteName = RouteName;
@ -174,6 +285,21 @@ export default class EventMetadataSidebar extends Vue {
EventMetadataType = EventMetadataType; EventMetadataType = EventMetadataType;
EventMetadataKeyType = EventMetadataKeyType; EventMetadataKeyType = EventMetadataKeyType;
RoutingParamType = {
[RoutingType.OPENSTREETMAP]: {
[RoutingTransportationType.FOOT]: "engine=fossgis_osrm_foot",
[RoutingTransportationType.BIKE]: "engine=fossgis_osrm_bike",
[RoutingTransportationType.TRANSIT]: null,
[RoutingTransportationType.CAR]: "engine=fossgis_osrm_car",
},
[RoutingType.GOOGLE_MAPS]: {
[RoutingTransportationType.FOOT]: "dirflg=w",
[RoutingTransportationType.BIKE]: "dirflg=b",
[RoutingTransportationType.TRANSIT]: "dirflg=r",
[RoutingTransportationType.CAR]: "driving",
},
};
get physicalAddress(): Address | null { get physicalAddress(): Address | null {
if (!this.event.physicalAddress) return null; if (!this.event.physicalAddress) return null;
@ -190,6 +316,50 @@ export default class EventMetadataSidebar extends Vue {
}); });
} }
makeNavigationPath(
transportationType: RoutingTransportationType
): string | undefined {
const geometry = this.physicalAddress?.geom;
if (geometry) {
const routingType = this.config.maps.routing.type;
/**
* build urls to routing map
*/
if (!this.RoutingParamType[routingType][transportationType]) {
return;
}
const urlGeometry = geometry.split(";").reverse().join(",");
switch (routingType) {
case RoutingType.GOOGLE_MAPS:
return `https://maps.google.com/?saddr=Current+Location&daddr=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}`;
case RoutingType.OPENSTREETMAP:
default: {
const bboxX = geometry.split(";").reverse()[0];
const bboxY = geometry.split(";").reverse()[1];
return `https://www.openstreetmap.org/directions?from=&to=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}#map=14/${bboxX}/${bboxY}`;
}
}
}
}
get addressLinkToRouteByCar(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.CAR);
}
get addressLinkToRouteByBike(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.BIKE);
}
get addressLinkToRouteByFeet(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.FOOT);
}
get addressLinkToRouteByTransit(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.TRANSIT);
}
urlToHostname(url: string): string | null { urlToHostname(url: string): string | null {
try { try {
return new URL(url).hostname; return new URL(url).hostname;
@ -222,10 +392,6 @@ export default class EventMetadataSidebar extends Vue {
} }
} }
} }
get userTimezone(): string | undefined {
return this.user?.settings?.timezone;
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -244,6 +410,23 @@ export default class EventMetadataSidebar extends Vue {
} }
} }
h2 {
font-size: 1.8rem;
font-weight: 500;
color: $violet-3;
}
.banner-container {
width: 60vw;
height: 60vh;
}
.object-cover {
width: 100%;
height: 100%;
object-fit: cover;
}
div.address-wrapper { div.address-wrapper {
display: flex; display: flex;
flex: 1; flex: 1;
@ -255,6 +438,50 @@ div.address-wrapper {
.map-show-button { .map-show-button {
cursor: pointer; cursor: pointer;
} }
address {
font-style: normal;
flex-wrap: wrap;
display: flex;
justify-content: flex-start;
span.addressDescription {
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 0 auto;
min-width: 100%;
max-width: 4rem;
overflow: hidden;
}
:not(.addressDescription) {
flex: 1;
min-width: 100%;
}
}
}
}
.map-modal {
.modal-card-head {
justify-content: flex-end;
button.delete {
margin-right: 1rem;
}
}
section.map {
height: calc(100% - 8rem);
width: calc(100% - 20px);
}
section.map-footer {
p.address {
margin: 1rem auto;
}
div.buttons {
justify-content: center;
}
} }
} }
</style> </style>

View file

@ -523,7 +523,7 @@ article.box {
line-height: 24px; line-height: 24px;
margin: auto 0; margin: auto 0;
font-weight: bold; font-weight: bold;
color: $title-color; color: #ffffff;
} }
} }
} }
@ -531,7 +531,7 @@ article.box {
} }
.identity-header { .identity-header {
background: $yellow-2; background: #21bef3;
display: flex; display: flex;
padding: 5px; padding: 5px;

View file

@ -0,0 +1,549 @@
<template>
<article class="box mb-5 mt-4">
<div class="identity-header" dir="auto">
<figure class="image is-24x24" v-if="participation.actor.avatar">
<img
class="is-rounded"
:src="participation.actor.avatar.url"
alt=""
height="24"
width="24"
/>
</figure>
<b-icon v-else icon="account-circle" />
{{ displayNameAndUsername(participation.actor) }}
</div>
<div class="list-card">
<div class="content-and-actions">
<div class="event-preview mr-0 ml-0">
<div>
<div class="date-component">
<date-calendar-icon
:date="participation.event.beginsOn"
:small="true"
/>
</div>
<router-link
:to="{
name: RouteName.EVENT,
params: { uuid: participation.event.uuid },
}"
>
<lazy-image-wrapper
:rounded="true"
:picture="participation.event.picture"
style="
height: 100%;
position: absolute;
top: 0;
left: 0;
width: 100%;
"
/>
</router-link>
</div>
</div>
<div class="list-card-content">
<div class="title-wrapper" dir="auto">
<b-tag
type="is-info"
class="mr-1 mb-1"
size="is-medium"
v-if="participation.event.status === EventStatus.TENTATIVE"
>
{{ $t("Tentative") }}
</b-tag>
<b-tag
type="is-danger"
class="mr-1 mb-1"
size="is-medium"
v-if="participation.event.status === EventStatus.CANCELLED"
>
{{ $t("Cancelled") }}
</b-tag>
<router-link
:to="{
name: RouteName.EVENT,
params: { uuid: participation.event.uuid },
}"
>
<h3 class="title" :lang="participation.event.language">
{{ participation.event.title }}
</h3>
</router-link>
</div>
<inline-address
v-if="participation.event.physicalAddress"
class="event-subtitle"
:physical-address="participation.event.physicalAddress"
/>
<div
class="event-subtitle"
v-else-if="
participation.event.options &&
participation.event.options.isOnline
"
>
<b-icon icon="video" />
<span>{{ $t("Online") }}</span>
</div>
<div class="event-subtitle event-organizer">
<figure
class="image is-24x24"
v-if="
organizer(participation.event) &&
organizer(participation.event).avatar
"
>
<img
class="is-rounded"
:src="organizer(participation.event).avatar.url"
alt=""
/>
</figure>
<b-icon v-else icon="account-circle" />
<span class="organizer-name">
{{ organizerDisplayName(participation.event) }}
</span>
</div>
<div class="event-subtitle event-participants">
<b-icon
:class="{ 'has-text-danger': lastSeatsLeft }"
icon="account-group"
/>
<span
class="participant-stats"
v-if="participation.role !== ParticipantRole.NOT_APPROVED"
>
<!-- Less than 10 seats left -->
<span class="has-text-danger" v-if="lastSeatsLeft">
{{
$t("{number} seats left", {
number: seatsLeft,
})
}}
</span>
<span
v-else-if="
participation.event.options.maximumAttendeeCapacity !== 0
"
>
{{
$tc(
"{available}/{capacity} available places",
participation.event.options.maximumAttendeeCapacity -
participation.event.participantStats.participant,
{
available:
participation.event.options.maximumAttendeeCapacity -
participation.event.participantStats.participant,
capacity:
participation.event.options.maximumAttendeeCapacity,
}
)
}}
</span>
<span v-else>
{{
$tc(
"{count} participants",
participation.event.participantStats.participant,
{
count: participation.event.participantStats.participant,
}
)
}}
</span>
<b-button
v-if="participation.event.participantStats.notApproved > 0"
type="is-text"
@click="
gotToWithCheck(participation, {
name: RouteName.PARTICIPATIONS,
query: { role: ParticipantRole.NOT_APPROVED },
params: { eventId: participation.event.uuid },
})
"
>
{{
$tc(
"{count} requests waiting",
participation.event.participantStats.notApproved,
{
count: participation.event.participantStats.notApproved,
}
)
}}
</b-button>
</span>
</div>
</div>
<div class="actions">
<b-dropdown aria-role="list" position="is-bottom-left">
<b-button slot="trigger" role="button" icon-right="dots-horizontal">
{{ $t("Actions") }}
</b-button>
<b-dropdown-item
v-if="
![
ParticipantRole.PARTICIPANT,
ParticipantRole.NOT_APPROVED,
].includes(participation.role)
"
aria-role="listitem"
@click="
gotToWithCheck(participation, {
name: RouteName.EDIT_EVENT,
params: { eventId: participation.event.uuid },
})
"
>
<b-icon icon="pencil" />
{{ $t("Edit") }}
</b-dropdown-item>
<b-dropdown-item
v-if="participation.role === ParticipantRole.CREATOR"
aria-role="listitem"
@click="
gotToWithCheck(participation, {
name: RouteName.DUPLICATE_EVENT,
params: { eventId: participation.event.uuid },
})
"
>
<b-icon icon="content-duplicate" />
{{ $t("Duplicate") }}
</b-dropdown-item>
<b-dropdown-item
v-if="
![
ParticipantRole.PARTICIPANT,
ParticipantRole.NOT_APPROVED,
].includes(participation.role)
"
aria-role="listitem"
@click="openDeleteEventModalWrapper"
>
<b-icon icon="delete" />
{{ $t("Delete") }}
</b-dropdown-item>
<b-dropdown-item
v-if="
![
ParticipantRole.PARTICIPANT,
ParticipantRole.NOT_APPROVED,
].includes(participation.role)
"
aria-role="listitem"
@click="
gotToWithCheck(participation, {
name: RouteName.PARTICIPATIONS,
params: { eventId: participation.event.uuid },
})
"
>
<b-icon icon="account-multiple-plus" />
{{ $t("Manage participations") }}
</b-dropdown-item>
<b-dropdown-item aria-role="listitem" has-link>
<router-link
:to="{
name: RouteName.EVENT,
params: { uuid: participation.event.uuid },
}"
>
<b-icon icon="view-compact" />
{{ $t("View event page") }}
</router-link>
</b-dropdown-item>
</b-dropdown>
</div>
</div>
</div>
</article>
</template>
<script lang="ts">
import { Component, Prop } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { mixins } from "vue-class-component";
import { RawLocation, Route } from "vue-router";
import { EventStatus, EventVisibility, ParticipantRole } from "@/types/enums";
import { IParticipant } from "../../types/participant.model";
import {
IEventCardOptions,
organizer,
organizerDisplayName,
} from "../../types/event.model";
import { displayNameAndUsername, IActor, IPerson } from "../../types/actor";
import ActorMixin from "../../mixins/actor";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import EventMixin from "../../mixins/event";
import RouteName from "../../router/name";
import { changeIdentity } from "../../utils/auth";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import InlineAddress from "@/components/Address/InlineAddress.vue";
import { PropType } from "vue";
const defaultOptions: IEventCardOptions = {
hideDate: true,
loggedPerson: false,
hideDetails: false,
organizerActor: null,
memberofGroup: false,
};
@Component({
components: {
DateCalendarIcon,
PopoverActorCard,
LazyImageWrapper,
InlineAddress,
},
apollo: {
currentActor: {
query: CURRENT_ACTOR_CLIENT,
},
},
})
export default class EventParticipationCard extends mixins(
ActorMixin,
EventMixin
) {
/**
* The participation associated
*/
@Prop({ required: true, type: Object as PropType<IParticipant> })
participation!: IParticipant;
/**
* Options are merged with default options
*/
@Prop({ required: false, default: () => defaultOptions })
options!: IEventCardOptions;
currentActor!: IPerson;
ParticipantRole = ParticipantRole;
EventVisibility = EventVisibility;
displayNameAndUsername = displayNameAndUsername;
organizerDisplayName = organizerDisplayName;
organizer = organizer;
RouteName = RouteName;
EventStatus = EventStatus;
get mergedOptions(): IEventCardOptions {
return { ...defaultOptions, ...this.options };
}
/**
* Delete the event
*/
async openDeleteEventModalWrapper(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await this.openDeleteEventModal(this.participation.event);
}
async gotToWithCheck(
participation: IParticipant,
route: RawLocation
): Promise<Route> {
if (
participation.actor.id !== this.currentActor.id &&
participation.event.organizerActor
) {
const organizerActor = participation.event.organizerActor as IPerson;
await changeIdentity(this.$apollo.provider.defaultClient, organizerActor);
this.$buefy.notification.open({
message: this.$t(
"Current identity has been changed to {identityName} in order to manage this event.",
{
identityName: organizerActor.preferredUsername,
}
) as string,
type: "is-info",
position: "is-bottom-right",
duration: 5000,
});
}
return this.$router.push(route);
}
get organizerActor(): IActor | undefined {
if (
this.participation.event.attributedTo &&
this.participation.event.attributedTo.id
) {
return this.participation.event.attributedTo;
}
return this.participation.event.organizerActor;
}
get seatsLeft(): number | null {
if (this.participation.event.options.maximumAttendeeCapacity > 0) {
return (
this.participation.event.options.maximumAttendeeCapacity -
this.participation.event.participantStats.participant
);
}
return null;
}
get lastSeatsLeft(): boolean {
if (this.seatsLeft) {
return this.seatsLeft < 10;
}
return false;
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
@import "~bulma/sass/utilities/mixins.sass";
article.box {
div.tag-container {
position: absolute;
top: 10px;
right: 0;
@include margin-left(-5px);
z-index: 10;
max-width: 40%;
span.tag {
margin: 5px auto;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
/*word-break: break-all;*/
text-overflow: ellipsis;
overflow: hidden;
display: block;
/*text-align: right;*/
font-size: 1em;
/*padding: 0 1px;*/
line-height: 1.75em;
}
}
.list-card {
display: flex;
padding: 0 6px 0 0;
position: relative;
flex-direction: column;
.content-and-actions {
display: grid;
grid-gap: 5px 10px;
grid-template-areas: "preview" "body" "actions";
@include tablet {
grid-template-columns: 1fr 3fr;
grid-template-areas: "preview body" "actions actions";
}
@include desktop {
grid-template-columns: 1fr 3fr 1fr;
grid-template-areas: "preview body actions";
}
.event-preview {
grid-area: preview;
& > div {
height: 128px;
width: 100%;
position: relative;
div.date-component {
display: flex;
position: absolute;
bottom: 5px;
left: 5px;
z-index: 1;
}
img {
width: 100%;
object-position: center;
object-fit: cover;
height: 100%;
}
}
}
.actions {
padding: 7px;
cursor: pointer;
align-self: center;
justify-self: center;
grid-area: actions;
}
div.list-card-content {
flex: 1;
padding: 5px;
grid-area: body;
.participant-stats {
display: flex;
align-items: center;
padding: 0 5px;
}
div.title-wrapper {
display: flex;
align-items: center;
padding-top: 5px;
a {
text-decoration: none;
padding-bottom: 5px;
}
.title {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: 18px;
line-height: 24px;
margin: auto 0;
font-weight: bold;
color: $title-color;
}
}
}
}
}
.identity-header {
background: $yellow-2;
display: flex;
padding: 5px;
figure,
span.icon {
@include padding-right(3px);
}
}
& > .columns {
padding: 1.25rem;
}
padding: 0;
}
</style>

View file

@ -1,22 +1,5 @@
<template> <template>
<footer class="footer" ref="footer"> <footer class="footer" ref="footer">
<picture>
<source
:srcset="`/img/pics/footer_${random}-1024w.webp 1x, /img/pics/footer_${random}-1920w.webp 2x`"
type="image/webp"
/>
<source
:srcset="`/img/pics/footer_${random}-1024w.jpg 1x, /img/pics/footer_${random}-1920w.jpg 2x`"
type="image/jpeg"
/>
<img
:src="`/img/pics/footer_${random}-1024w.jpg`"
alt=""
width="5234"
height="2189"
loading="lazy"
/>
</picture>
<ul> <ul>
<li> <li>
<b-select <b-select
@ -61,6 +44,7 @@
<div class="content has-text-centered"> <div class="content has-text-centered">
<i18n <i18n
tag="span" tag="span"
color="#fff"
path="Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}." path="Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}."
> >
<a rel="external" slot="mobilizon" href="https://joinmobilizon.org">{{ <a rel="external" slot="mobilizon" href="https://joinmobilizon.org">{{
@ -71,6 +55,7 @@
rel="external" rel="external"
href="https://joinmobilizon.org/hall-of-fame" href="https://joinmobilizon.org/hall-of-fame"
slot="contributors" slot="contributors"
color="#fff"
>{{ $t("more than 1360 contributors") }}</a >{{ $t("more than 1360 contributors") }}</a
> >
</i18n> </i18n>
@ -160,7 +145,7 @@ footer.footer {
a { a {
color: $white; color: $white;
text-decoration: underline; text-decoration: underline;
text-decoration-color: $secondary; text-decoration-color: #fff;
&:focus { &:focus {
background-color: #000; background-color: #000;

View file

@ -0,0 +1,181 @@
<template>
<footer class="footer" ref="footer">
<picture>
<source
:srcset="`/img/pics/footer_${random}-1024w.webp 1x, /img/pics/footer_${random}-1920w.webp 2x`"
type="image/webp"
/>
<source
:srcset="`/img/pics/footer_${random}-1024w.jpg 1x, /img/pics/footer_${random}-1920w.jpg 2x`"
type="image/jpeg"
/>
<img
:src="`/img/pics/footer_${random}-1024w.jpg`"
alt=""
width="5234"
height="2189"
loading="lazy"
/>
</picture>
<ul>
<li>
<b-select
:aria-label="$t('Language')"
v-if="$i18n"
v-model="locale"
:placeholder="$t('Select a language')"
>
<option
v-for="(language, lang) in langs"
:value="lang"
:key="lang"
:selected="isLangSelected(lang)"
>
{{ language }}
</option>
</b-select>
</li>
<li>
<router-link :to="{ name: RouteName.ABOUT }">{{
$t("About")
}}</router-link>
</li>
<li>
<router-link :to="{ name: RouteName.TERMS }">{{
$t("Terms")
}}</router-link>
</li>
<li>
<a
rel="external"
hreflang="en"
href="https://framagit.org/framasoft/mobilizon/blob/main/LICENSE"
>
{{ $t("License") }}
</a>
</li>
<li>
<a href="#navbar">{{ $t("Back to top") }}</a>
</li>
</ul>
<div class="content has-text-centered">
<i18n
tag="span"
path="Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}."
>
<a rel="external" slot="mobilizon" href="https://joinmobilizon.org">{{
$t("Mobilizon")
}}</a>
<span slot="date">{{ new Date().getFullYear() }}</span>
<a
rel="external"
href="https://joinmobilizon.org/hall-of-fame"
slot="contributors"
>{{ $t("more than 1360 contributors") }}</a
>
</i18n>
</div>
</footer>
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { saveLocaleData } from "@/utils/auth";
import { loadLanguageAsync } from "@/utils/i18n";
import RouteName from "../router/name";
import langs from "../i18n/langs.json";
@Component
export default class Footer extends Vue {
RouteName = RouteName;
locale: string | null = this.$i18n.locale;
langs: Record<string, string> = langs;
// eslint-disable-next-line class-methods-use-this
get random(): number {
return Math.floor(Math.random() * 4) + 1;
}
@Watch("locale")
// eslint-disable-next-line class-methods-use-this
async updateLocale(locale: string): Promise<void> {
if (locale) {
console.debug("Setting locale from footer");
await loadLanguageAsync(locale);
saveLocaleData(locale);
}
}
@Watch("$i18n.locale", { deep: true })
updateLocaleFromI18n(locale: string): void {
if (locale) {
this.locale = locale;
}
}
isLangSelected(lang: string): boolean {
return lang === this.locale;
}
}
</script>
<style lang="scss" scoped>
@import "~bulma/sass/utilities/mixins.sass";
footer.footer {
color: $secondary;
display: flex;
flex-direction: column;
align-items: center;
font-size: 14px;
padding: 1rem 1.5rem;
img {
flex: 1;
max-width: 40rem;
@include mobile {
max-width: 100%;
}
}
div.content {
flex: 1;
padding-top: 10px;
}
ul {
display: inline-flex;
flex-wrap: wrap;
justify-content: space-around;
li {
display: inline-flex;
margin: auto 5px;
padding: 2px 0;
a {
font-size: 1.1rem;
}
}
}
a {
color: $white;
text-decoration: underline;
text-decoration-color: $secondary;
&:focus {
background-color: #000;
color: #fff;
outline: 3px solid #000;
text-decoration: none;
}
}
::v-deep span.select {
select,
option {
background: $background-color;
color: $white;
}
}
}
</style>

View file

@ -0,0 +1,164 @@
<template>
<footer class="footer" ref="footer">
<ul>
<li>
<b-select
:aria-label="$t('Language')"
v-if="$i18n"
v-model="locale"
:placeholder="$t('Select a language')"
>
<option
v-for="(language, lang) in langs"
:value="lang"
:key="lang"
:selected="isLangSelected(lang)"
>
{{ language }}
</option>
</b-select>
</li>
<li>
<router-link :to="{ name: RouteName.ABOUT }">{{
$t("About")
}}</router-link>
</li>
<li>
<router-link :to="{ name: RouteName.TERMS }">{{
$t("Terms")
}}</router-link>
</li>
<li>
<a
rel="external"
hreflang="en"
href="https://framagit.org/framasoft/mobilizon/blob/main/LICENSE"
>
{{ $t("License") }}
</a>
</li>
<li>
<a href="#navbar">{{ $t("Back to top") }}</a>
</li>
</ul>
<div class="content has-text-centered">
<i18n
tag="span"
path="Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}."
>
<a rel="external" slot="mobilizon" href="https://joinmobilizon.org">{{
$t("Mobilizon")
}}</a>
<span slot="date">{{ new Date().getFullYear() }}</span>
<a
rel="external"
href="https://joinmobilizon.org/hall-of-fame"
slot="contributors"
>{{ $t("more than 1360 contributors") }}</a
>
</i18n>
</div>
</footer>
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { saveLocaleData } from "@/utils/auth";
import { loadLanguageAsync } from "@/utils/i18n";
import RouteName from "../router/name";
import langs from "../i18n/langs.json";
@Component
export default class Footer extends Vue {
RouteName = RouteName;
locale: string | null = this.$i18n.locale;
langs: Record<string, string> = langs;
// eslint-disable-next-line class-methods-use-this
get random(): number {
return Math.floor(Math.random() * 4) + 1;
}
@Watch("locale")
// eslint-disable-next-line class-methods-use-this
async updateLocale(locale: string): Promise<void> {
if (locale) {
console.debug("Setting locale from footer");
await loadLanguageAsync(locale);
saveLocaleData(locale);
}
}
@Watch("$i18n.locale", { deep: true })
updateLocaleFromI18n(locale: string): void {
if (locale) {
this.locale = locale;
}
}
isLangSelected(lang: string): boolean {
return lang === this.locale;
}
}
</script>
<style lang="scss" scoped>
@import "~bulma/sass/utilities/mixins.sass";
footer.footer {
color: $secondary;
display: flex;
flex-direction: column;
align-items: center;
font-size: 14px;
padding: 1rem 1.5rem;
img {
flex: 1;
max-width: 40rem;
@include mobile {
max-width: 100%;
}
}
div.content {
flex: 1;
padding-top: 10px;
}
ul {
display: inline-flex;
flex-wrap: wrap;
justify-content: space-around;
li {
display: inline-flex;
margin: auto 5px;
padding: 2px 0;
a {
font-size: 1.1rem;
}
}
}
a {
color: $white;
text-decoration: underline;
text-decoration-color: $secondary;
&:focus {
background-color: #000;
color: #fff;
outline: 3px solid #000;
text-decoration: none;
}
}
::v-deep span.select {
select,
option {
background: $background-color;
color: $white;
}
}
}
</style>

View file

@ -105,7 +105,7 @@ export default class GroupMemberCard extends Vue {
} }
.identity-header { .identity-header {
background: $yellow-2; background: #21bef3;
display: flex; display: flex;
padding: 5px; padding: 5px;

View file

@ -0,0 +1,118 @@
<template>
<div class="card">
<div class="identity-header" dir="auto">
<figure class="image is-24x24" v-if="member.actor.avatar">
<img class="is-rounded" :src="member.actor.avatar.url" alt="" />
</figure>
<b-icon v-else icon="account-circle" />
{{ displayNameAndUsername(member.actor) }}
</div>
<div class="card-content" dir="auto">
<div>
<div class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="member.parent.avatar">
<img class="is-rounded" :src="member.parent.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-large" icon="account-group" />
</div>
<div class="media-content" dir="auto">
<router-link
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(member.parent),
},
}"
>
<h2>{{ member.parent.name }}</h2>
<p class="is-6 has-text-grey-dark">
<span>{{ `@${usernameWithDomain(member.parent)}` }}</span>
<b-taglist>
<b-tag
type="is-info"
v-if="member.role === MemberRole.ADMINISTRATOR"
>{{ $t("Administrator") }}</b-tag
>
<b-tag
type="is-info"
v-else-if="member.role === MemberRole.MODERATOR"
>{{ $t("Moderator") }}</b-tag
>
</b-taglist>
</p>
</router-link>
</div>
</div>
<div class="content" v-if="member.parent.summary">
<p v-html="member.parent.summary" />
</div>
</div>
<div>
<b-dropdown aria-role="list" position="is-bottom-left">
<b-icon icon="dots-horizontal" slot="trigger" />
<b-dropdown-item aria-role="listitem" @click="$emit('leave')">
<b-icon icon="exit-to-app" />
{{ $t("Leave") }}
</b-dropdown-item>
</b-dropdown>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { displayNameAndUsername, usernameWithDomain } from "@/types/actor";
import { IMember } from "@/types/actor/member.model";
import { MemberRole } from "@/types/enums";
import RouteName from "../../router/name";
@Component
export default class GroupMemberCard extends Vue {
@Prop({ required: true }) member!: IMember;
RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
displayNameAndUsername = displayNameAndUsername;
MemberRole = MemberRole;
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.card {
.card-content {
display: flex;
align-items: center;
& > div:first-child {
flex: 1;
}
& > div:last-child {
cursor: pointer;
}
.media-content {
::v-deep .tags {
margin-bottom: 0;
}
}
}
.identity-header {
background: $yellow-2;
display: flex;
padding: 5px;
figure,
span.icon {
@include padding-right(3px);
}
}
}
</style>

View file

@ -1,27 +1,26 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
<title>Mobilizon Logo</title> <title>Fomo Logo</title>
<g data-name="header"> <g data-name="header">
<path <path
d="M0 45.82l3.18-40.8a29.88 29.88 0 015.07-.36 27.74 27.74 0 014.95.36l4.86 17.16a92.19 92.19 0 012.34 10.08h.36a92.19 92.19 0 012.34-10.08L28 5.02a29.23 29.23 0 015-.36 29.23 29.23 0 015 .36l3.18 40.8a13.61 13.61 0 01-3.63.42 23.41 23.41 0 01-3.63-.24l-1.2-19.92q-.36-5.52-.48-12.84h-.44l-7.32 26.51a25.62 25.62 0 01-4 .3 23.36 23.36 0 01-3.84-.3L9.36 13.24H9q-.3 8.94-.48 12.84L7.26 46a22.47 22.47 0 01-3.6.24A13.75 13.75 0 010 45.82zM74 31.06q0 8-4.26 12.3a12.21 12.21 0 01-9 3.42 12.21 12.21 0 01-9-3.42q-4.26-4.26-4.26-12.3t4.24-12.31a12.21 12.21 0 019-3.42 12.21 12.21 0 019 3.42Q74 23.02 74 31.06zM60.75 20.98q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM103.2 19.75q2.7 4.11 2.7 11.28T102 42.31a13.18 13.18 0 01-10 4.11 31.41 31.41 0 01-11.34-2V2.2l.4-.45h2.76A4 4 0 0187 2.83a5.38 5.38 0 01.93 3.57v11.94a12.08 12.08 0 017.56-2.7 8.71 8.71 0 017.71 4.11zm-9.72 2a7.28 7.28 0 00-5.58 2.82v16a15 15 0 004.08.54 5.25 5.25 0 004.68-2.67q1.68-2.67 1.68-7.59 0-9.03-4.86-9.1zM121 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014 1.62A6.27 6.27 0 01121 22z" d="M 62.099803,46.107167 H 61.977828 L 56.44017,42.887031 q -0.170765,-0.09758 -0.170765,-0.292739 L 56.171825,11.905419 Q 56.19622,11.734654 56.34259,11.637074 75.248692,0.70812735 75.346272,0.70812735 q 0.09758,0 5.659633,3.24453105 l 0.04879,0.04879 0.04879,0.024395 v 0.024395 h 0.02439 l 0.04879,0.09758 0.02439,3.7080355 Q 81.176669,8.0510134 81.0303,8.1485933 L 65.490704,17.101547 v 2.805422 q 9.806778,-5.659633 9.904357,-5.659633 0.07318,0 2.878607,1.634463 2.805422,1.610068 2.805422,1.634463 h 0.0244 v 0.02439 q 0.02439,0.0244 0.02439,0.04879 0.02439,0 0.02439,0 0.02439,0 0.02439,0.0244 l 0.02439,0.121975 h 0.02439 v 3.659245 q 0,0.170765 -0.170764,0.268345 l -15.515202,8.977349 0.0244,13.344049 q 0,0.170765 -0.170764,0.268344 -3.171347,1.854018 -3.293321,1.854018 z m 3.415295,-19.833111 14.710168,-8.489449 -4.830205,-2.829817 -9.879963,5.708423 z m -3.756825,18.930497 -0.07319,-29.883838 -4.830204,-2.829817 0.07319,29.908234 z M 62.002223,14.735235 80.176476,4.2453981 75.346272,1.4399765 57.172019,11.929814 Z m 0.414714,30.469318 2.488287,-1.414908 -0.04879,-13.344049 q 0.0244,-0.19516 0.170765,-0.292739 l 15.539596,-8.952955 v -2.854211 q -15.295646,8.830979 -15.368831,8.830979 -0.317135,0 -0.34153,-0.341529 l -0.02439,-9.928753 q 0,-0.170765 0.170765,-0.268345 L 80.518005,7.6606939 V 4.8308773 L 62.343752,15.320715 Z"
stroke="#ffffff"
stroke-opacity="1"
/> />
<path <path
d="M119.82.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z" d="m 98.667864,46.107167 q -0.09758,0 -0.09758,-0.02439 h -0.04879 l -5.513264,-3.220136 q -0.146369,-0.09758 -0.170764,-0.268344 l -0.07319,-30.688873 q 0.0244,-0.170765 0.170764,-0.268345 18.906104,-10.92894665 19.003684,-10.92894665 0.0732,0 2.8786,1.63446305 l 2.80543,1.634463 h 0.0244 v 0.024395 h 0.0244 v 0.024395 h 0.0244 l 0.0244,0.04879 v 0.024395 q 0.0244,0.09758 0.0244,0.09758 0.0244,0 0.0244,0.024395 l 0.0732,30.6888729 q 0,0.19516 -0.17076,0.292739 Q 98.789839,46.107167 98.667864,46.107167 Z M 98.35073,45.204553 98.25315,15.320715 93.422946,12.490898 93.520526,42.399132 Z M 98.59468,14.735235 116.76893,4.2453981 111.93873,1.4399765 93.764475,11.929814 Z M 99.009394,45.204553 117.18365,34.714716 117.11046,4.8064824 98.936209,15.320715 Z m 2.805426,-4.513069 q -0.34153,0 -0.34153,-0.317135 l -0.0732,-23.467961 q 0.0244,-0.170765 0.17077,-0.268345 12.6122,-7.3184911 12.73417,-7.3184911 0.31714,0 0.31714,0.3415296 l 0.0732,23.4923565 q -0.0244,0.14637 -0.17076,0.24395 -12.6122,7.294096 -12.70978,7.294096 z m 0.31713,-7.269701 6.39148,-3.708036 -0.0488,-16.320235 -6.39148,3.708035 z m 0,6.367087 11.56322,-6.684222 -4.83021,-2.805422 -6.73301,3.903196 z m 11.90475,-7.245306 -0.0732,-22.297003 -4.80581,2.781026 0.0488,16.68616 z"
fill="#fff" stroke="#ffffff"
stroke-opacity="1"
/> />
<path <path
d="M139.08 40.42h2a10.23 10.23 0 01.6 3.18 9.24 9.24 0 01-.18 2.1 38.47 38.47 0 01-5.64.54q-6.48 0-6.48-7v-37l.36-.42h2.88a3.94 3.94 0 013.12 1.05 5.52 5.52 0 01.9 3.57v31.31q-.02 2.67 2.44 2.67zM155.94 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014.05 1.62 6.27 6.27 0 011.43 4.39z" d="m 135.28472,46.107167 q -0.12198,0 -0.12198,-0.02439 h -0.0488 l -5.51326,-3.220136 q -0.14637,-0.09758 -0.17077,-0.268344 l -0.0732,-30.688873 q 0.0244,-0.170765 0.17076,-0.268345 18.90611,-10.92894665 19.00369,-10.92894665 0.0732,0 2.903,1.63446305 l 2.80542,1.634463 v 0.024395 h 0.0244 v 0.024395 h 0.0244 l 0.0244,0.04879 0.0488,0.1463698 0.0732,30.6888729 q 0,0.19516 -0.14637,0.292739 -3.19574,1.829623 -3.24453,1.854018 -0.0244,0 -0.0732,0 l -0.12197,-0.02439 -4.26912,-2.488287 0.0244,4.903389 q -0.0244,0.170765 -0.17076,0.268345 -3.19574,1.854018 -3.24453,1.878413 -0.0244,0 -0.0732,0 -0.0488,0 -0.0976,0 -0.0244,-0.0244 -0.0488,-0.04879 l -4.24472,-2.463892 0.0244,4.878994 q 0,0.19516 -0.17076,0.292739 -3.19574,1.854018 -3.29332,1.854018 z m -0.34153,-0.902614 -0.0976,-29.883838 -4.83021,-2.829817 0.0976,29.908234 z m 0.24395,-30.469318 18.17425,-10.4898369 -4.8302,-2.8054216 -18.17426,10.4898375 z m 7.61123,25.931854 -0.0732,-25.907459 -4.04956,2.341917 0.0488,21.223625 z m 7.83078,-4.51307 -0.0732,-25.907458 -4.04957,2.317522 0.0732,21.223624 z m -15.0273,9.050534 2.48829,-1.414908 -0.0732,-26.883257 q 0,-0.170765 0.14637,-0.29274 4.78142,-2.756632 4.879,-2.756632 0.34153,0 0.34153,0.34153 l 0.0732,26.468543 2.46389,-1.414908 v -5.269314 l -0.0732,-21.589549 q 0,-0.19516 0.17076,-0.292739 4.78142,-2.7810271 4.879,-2.7810271 0.31713,0 0.31713,0.3415296 l 0.0976,26.4929375 2.46389,-1.439303 -0.0732,-29.9082336 -18.17425,10.5142326 z"
stroke="#ffffff"
stroke-opacity="1"
/> />
<path <path
d="M154.8 2.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z" d="m 171.85278,46.107167 q -0.0976,0 -0.0976,-0.02439 h -0.0488 l -5.51326,-3.220136 q -0.14637,-0.09758 -0.17077,-0.268344 l -0.0732,-30.688873 q 0.0244,-0.170765 0.17076,-0.268345 18.90611,-10.92894665 19.00369,-10.92894665 0.0732,0 2.8786,1.63446305 l 2.80542,1.634463 h 0.0244 v 0.024395 h 0.0244 v 0.024395 h 0.0244 l 0.0244,0.04879 v 0.024395 q 0.0244,0.09758 0.0244,0.09758 0.0244,0 0.0244,0.024395 l 0.0732,30.6888729 q 0,0.19516 -0.17077,0.292739 -18.8817,10.904552 -19.00368,10.904552 z m -0.31713,-0.902614 -0.0976,-29.883838 -4.83021,-2.829817 0.0976,29.908234 z M 171.7796,14.735235 189.95385,4.2453981 185.12365,1.4399765 166.94939,11.929814 Z m 0.41471,30.469318 18.17425,-10.489837 -0.0732,-29.9082336 -18.17425,10.5142326 z m 2.80542,-4.513069 q -0.34153,0 -0.34153,-0.317135 l -0.0732,-23.467961 q 0.0244,-0.170765 0.17076,-0.268345 12.6122,-7.3184911 12.73418,-7.3184911 0.31713,0 0.31713,0.3415296 l 0.0732,23.4923565 q -0.0244,0.14637 -0.17077,0.24395 -12.6122,7.294096 -12.70978,7.294096 z m 0.31714,-7.269701 6.39148,-3.708036 -0.0488,-16.320235 -6.39148,3.708035 z m 0,6.367087 11.56321,-6.684222 -4.8302,-2.805422 -6.73301,3.903196 z m 11.90474,-7.245306 -0.0732,-22.297003 -4.80581,2.781026 0.0488,16.68616 z"
fill="#fff" stroke="#ffffff"
/> stroke-opacity="1"
<path
d="M163.08 39.22l8.76-11.82q1.32-1.8 4.8-5.7l-.18-.3a63.09 63.09 0 01-7.74.42H163a9.79 9.79 0 01-.24-2.34 15.8 15.8 0 01.42-3.3h20.4a16.31 16.31 0 011 4.26 4.1 4.1 0 01-.78 2.34L175 34.66a64.65 64.65 0 01-4.56 5.7l.18.24q3.12-.3 5.22-.3h2.58a15.35 15.35 0 006.12-.9 9.4 9.4 0 01.72 3.12q0 3.42-4.32 3.42h-18a14.27 14.27 0 01-.9-3.93 5.08 5.08 0 011.04-2.79zM215.88 31.06q0 8-4.26 12.3a13.63 13.63 0 01-18.06 0q-4.26-4.26-4.26-12.3t4.26-12.31a13.63 13.63 0 0118.06 0q4.26 4.27 4.26 12.31zm-13.29-10.08q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM247 25.84v13.32a11 11 0 001.2 5.64 7 7 0 01-4.41 1.56q-2.43 0-3.33-1.14a5.69 5.69 0 01-.9-3.54V27.4a7.74 7.74 0 00-.72-3.87 2.78 2.78 0 00-2.58-1.17 8.62 8.62 0 00-6.3 3v20.58a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3v-29.7l.42-.36h2.76q3.42 0 4.08 3.6 4.38-3.84 8.73-3.84t6.42 2.82a12.17 12.17 0 012.07 7.38z"
/>
<path
d="M57.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84zM198.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84z"
fill="#fff"
/> />
</g> </g>
</svg> </svg>

View file

@ -0,0 +1,60 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
<title>Mobihb Logo</title>
<g data-name="header">
<path
d="M 37.247251,36.425753 H 32.934943 V 16.712344 H 30.676114 V 23.28348 H 25.388403 V 16.712344 H 23.89963 V 36.425753 H 15.480362 V 1.8246137 h 8.419268 v 2.2588281 h 3.439579 v 6.5711362 h 1.488773 V 4.0834418 h 4.106961 V 1.8246137 h 4.312308 z"
fill="#f00"
/>
<path
d="M 62.299752,18.252454 V 37.452493 H 40.532864 V 18.252454 Z m -13.34762,2.772198 v 13.655643 h 9.035312 V 21.024652 Z"
fill="#0f0"
/>
<path
d="M 87.352254,26.312364 V 36.425753 H 65.585365 V 7.1123249 h 8.419269 V 23.026795 h 10.062052 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.928352 v -6.314451 z"
fill="#00f"
/>
<path
d="M 98.235739,22.25674 V 36.425753 H 90.637863 V 22.25674 Z m -7.597876,-1.232088 v -2.772198 h 7.597876 v 2.772198 z"
fill="#ff0"
/>
<path
d="m 123.2882,26.312364 v 10.113389 h -8.41927 v -9.086649 h -4.92835 v 9.086649 h -8.41927 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z"
fill="#f0f"
/>
<path
d="M 148.3407,26.312364 V 36.425753 H 126.57381 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.92835 v -6.314451 z"
fill="#0ff"
/>
<path d="m 151.4041,30.014916 h 7.32726 v 6.419452 h -7.32726 z" />
<path
d="M 183.54661,17.241719 V 36.441758 H 161.77972 V 17.241719 Z m -13.34762,2.772198 V 33.66956 h 9.03531 V 20.013917 Z"
/>
<path
d="m 195.25149,24.06954 v 12.372218 h -8.41927 V 17.241719 h 8.41927 v 2.515513 h 1.48877 v -2.515513 h 11.85885 v 2.772198 h -6.57114 v 4.055623 z"
/>
<path
d="m 233.65161,18.525144 v 27.003263 h -18.48132 v -3.285568 h -3.28557 v -4.312308 h 8.41927 v 3.285568 h 4.92835 V 35.671703 H 211.88472 V 21.554027 h 3.28557 v -3.028883 z m -13.29628,3.901612 q 0,2.258828 -0.0513,4.62033 0,2.310166 0,4.568994 h 4.92835 q 0,-2.258828 0,-4.568994 0.0513,-2.361502 0.0513,-4.62033 z"
fill="#ffffff"
/>
</g>
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
</script>
<style lang="scss" scoped>
svg {
fill: $background-color;
&.invert {
fill: $secondary;
}
}
</style>

View file

@ -0,0 +1,46 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
<title>Mobilizon Logo</title>
<g data-name="header">
<path
d="M0 45.82l3.18-40.8a29.88 29.88 0 015.07-.36 27.74 27.74 0 014.95.36l4.86 17.16a92.19 92.19 0 012.34 10.08h.36a92.19 92.19 0 012.34-10.08L28 5.02a29.23 29.23 0 015-.36 29.23 29.23 0 015 .36l3.18 40.8a13.61 13.61 0 01-3.63.42 23.41 23.41 0 01-3.63-.24l-1.2-19.92q-.36-5.52-.48-12.84h-.44l-7.32 26.51a25.62 25.62 0 01-4 .3 23.36 23.36 0 01-3.84-.3L9.36 13.24H9q-.3 8.94-.48 12.84L7.26 46a22.47 22.47 0 01-3.6.24A13.75 13.75 0 010 45.82zM74 31.06q0 8-4.26 12.3a12.21 12.21 0 01-9 3.42 12.21 12.21 0 01-9-3.42q-4.26-4.26-4.26-12.3t4.24-12.31a12.21 12.21 0 019-3.42 12.21 12.21 0 019 3.42Q74 23.02 74 31.06zM60.75 20.98q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM103.2 19.75q2.7 4.11 2.7 11.28T102 42.31a13.18 13.18 0 01-10 4.11 31.41 31.41 0 01-11.34-2V2.2l.4-.45h2.76A4 4 0 0187 2.83a5.38 5.38 0 01.93 3.57v11.94a12.08 12.08 0 017.56-2.7 8.71 8.71 0 017.71 4.11zm-9.72 2a7.28 7.28 0 00-5.58 2.82v16a15 15 0 004.08.54 5.25 5.25 0 004.68-2.67q1.68-2.67 1.68-7.59 0-9.03-4.86-9.1zM121 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014 1.62A6.27 6.27 0 01121 22z"
/>
<path
d="M119.82.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z"
fill="#fff"
/>
<path
d="M139.08 40.42h2a10.23 10.23 0 01.6 3.18 9.24 9.24 0 01-.18 2.1 38.47 38.47 0 01-5.64.54q-6.48 0-6.48-7v-37l.36-.42h2.88a3.94 3.94 0 013.12 1.05 5.52 5.52 0 01.9 3.57v31.31q-.02 2.67 2.44 2.67zM155.94 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014.05 1.62 6.27 6.27 0 011.43 4.39z"
/>
<path
d="M154.8 2.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z"
fill="#fff"
/>
<path
d="M163.08 39.22l8.76-11.82q1.32-1.8 4.8-5.7l-.18-.3a63.09 63.09 0 01-7.74.42H163a9.79 9.79 0 01-.24-2.34 15.8 15.8 0 01.42-3.3h20.4a16.31 16.31 0 011 4.26 4.1 4.1 0 01-.78 2.34L175 34.66a64.65 64.65 0 01-4.56 5.7l.18.24q3.12-.3 5.22-.3h2.58a15.35 15.35 0 006.12-.9 9.4 9.4 0 01.72 3.12q0 3.42-4.32 3.42h-18a14.27 14.27 0 01-.9-3.93 5.08 5.08 0 011.04-2.79zM215.88 31.06q0 8-4.26 12.3a13.63 13.63 0 01-18.06 0q-4.26-4.26-4.26-12.3t4.26-12.31a13.63 13.63 0 0118.06 0q4.26 4.27 4.26 12.31zm-13.29-10.08q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM247 25.84v13.32a11 11 0 001.2 5.64 7 7 0 01-4.41 1.56q-2.43 0-3.33-1.14a5.69 5.69 0 01-.9-3.54V27.4a7.74 7.74 0 00-.72-3.87 2.78 2.78 0 00-2.58-1.17 8.62 8.62 0 00-6.3 3v20.58a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3v-29.7l.42-.36h2.76q3.42 0 4.08 3.6 4.38-3.84 8.73-3.84t6.42 2.82a12.17 12.17 0 012.07 7.38z"
/>
<path
d="M57.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84zM198.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84z"
fill="#fff"
/>
</g>
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
</script>
<style lang="scss" scoped>
svg {
fill: $background-color;
&.invert {
fill: $secondary;
}
}
</style>

View file

@ -0,0 +1,55 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
<title>Mobihb Logo</title>
<g data-name="header">
<path
d="M 37.247251,36.425753 H 32.934943 V 16.712344 H 30.676114 V 23.28348 H 25.388403 V 16.712344 H 23.89963 V 36.425753 H 15.480362 V 1.8246137 h 8.419268 v 2.2588281 h 3.439579 v 6.5711362 h 1.488773 V 4.0834418 h 4.106961 V 1.8246137 h 4.312308 z"
/>
<path
d="M 62.299752,18.252454 V 37.452493 H 40.532864 V 18.252454 Z m -13.34762,2.772198 v 13.655643 h 9.035312 V 21.024652 Z"
/>
<path
d="M 87.352254,26.312364 V 36.425753 H 65.585365 V 7.1123249 h 8.419269 V 23.026795 h 10.062052 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.928352 v -6.314451 z"
/>
<path
d="M 98.235739,22.25674 V 36.425753 H 90.637863 V 22.25674 Z m -7.597876,-1.232088 v -2.772198 h 7.597876 v 2.772198 z"
/>
<path
d="m 123.2882,26.312364 v 10.113389 h -8.41927 v -9.086649 h -4.92835 v 9.086649 h -8.41927 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z"
/>
<path
d="M 148.3407,26.312364 V 36.425753 H 126.57381 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.92835 v -6.314451 z"
/>
<path
d="m 151.4041,30.014916 h 7.32726 v 6.419452 h -7.32726 z"
/>
<path
d="M 183.54661,17.241719 V 36.441758 H 161.77972 V 17.241719 Z m -13.34762,2.772198 V 33.66956 h 9.03531 V 20.013917 Z"
/>
<path
d="m 195.25149,24.06954 v 12.372218 h -8.41927 V 17.241719 h 8.41927 v 2.515513 h 1.48877 v -2.515513 h 11.85885 v 2.772198 h -6.57114 v 4.055623 z"
/>
<path
d="m 233.65161,18.525144 v 27.003263 h -18.48132 v -3.285568 h -3.28557 v -4.312308 h 8.41927 v 3.285568 h 4.92835 V 35.671703 H 211.88472 V 21.554027 h 3.28557 v -3.028883 z m -13.29628,3.901612 q 0,2.258828 -0.0513,4.62033 0,2.310166 0,4.568994 h 4.92835 q 0,-2.258828 0,-4.568994 0.0513,-2.361502 0.0513,-4.62033 z"
/>
</g>
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
</script>
<style lang="scss" scoped>
svg {
fill: $background-color;
&.invert {
fill: $secondary;
}
}
</style>

View file

@ -0,0 +1,65 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
<title>Mobihb Logo</title>
<g data-name="header">
<path
d="M 37.247251,36.425753 H 32.934943 V 16.712344 H 30.676114 V 23.28348 H 25.388403 V 16.712344 H 23.89963 V 36.425753 H 15.480362 V 1.8246137 h 8.419268 v 2.2588281 h 3.439579 v 6.5711362 h 1.488773 V 4.0834418 h 4.106961 V 1.8246137 h 4.312308 z"
fill="#ffffff"
/>
<path
d="M 62.299752,18.252454 V 37.452493 H 40.532864 V 18.252454 Z m -13.34762,2.772198 v 13.655643 h 9.035312 V 21.024652 Z"
fill="#ffffff"
/>
<path
d="M 87.352254,26.312364 V 36.425753 H 65.585365 V 7.1123249 h 8.419269 V 23.026795 h 10.062052 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.928352 v -6.314451 z"
fill="#ffffff"
/>
<path
d="M 98.235739,22.25674 V 36.425753 H 90.637863 V 22.25674 Z m -7.597876,-1.232088 v -2.772198 h 7.597876 v 2.772198 z"
fill="#ffffff"
/>
<path
d="m 123.2882,26.312364 v 10.113389 h -8.41927 v -9.086649 h -4.92835 v 9.086649 h -8.41927 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z"
fill="#ffffff"
/>
<path
d="M 148.3407,26.312364 V 36.425753 H 126.57381 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.92835 v -6.314451 z"
fill="#ffffff"
/>
<path
d="m 151.4041,30.014916 h 7.32726 v 6.419452 h -7.32726 z"
fill="#ffffff"
/>
<path
d="M 183.54661,17.241719 V 36.441758 H 161.77972 V 17.241719 Z m -13.34762,2.772198 V 33.66956 h 9.03531 V 20.013917 Z"
fill="#ffffff"
/>
<path
d="m 195.25149,24.06954 v 12.372218 h -8.41927 V 17.241719 h 8.41927 v 2.515513 h 1.48877 v -2.515513 h 11.85885 v 2.772198 h -6.57114 v 4.055623 z"
fill="#ffffff"
/>
<path
d="m 233.65161,18.525144 v 27.003263 h -18.48132 v -3.285568 h -3.28557 v -4.312308 h 8.41927 v 3.285568 h 4.92835 V 35.671703 H 211.88472 V 21.554027 h 3.28557 v -3.028883 z m -13.29628,3.901612 q 0,2.258828 -0.0513,4.62033 0,2.310166 0,4.568994 h 4.92835 q 0,-2.258828 0,-4.568994 0.0513,-2.361502 0.0513,-4.62033 z"
fill="#ffffff"
/>
</g>
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
</script>
<style lang="scss" scoped>
svg {
fill: $background-color;
&.invert {
fill: $secondary;
}
}
</style>

View file

@ -0,0 +1,65 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
<title>Mobihb Logo</title>
<g data-name="header">
<path
d="M 37.247251,36.425753 H 32.934943 V 16.712344 H 30.676114 V 23.28348 H 25.388403 V 16.712344 H 23.89963 V 36.425753 H 15.480362 V 1.8246137 h 8.419268 v 2.2588281 h 3.439579 v 6.5711362 h 1.488773 V 4.0834418 h 4.106961 V 1.8246137 h 4.312308 z"
fill="#ffffff"
/>
<path
d="M 62.299752,18.252454 V 37.452493 H 40.532864 V 18.252454 Z m -13.34762,2.772198 v 13.655643 h 9.035312 V 21.024652 Z"
fill="#ffffff"
/>
<path
d="M 87.352254,26.312364 V 36.425753 H 65.585365 V 7.1123249 h 8.419269 V 23.026795 h 10.062052 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.928352 v -6.314451 z"
fill="#ffffff"
/>
<path
d="M 98.235739,22.25674 V 36.425753 H 90.637863 V 22.25674 Z m -7.597876,-1.232088 v -2.772198 h 7.597876 v 2.772198 z"
fill="#ffffff"
/>
<path
d="m 123.2882,26.312364 v 10.113389 h -8.41927 v -9.086649 h -4.92835 v 9.086649 h -8.41927 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z"
fill="#ffffff"
/>
<path
d="M 148.3407,26.312364 V 36.425753 H 126.57381 V 7.1123249 h 8.41927 V 23.026795 h 10.06205 v 3.285569 z m -13.34762,1.02674 v 6.314451 h 4.92835 v -6.314451 z"
fill="#ffffff"
/>
<path
d="m 151.4041,30.014916 h 7.32726 v 6.419452 h -7.32726 z"
fill="#ffffff"
/>
<path
d="M 183.54661,17.241719 V 36.441758 H 161.77972 V 17.241719 Z m -13.34762,2.772198 V 33.66956 h 9.03531 V 20.013917 Z"
fill="#ffffff"
/>
<path
d="m 195.25149,24.06954 v 12.372218 h -8.41927 V 17.241719 h 8.41927 v 2.515513 h 1.48877 v -2.515513 h 11.85885 v 2.772198 h -6.57114 v 4.055623 z"
fill="#ffffff"
/>
<path
d="m 233.65161,18.525144 v 27.003263 h -18.48132 v -3.285568 h -3.28557 v -4.312308 h 8.41927 v 3.285568 h 4.92835 V 35.671703 H 211.88472 V 21.554027 h 3.28557 v -3.028883 z m -13.29628,3.901612 q 0,2.258828 -0.0513,4.62033 0,2.310166 0,4.568994 h 4.92835 q 0,-2.258828 0,-4.568994 0.0513,-2.361502 0.0513,-4.62033 z"
fill="#ffffff"
/>
</g>
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
</script>
<style lang="scss" scoped>
svg {
fill: $background-color;
&.invert {
fill: $secondary;
}
}
</style>

View file

@ -1,7 +1,7 @@
<template> <template>
<b-navbar <b-navbar
id="navbar" id="navbar"
type="is-secondary" type="is-bleuvert"
wrapper-class="container" wrapper-class="container"
:active.sync="mobileNavbarActive" :active.sync="mobileNavbarActive"
> >
@ -16,7 +16,7 @@
</template> </template>
<template slot="start"> <template slot="start">
<b-navbar-item tag="router-link" :to="{ name: RouteName.SEARCH }">{{ <b-navbar-item tag="router-link" :to="{ name: RouteName.SEARCH }">{{
$t("Explore") $t("Filter")
}}</b-navbar-item> }}</b-navbar-item>
<b-navbar-item <b-navbar-item
v-if="currentActor.id && currentUser.isLoggedIn" v-if="currentActor.id && currentUser.isLoggedIn"
@ -344,7 +344,7 @@ nav {
} }
&.is-active { &.is-active {
background: $secondary; background: #1d1d1d;
} }
span.icon.is-medium { span.icon.is-medium {

View file

@ -40,12 +40,12 @@ export default class SettingMenuSection extends Vue {
<style lang="scss" scoped> <style lang="scss" scoped>
li { li {
font-size: 1.3rem; font-size: 1.3rem;
background-color: $secondary; background-color: #007292;
color: $background-color; color: $background-color;
margin: 2px auto; margin: 2px auto;
&.active { &.active {
background-color: #fea72b; background-color: #0000;
} }
a, a,
@ -54,7 +54,7 @@ li {
margin: 5px 0; margin: 5px 0;
display: block; display: block;
padding: 5px 10px; padding: 5px 10px;
color: inherit; color: rgb(0, 0, 0);
font-weight: 500; font-weight: 500;
} }
} }

View file

@ -0,0 +1,61 @@
<template>
<li :class="{ active: sectionActive }">
<router-link v-if="to" :to="to">{{ title }}</router-link>
<b v-else>{{ title }}</b>
<ul>
<slot></slot>
</ul>
</li>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import SettingMenuItem from "@/components/Settings/SettingMenuItem.vue";
import { Route } from "vue-router";
@Component({
components: { SettingMenuItem },
})
export default class SettingMenuSection extends Vue {
@Prop({ required: false, type: String }) title!: string;
@Prop({ required: true, type: Object }) to!: Route;
get sectionActive(): boolean {
if (this.$slots.default) {
return this.$slots.default.some(
({
componentOptions: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
propsData: { to },
},
}) => to && to.name === this.$route.name
);
}
return false;
}
}
</script>
<style lang="scss" scoped>
li {
font-size: 1.3rem;
background-color: $secondary;
color: $background-color;
margin: 2px auto;
&.active {
background-color: #fea72b;
}
a,
b {
cursor: pointer;
margin: 5px 0;
display: block;
padding: 5px 10px;
color: inherit;
font-weight: 500;
}
}
</style>

View file

@ -14,7 +14,7 @@
"@{username} ({role})": "@{username} ({role})", "@{username} ({role})": "@{username} ({role})",
"@{username}'s follow request was accepted": "Die Folgeanfrage von @{username} wurde angenommen", "@{username}'s follow request was accepted": "Die Folgeanfrage von @{username} wurde angenommen",
"@{username}'s follow request was rejected": "@{username}'s Folgeanfrage wurde zurückgewiesen", "@{username}'s follow request was rejected": "@{username}'s Folgeanfrage wurde zurückgewiesen",
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "Ein Cookie ist eine kleine Datei mit Informationen, die an Ihren Computer gesendet wird, wenn Sie eine Website besuchen. Wenn Sie die Website erneut besuchen, ermöglicht das Cookie dieser Website, Ihren Browser zu erkennen. Cookies können Benutzereinstellungen und andere Informationen speichern. Sie können Ihren Browser so konfigurieren, dass er alle Cookies ablehnt. Dies kann jedoch dazu führen, dass einige Funktionen oder Dienste der Website nur eingeschränkt funktionieren. Die lokale Speicherung funktioniert auf die gleiche Weise, ermöglicht es Ihnen jedoch, mehr Daten zu speichern.", "A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "Ein Cookie ist eine kleine Datei mit Informationen, die an Ihren Computer gesendet wird, wenn Sie eine Website besuchen. Wenn Sie die Website erneut besuchen, ermöglicht das Cookie dieser Website, Ihren Browser zu erkennen. Cookies können Benutzer*ineinstellungen und andere Informationen speichern. Sie können Ihren Browser so konfigurieren, dass er alle Cookies ablehnt. Dies kann jedoch dazu führen, dass einige Funktionen oder Dienste der Website nur eingeschränkt funktionieren. Die lokale Speicherung funktioniert auf die gleiche Weise, ermöglicht es Ihnen jedoch, mehr Daten zu speichern.",
"A discussion has been created or updated": "Eine Diskussion wurde erstellt oder aktualisiert", "A discussion has been created or updated": "Eine Diskussion wurde erstellt oder aktualisiert",
"A federated software": "Eine föderierte Software", "A federated software": "Eine föderierte Software",
"A fediverse account URL to follow for event updates": "Ein Link zu eine Fediverse-Account um Veranstaltungsankündigungen folgen zu können", "A fediverse account URL to follow for event updates": "Ein Link zu eine Fediverse-Account um Veranstaltungsankündigungen folgen zu können",
@ -33,7 +33,7 @@
"A resource has been created or updated": "Eine Ressource wurde erstellt oder aktualisiert", "A resource has been created or updated": "Eine Ressource wurde erstellt oder aktualisiert",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Ein kurzer Slogan für Ihre Instanz-Homepage. Standardmäßig steht dort \"Treffen ⋅ Organisieren ⋅ Mobilisieren\"", "A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Ein kurzer Slogan für Ihre Instanz-Homepage. Standardmäßig steht dort \"Treffen ⋅ Organisieren ⋅ Mobilisieren\"",
"A twitter account handle to follow for event updates": "Ein Twitter-Konto, dem Sie für Aktualisierungen der Veranstaltung folgen können", "A twitter account handle to follow for event updates": "Ein Twitter-Konto, dem Sie für Aktualisierungen der Veranstaltung folgen können",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Ein benutzerfreundliches, emanzipatorisches und ethisches Instrument zum Sammeln, Organisieren und Mobilisieren.", "A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Ein benutzer*innenfreundliches, emanzipatorisches und ethisches Instrument zum Sammeln, Organisieren und Mobilisieren.",
"A validation email was sent to {email}": "Es wurde eine Bestätigung-E-Mail an {email} gesendet", "A validation email was sent to {email}": "Es wurde eine Bestätigung-E-Mail an {email} gesendet",
"API": "API", "API": "API",
"Abandon editing": "Bearbeitung abbrechen", "Abandon editing": "Bearbeitung abbrechen",
@ -58,7 +58,7 @@
"Activated": "Aktiviert", "Activated": "Aktiviert",
"Active": "Aktiv", "Active": "Aktiv",
"Activity": "Ereignisse", "Activity": "Ereignisse",
"Actor": "Akteur:in", "Actor": "Akteur*in",
"Add": "Hinzufügen", "Add": "Hinzufügen",
"Add / Remove…": "Hinzufügen / Entfernen…", "Add / Remove…": "Hinzufügen / Entfernen…",
"Add a contact": "Füge einen Kontakt hinzu", "Add a contact": "Füge einen Kontakt hinzu",
@ -73,7 +73,7 @@
"Add some tags": "Füge Tags hinzu", "Add some tags": "Füge Tags hinzu",
"Add to my calendar": "Zu meinem Kalender hinzufügen", "Add to my calendar": "Zu meinem Kalender hinzufügen",
"Additional comments": "Zusätzliche Kommentare", "Additional comments": "Zusätzliche Kommentare",
"Admin": "Admininstrator:in", "Admin": "Admininstrator*in",
"Admin dashboard": "Übersichtsseite der Administration", "Admin dashboard": "Übersichtsseite der Administration",
"Admin settings": "Admineinstellungen", "Admin settings": "Admineinstellungen",
"Admin settings successfully saved.": "Admineinstellungen erfolgreich gespeichert.", "Admin settings successfully saved.": "Admineinstellungen erfolgreich gespeichert.",
@ -96,21 +96,21 @@
"An event I'm organizing has a new pending participation": "Eine Veranstaltung die ich organisiere, hat eine ausstehende Teilnahmeanfrage", "An event I'm organizing has a new pending participation": "Eine Veranstaltung die ich organisiere, hat eine ausstehende Teilnahmeanfrage",
"An event from one of my groups has been published": "Eine Veranstaltung einer meiner Gruppen wurde veröffentlicht", "An event from one of my groups has been published": "Eine Veranstaltung einer meiner Gruppen wurde veröffentlicht",
"An event from one of my groups has been updated or deleted": "Eine Veranstaltung einer meiner Gruppen wurde aktualisiert oder gelöscht", "An event from one of my groups has been updated or deleted": "Eine Veranstaltung einer meiner Gruppen wurde aktualisiert oder gelöscht",
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Als Instanz bezeichnen wir eine Installation der Mobilizon-Software auf einem Server. Eine Instanz kann von jedem mit Hilfe der {mobilizon_software} oder anderer kompatibler Software betrieben werden. Der Name dieser Instanz lautet „{instance_name}“. Diese Instanz ist Teil des „Fediverse“, einem Netzwerk aus vielen verbundenen Instanzen, die alle miteinander kommunizieren können. Nutzer von verschiedenen Instanzen können so - genau wie beim E-Mail-System - miteinander kommunizieren, auch wenn sie Accounts bei völlig verschiedenen Instanzen registriert haben.", "An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Als Instanz bezeichnen wir eine Installation der Mobilizon-Software auf einem Server. Eine Instanz kann von jedem mit Hilfe der {mobilizon_software} oder anderer kompatibler Software betrieben werden. Der Name dieser Instanz lautet „{instance_name}“. Diese Instanz ist Teil des „Fediverse“, einem Netzwerk aus vielen verbundenen Instanzen, die alle miteinander kommunizieren können. Nutzer*innen von verschiedenen Instanzen können so - genau wie beim E-Mail-System - miteinander kommunizieren, auch wenn sie Accounts bei völlig verschiedenen Instanzen registriert haben.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Eine Programmierschnittstelle, auch API genannt (von englisch „application programming interface“) definiert ein Kommunikationsprotokoll, das Softwarekomponenten erlaubt, miteinander zu interagieren. Die Mobilizon-API ermöglicht Drittanbietersoftware beispielsweise automatisiert bestimmte Aktionen auszuführen, z.B. das Erstellen von Veranstaltungen in Ihrem Namen.", "An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Eine Programmierschnittstelle, auch API genannt (von englisch „application programming interface“) definiert ein Kommunikationsprotokoll, das Softwarekomponenten erlaubt, miteinander zu interagieren. Die Mobilizon-API ermöglicht Drittanbietersoftware beispielsweise automatisiert bestimmte Aktionen auszuführen, z.B. das Erstellen von Veranstaltungen in Ihrem Namen.",
"And {number} comments": "Und {number} Kommentare", "And {number} comments": "Und {number} Kommentare",
"Announcements and mentions notifications are always sent straight away.": "Benachrichtigungen über Ankündigungen und Erwähnungen werden immer sofort verschickt.", "Announcements and mentions notifications are always sent straight away.": "Benachrichtigungen über Ankündigungen und Erwähnungen werden immer sofort verschickt.",
"Anonymous participant": "Anonyme Teilnehmer:in", "Anonymous participant": "Anonyme Teilnehmer*in",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonyme Teilnehmer:innen werden gebeten, ihre Teilnahme per E-Mail zu bestätigen.", "Anonymous participants will be asked to confirm their participation through e-mail.": "Anonyme Teilnehmer*innen werden gebeten, ihre Teilnahme per E-Mail zu bestätigen.",
"Anonymous participations": "Anonyme Teilnahme", "Anonymous participations": "Anonyme Teilnahme",
"Any day": "Egal wann", "Any day": "Egal wann",
"Any type": "Jeder Typ", "Any type": "Jeder Typ",
"Anyone can join freely": "Jeder kann frei beitreten", "Anyone can join freely": "Jeder kann frei beitreten",
"Anyone can request being a member, but an administrator needs to approve the membership.": "Jeder kann einen Antrag auf Mitgliedschaft stellen, aber ein Administrator muss die Mitgliedschaft genehmigen.", "Anyone can request being a member, but an administrator needs to approve the membership.": "Jeder kann einen Antrag auf Mitgliedschaft stellen, aber ein/e Administrator*in muss die Mitgliedschaft genehmigen.",
"Anyone wanting to be a member from your group will be able to from your group page.": "Jeder, der ein Mitglied Ihrer Gruppe werden möchte, kann dies von Ihrer Gruppenseite aus tun.", "Anyone wanting to be a member from your group will be able to from your group page.": "Jeder, der ein Mitglied Ihrer Gruppe werden möchte, kann dies von Ihrer Gruppenseite aus tun.",
"Application": "Anwendung", "Application": "Anwendung",
"Approve member": "Mitglied zulassen", "Approve member": "Mitglied zulassen",
"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.": "Sind Sie sich sicher, dass Sie Ihr gesamtes Konto löschen möchten? Sie verlieren dadurch alles. Identitäten, Einstellungen, erstellte Veranstaltungen, Nachrichten, Teilnahmen sind dann für immer verschwunden.", "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.": "Bist du sicher, dass du dein gesamten Account löschen möchtest? Du verlierst dadurch alles. Identitäten, Einstellungen, erstellte Veranstaltungen, Nachrichten, Teilnahmen sind dann für immer verschwunden.",
"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>.": "Möchten Sie diese Gruppe wirklich <b>löschen</b>? Alle Mitglieder - einschließlich externe - werden benachrichtigt und aus der Gruppe entfernt. Es werden <b>alle Gruppendaten (Veranstaltungen, Beiträge, Diskussionen, Aufgaben, ...) irreversibel gelöscht</b>.", "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>.": "Möchten Sie diese Gruppe wirklich <b>löschen</b>? Alle Mitglieder - einschließlich externe - werden benachrichtigt und aus der Gruppe entfernt. Es werden <b>alle Gruppendaten (Veranstaltungen, Beiträge, Diskussionen, Aufgaben, ...) irreversibel gelöscht</b>.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Sind Sie sicher, dass Sie diesen Kommentar <b>löschen</b> wollen? Diese Aktion kann nicht rückgängig gemacht werden.", "Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Sind Sie sicher, dass Sie diesen Kommentar <b>löschen</b> wollen? Diese Aktion kann nicht rückgängig gemacht werden.",
"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.": "Sind Sie sicher, dass Sie diese Veranstaltung <b>löschen</b> wollen? Diese Aktion kann nicht rückgängig gemacht werden.", "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.": "Sind Sie sicher, dass Sie diese Veranstaltung <b>löschen</b> wollen? Diese Aktion kann nicht rückgängig gemacht werden.",
@ -124,18 +124,19 @@
"Are you sure you want to delete this post? This action cannot be reverted.": "Bist du sicher, dass du diesen Beitrag löschen willst? Das kann nicht rückgängig gemacht werden.", "Are you sure you want to delete this post? This action cannot be reverted.": "Bist du sicher, dass du diesen Beitrag löschen willst? Das kann nicht rückgängig gemacht werden.",
"Are you sure you want to leave the group {groupName}? You'll loose access to this group's private content. This action cannot be undone.": "Sind Sie sicher, dass Sie die Gruppe {groupName} verlassen möchten? Sie verlieren den Zugang zu den privaten Inhalten dieser Gruppe. Diese Aktion kann nicht rückgängig gemacht werden.", "Are you sure you want to leave the group {groupName}? You'll loose access to this group's private content. This action cannot be undone.": "Sind Sie sicher, dass Sie die Gruppe {groupName} verlassen möchten? Sie verlieren den Zugang zu den privaten Inhalten dieser Gruppe. Diese Aktion kann nicht rückgängig gemacht werden.",
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Da der Veranstalter sich entschieden hat, die Teilnahmeanfragen manuell zu validieren, wird Ihre Teilnahme erst dann wirklich bestätigt, wenn Sie eine E-Mail erhalten, in der die Annahme bestätigt wird.", "As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Da der Veranstalter sich entschieden hat, die Teilnahmeanfragen manuell zu validieren, wird Ihre Teilnahme erst dann wirklich bestätigt, wenn Sie eine E-Mail erhalten, in der die Annahme bestätigt wird.",
"Ask your instance admin to {enable_feature}.": "Bitten Sie Ihren Instanzadministrator um {enable_feature}.", "Ask your instance admin to {enable_feature}.": "Bitte deine/n Instanzadministrator*in um {enable_feature}.",
"Assigned to": "Zugewiesen an", "Assigned to": "Zugewiesen an",
"Atom feed for events and posts": "Atom-Feed mit Veranstaltungen und Beiträgen", "Atom feed for events and posts": "Atom-Feed mit Veranstaltungen und Beiträgen",
"Attending": "Teilnahme", "Attending": "Teilnahme",
"Attention! The username below is unique! If you want to create a group with the same name, change your username below now as you can't later on. (With your group name you'll be able to link to your group directly eg. fomobremen.info/@groupname)": "Achtung! der Nutzer*inname unten ist einmalig. Wenn du später noch eine Gruppe mit dem selben Namen Anlegen möchtest, solltest du den Nutzer*inname unten entsprechend anpassen. (Deine Gruppe lässt sich direkt über einen Link wie fomobremen.info/@gruppenname öffnen.)",
"Avatar": "Profilbild", "Avatar": "Profilbild",
"Back to group list": "Zurück zur Gruppenliste", "Back to group list": "Zurück zur Gruppenliste",
"Back to previous page": "Zurück zur vorherigen Seite", "Back to previous page": "Zurück zur vorherigen Seite",
"Back to profile list": "Zurück zur Profilliste", "Back to profile list": "Zurück zur Profilliste",
"Back to top": "Zurück nach oben", "Back to top": "Zurück nach oben",
"Back to user list": "Zurück zur Benutzerliste", "Back to user list": "Zurück zur Benutzer*innenliste",
"Banner": "Banner", "Banner": "Banner",
"Before you can login, you need to click on the link inside it to validate your account.": "Bevor Sie sich anmelden können, müssen Sie auf den darin enthaltenen Link klicken, um Ihr Konto zu validieren.", "Before you can login, you need to click on the link inside it to validate your account.": "Bevor du dich einloggen kannst, musst da auf den darin enthaltenen Link klicken, um dein Account zu validieren.",
"Begins on": "Beginnt am", "Begins on": "Beginnt am",
"Big Blue Button": "Big Blue Button", "Big Blue Button": "Big Blue Button",
"Bold": "Fett", "Bold": "Fett",
@ -166,8 +167,8 @@
"Change my password": "Mein Passwort ändern", "Change my password": "Mein Passwort ändern",
"Change role": "Rolle ändern", "Change role": "Rolle ändern",
"Change timezone": "Zeitzone ändern", "Change timezone": "Zeitzone ändern",
"Change user email": "Benutzer-E-Mail ändern", "Change user email": "Benutzer*in-E-Mail ändern",
"Change user role": "Benutzerrolle ändern", "Change user role": "Benutzer*inrolle ändern",
"Check your inbox (and your junk mail folder).": "Prüfen Sie Ihren Posteingang (und den Spamordner).", "Check your inbox (and your junk mail folder).": "Prüfen Sie Ihren Posteingang (und den Spamordner).",
"Choose the source of the instance's Privacy Policy": "Wählen Sie die Quelle für die Instanz-Datenschutzrichtlinie", "Choose the source of the instance's Privacy Policy": "Wählen Sie die Quelle für die Instanz-Datenschutzrichtlinie",
"Choose the source of the instance's Terms": "Wählen Sie die Quelle der Instanz-Begriffe", "Choose the source of the instance's Terms": "Wählen Sie die Quelle der Instanz-Begriffe",
@ -192,7 +193,7 @@
"Confirm my participation": "Meine Teilnahme bestätigen", "Confirm my participation": "Meine Teilnahme bestätigen",
"Confirm my particpation": "Bestätige meine Teilnahme", "Confirm my particpation": "Bestätige meine Teilnahme",
"Confirm participation": "Teilnahme bestätigen", "Confirm participation": "Teilnahme bestätigen",
"Confirm user": "Benutzer bestätigen", "Confirm user": "Benutzer*in bestätigen",
"Confirmed": "Bestätigt", "Confirmed": "Bestätigt",
"Confirmed at": "Bestätigt um", "Confirmed at": "Bestätigt um",
"Confirmed: Will happen": "Bestätigt: Wird stattfinden", "Confirmed: Will happen": "Bestätigt: Wird stattfinden",
@ -214,7 +215,7 @@
"Create a new profile": "Neues Profil erstellen", "Create a new profile": "Neues Profil erstellen",
"Create a pad": "Pad erstellen", "Create a pad": "Pad erstellen",
"Create a videoconference": "Videokonferenz erstellen", "Create a videoconference": "Videokonferenz erstellen",
"Create an account": "Konto erstellen", "Create an account": "Account erstellen",
"Create discussion": "Diskussion erstellen", "Create discussion": "Diskussion erstellen",
"Create event": "Veranstaltung erstellen", "Create event": "Veranstaltung erstellen",
"Create group": "Erstelle eine Gruppe", "Create group": "Erstelle eine Gruppe",
@ -231,9 +232,9 @@
"Created by {username}": "Erstellt von {username}", "Created by {username}": "Erstellt von {username}",
"Current identity has been changed to {identityName} in order to manage this event.": "Aktuelle Identität wurde zu {identityName} geändert, um das Event bearbeiten zu können.", "Current identity has been changed to {identityName} in order to manage this event.": "Aktuelle Identität wurde zu {identityName} geändert, um das Event bearbeiten zu können.",
"Current page": "Aktuelle Seite", "Current page": "Aktuelle Seite",
"Custom": "Benutzerdefiniert", "Custom": "Benutzer*indefiniert",
"Custom URL": "Benutzerdefinierte URL", "Custom URL": "Benutzer*indefinierte URL",
"Custom text": "Benutzerdefinierter Text", "Custom text": "Benutzer*indefinierter Text",
"Daily email summary": "Tägliche E-Mail-Zusammenfassungen", "Daily email summary": "Tägliche E-Mail-Zusammenfassungen",
"Dashboard": "Dashboard", "Dashboard": "Dashboard",
"Date": "Datum", "Date": "Datum",
@ -248,13 +249,13 @@
"Delete": "Löschen", "Delete": "Löschen",
"Delete Comment": "Kommentar löschen", "Delete Comment": "Kommentar löschen",
"Delete Event": "Veranstaltung löschen", "Delete Event": "Veranstaltung löschen",
"Delete account": "Konto löschen", "Delete account": "Account löschen",
"Delete conversation": "Lösche Konversation", "Delete conversation": "Lösche Konversation",
"Delete discussion": "Diskussion löschen", "Delete discussion": "Diskussion löschen",
"Delete event": "Veranstaltung löschen", "Delete event": "Veranstaltung löschen",
"Delete everything": "Alles löschen", "Delete everything": "Alles löschen",
"Delete group": "Gruppe löschen", "Delete group": "Gruppe löschen",
"Delete my account": "Mein Konto löschen", "Delete my account": "Mein Account löschen",
"Delete post": "Beitrag löschen", "Delete post": "Beitrag löschen",
"Delete this discussion": "Diese Diskussion löschen", "Delete this discussion": "Diese Diskussion löschen",
"Delete this identity": "Diese Identität löschen", "Delete this identity": "Diese Identität löschen",
@ -262,22 +263,22 @@
"Delete {eventTitle}": "Lösche {eventTitle}", "Delete {eventTitle}": "Lösche {eventTitle}",
"Delete {preferredUsername}": "Lösche {preferredUsername}", "Delete {preferredUsername}": "Lösche {preferredUsername}",
"Deleting comment": "Kommentar löschen", "Deleting comment": "Kommentar löschen",
"Deleting event": "Veranstaltung wir gelöscht", "Deleting event": "Veranstaltung wird gelöscht",
"Deleting my account will delete all of my identities.": "Das Löschen meines Kontos wird alle meine Identitäten löschen.", "Deleting my account will delete all of my identities.": "Das Löschen meines Kontos wird alle meine Identitäten löschen.",
"Deleting your Mobilizon account": "Ihr Mobilizon Konto wird gelöscht", "Deleting your Mobilizon account": "Ihr Mobilizon Account wird gelöscht",
"Demote": "zurückstufen", "Demote": "zurückstufen",
"Description": "Beschreibung", "Description": "Beschreibung",
"Details": "Details", "Details": "Details",
"Didn't receive the instructions?": "Sie haben die Anleitung nicht erhalten?", "Didn't receive the instructions?": "Du hast die Anleitung nicht erhalten?",
"Disabled": "Deaktiviert", "Disabled": "Deaktiviert",
"Discussions": "Diskussionen", "Discussions": "Diskussionen",
"Discussions list": "Liste der Diskussionen", "Discussions list": "Liste der Diskussionen",
"Display name": "Anzeigename", "Display name": "Anzeigename",
"Display participation price": "Teilnahmegebühr anzeigen", "Display participation price": "Teilnahmegebühr anzeigen",
"Displayed nickname": "Angezeigter Nutzername", "Displayed nickname": "Angezeigter Nutzer*inname",
"Displayed on homepage and meta tags. Describe what Mobilizon is and what makes this instance special in a single paragraph.": "Wird auf der Startseite und in den Meta-Tags angezeigt. Beschreiben Sie in einem Absatz was Mobilizon ist und was diese Instanz besonders macht.", "Displayed on homepage and meta tags. Describe what Mobilizon is and what makes this instance special in a single paragraph.": "Wird auf der Startseite und in den Meta-Tags angezeigt. Beschreiben Sie in einem Absatz was Mobilizon ist und was diese Instanz besonders macht.",
"Do not receive any mail": "Keine E-Mails erhalten", "Do not receive any mail": "Keine E-Mails erhalten",
"Do you really want to suspend this account? All of the user's profiles will be deleted.": "Wollen Sie dieses Konto wirklich sperren? Alle Profile des Benutzers werden gelöscht.", "Do you really want to suspend this account? All of the user's profiles will be deleted.": "Willst du diesen Account wirklich sperren? Alle Profile der/des Benutzer*in werden gelöscht.",
"Do you wish to {create_event} or {explore_events}?": "Möchten Sie ein {create_event} oder {explore_events}?", "Do you wish to {create_event} or {explore_events}?": "Möchten Sie ein {create_event} oder {explore_events}?",
"Do you wish to {create_group} or {explore_groups}?": "Möchten Sie eine {create_group} oder {explore_groups}?", "Do you wish to {create_group} or {explore_groups}?": "Möchten Sie eine {create_group} oder {explore_groups}?",
"Does the event needs to be confirmed later or is it cancelled?": "Muss die Veranstaltung später noch bestätigt werden oder wurde sie abgesagt?", "Does the event needs to be confirmed later or is it cancelled?": "Muss die Veranstaltung später noch bestätigt werden oder wurde sie abgesagt?",
@ -289,12 +290,12 @@
"Edit": "Bearbeiten", "Edit": "Bearbeiten",
"Edit post": "Beitrag bearbeiten", "Edit post": "Beitrag bearbeiten",
"Edit profile {profile}": "Profil {profile} bearbeiten", "Edit profile {profile}": "Profil {profile} bearbeiten",
"Edit user email": "Benutzer-E-Mail bearbeiten", "Edit user email": "Benutzer*in-E-Mail bearbeiten",
"Edited {ago}": "Editiert {ago}", "Edited {ago}": "Editiert {ago}",
"Edited {relative_time} ago": "Vor {relative_time} bearbeitet", "Edited {relative_time} ago": "Vor {relative_time} bearbeitet",
"Eg: Stockholm, Dance, Chess…": "z.B.: Berlin, Tanzen, Schach…", "Eg: Stockholm, Dance, Chess…": "z.B.: Berlin, Tanzen, Schach…",
"Either on the {instance} instance or on another instance.": "Entweder auf der Instanz {instance} oder auf einer andere Instanz.", "Either on the {instance} instance or on another instance.": "Entweder auf der Instanz {instance} oder auf einer andere Instanz.",
"Either the account is already validated, either the validation token is incorrect.": "Das Konto ist bereits bestätigt oder der Bestätigungstoken ist falsch.", "Either the account is already validated, either the validation token is incorrect.": "Der Account ist bereits bestätigt oder der Bestätigungstoken ist falsch.",
"Either the email has already been changed, either the validation token is incorrect.": "Die E-Mail-Adresse wurde bereits geändert oder der Bestätigungstoken ist falsch.", "Either the email has already been changed, either the validation token is incorrect.": "Die E-Mail-Adresse wurde bereits geändert oder der Bestätigungstoken ist falsch.",
"Either the participation request has already been validated, either the validation token is incorrect.": "Die Teilnahmeanfrage wurde schon bestätigt, oder das Bestätigungstoken ist falsch.", "Either the participation request has already been validated, either the validation token is incorrect.": "Die Teilnahmeanfrage wurde schon bestätigt, oder das Bestätigungstoken ist falsch.",
"Element title": "Element-Überschrift", "Element title": "Element-Überschrift",
@ -345,14 +346,14 @@
"Everything": "Alles", "Everything": "Alles",
"Ex: mobilizon.fr": "Z.B.: mobilizon.fr", "Ex: mobilizon.fr": "Z.B.: mobilizon.fr",
"Ex: someone@mobilizon.org": "Bsp.: irgendjemand@mobilizon.org", "Ex: someone@mobilizon.org": "Bsp.: irgendjemand@mobilizon.org",
"Explore": "Entdecken", "Explore": "Alle Veranstaltungen",
"Explore events": "Entdecke Veranstaltungen", "Explore events": "Entdecke Veranstaltungen",
"Export": "Export", "Export": "Export",
"Failed to get location.": "Ort nicht ermittelbar.", "Failed to get location.": "Ort nicht ermittelbar.",
"Failed to save admin settings": "Admin-Einstellungen konnten nicht gespeichert werden", "Failed to save admin settings": "Admin-Einstellungen konnten nicht gespeichert werden",
"Featured events": "Vorgestellte Veranstaltungen", "Featured events": "Veranstaltungen",
"Federated Group Name": "Föderierter Gruppenname", "Federated Group Name": "Föderierter Gruppenname",
"Federation": "Federation", "Federation": "Föderation",
"Fediverse account": "Fediverse-Konto", "Fediverse account": "Fediverse-Konto",
"Fetch more": "Mehr abrufen", "Fetch more": "Mehr abrufen",
"Filter": "Filter", "Filter": "Filter",
@ -424,16 +425,16 @@
"Headline picture": "Titelbild", "Headline picture": "Titelbild",
"Hide replies": "Antworten ausblenden", "Hide replies": "Antworten ausblenden",
"Home": "Home", "Home": "Home",
"Home to {number} users": "Zuhause von {number} Nutzern", "Home to {number} users": "Zuhause von {number} Nutzer*innen",
"Homepage": "Website", "Homepage": "Website",
"Hourly email summary": "Stündliche E-Mail-Zusammenfassungen", "Hourly email summary": "Stündliche E-Mail-Zusammenfassungen",
"I agree to the {instanceRules} and {termsOfService}": "Ich stimme den {instanceRules} und den {termsOfService} zu", "I agree to the {instanceRules} and {termsOfService}": "Ich stimme den {instanceRules} und den {termsOfService} zu",
"I create an identity": "Neue Identität erstellen", "I create an identity": "Neue Identität erstellen",
"I don't have a Mobilizon account": "Ich habe kein Mobilizon Konto", "I don't have a Mobilizon account": "Ich habe kein Mobilizon Konto",
"I have a Mobilizon account": "Ich habe ein Mobilizon Konto", "I have a Mobilizon account": "Ich habe ein Mobilizon Konto",
"I have an account on another Mobilizon instance.": "Ich habe ein Konto auf einer anderen Mobilizon-Instanz.", "I have an account on another Mobilizon instance.": "Ich habe ein Account auf einer anderen Mobilizon-Instanz.",
"I participate": "Ich nehme teil", "I participate": "Ich nehme teil",
"I want to allow people to participate without an account.": "Ich möchte Menschen erlauben ohne Konto teilzunehmen.", "I want to allow people to participate without an account.": "Ich möchte Menschen erlauben ohne Account teilzunehmen.",
"I want to approve every participation request": "Ich möchte jede Teilnahmeanfrage manuell bestätigen", "I want to approve every participation request": "Ich möchte jede Teilnahmeanfrage manuell bestätigen",
"I've been mentionned in a comment under an event": "Ich wurde in einem Kommentar unter einer Veranstaltung erwähnt", "I've been mentionned in a comment under an event": "Ich wurde in einem Kommentar unter einer Veranstaltung erwähnt",
"I've been mentionned in a group discussion": "Ich wurde in einer Gruppendiskussion erwähnt", "I've been mentionned in a group discussion": "Ich wurde in einer Gruppendiskussion erwähnt",
@ -444,12 +445,12 @@
"Identity {displayName} created": "Identität {displayName} erstellt", "Identity {displayName} created": "Identität {displayName} erstellt",
"Identity {displayName} deleted": "Identität {displayName} gelöscht", "Identity {displayName} deleted": "Identität {displayName} gelöscht",
"Identity {displayName} updated": "Identität {displayName} aktualisiert", "Identity {displayName} updated": "Identität {displayName} aktualisiert",
"If allowed by organizer": "Wenn vom Organisator erlaubt", "If allowed by organizer": "Wenn von der/dem Organisator*in erlaubt",
"If an account with this email exists, we just sent another confirmation email to {email}": "Falls ein Konto mit dieser E-Mail-Adresse existiert, senden wir eine neue Bestätigungs-E-Mail an {email}", "If an account with this email exists, we just sent another confirmation email to {email}": "Falls ein Account mit dieser E-Mail-Adresse existiert, senden wir eine neue Bestätigungs-E-Mail an {email}",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Falls diese Identität der einzige Administrator einer oder mehrerer Gruppen sein sollte, ist zunächst die Gruppe zu löschen, bevor diese Identität gelöscht werden kann.", "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Falls diese Identität die/der einzige Administrator*in einer oder mehrerer Gruppen sein sollte, ist zunächst die Gruppe zu löschen, bevor diese Identität gelöscht werden kann.",
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Wenn Sie nach Ihrer föderierten Identität gefragt werden, setzt sich dieser aus Ihrem Benutzernamen und Ihrer Instanz zusammen. Die föderierte Identität für Ihr erstes Profil lautet zum Beispiel:", "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Wenn Sie nach Ihrer föderierten Identität gefragt werden, setzt sich dieser aus Ihrem Benutzer*innamen und Ihrer Instanz zusammen. Die föderierte Identität für Ihr erstes Profil lautet zum Beispiel:",
"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.": "Wenn Sie sich für die manuelle Validierung von Teilnehmern entschieden haben, sendet Ihnen Mobilizon eine E-Mail, um Sie über neue zu bearbeitende Teilnahmen zu informieren. Sie können die Häufigkeit dieser Benachrichtigungen unten auswählen.", "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.": "Wenn Sie sich für die manuelle Validierung von Teilnehmern entschieden haben, sendet Ihnen Mobilizon eine E-Mail, um Sie über neue zu bearbeitende Teilnahmen zu informieren. Sie können die Häufigkeit dieser Benachrichtigungen unten auswählen.",
"If you want, you may send a message to the event organizer here.": "Wenn Sie möchten, können Sie der Organisator:in eine Nachricht senden.", "If you want, you may send a message to the event organizer here.": "Wenn Sie möchten, können Sie dem/der Organisator*in eine Nachricht senden.",
"Ignore": "Ignorieren", "Ignore": "Ignorieren",
"In person": "Persönlich", "In person": "Persönlich",
"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.": "Im Folgenden meinen wir mit Anwendung eine Software, über die Sie mit Ihrer Instanz interagieren. Diese Software kann vom Mobilizon-Team oder von Dritten bereitgestellt werden.", "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.": "Im Folgenden meinen wir mit Anwendung eine Software, über die Sie mit Ihrer Instanz interagieren. Diese Software kann vom Mobilizon-Team oder von Dritten bereitgestellt werden.",
@ -467,7 +468,7 @@
"Instance Terms": "Instanz-Regeln", "Instance Terms": "Instanz-Regeln",
"Instance Terms Source": "Herkunft der Instanz-Regeln", "Instance Terms Source": "Herkunft der Instanz-Regeln",
"Instance Terms URL": "URL der Instanz-Regeln", "Instance Terms URL": "URL der Instanz-Regeln",
"Instance administrator": "Administrator der Instanz", "Instance administrator": "Administrator*in der Instanz",
"Instance configuration": "Einstellungen der Instanz", "Instance configuration": "Einstellungen der Instanz",
"Instance feeds": "Instanz Feeds", "Instance feeds": "Instanz Feeds",
"Instance languages": "Sprache der Instanz", "Instance languages": "Sprache der Instanz",
@ -519,19 +520,19 @@
"Local time ({timezone})": "Ortszeit ({timezone})", "Local time ({timezone})": "Ortszeit ({timezone})",
"Locality": "Ort", "Locality": "Ort",
"Location": "Ort", "Location": "Ort",
"Log in": "Anmelden", "Log in": "Einloggen",
"Log out": "Abmelden", "Log out": "Ausloggen",
"Login": "Login", "Login": "Login",
"Login on Mobilizon!": "Bei Mobilizon anmelden!", "Login on Mobilizon!": "Bei Mobilizon einloggen!",
"Login on {instance}": "Anmelden auf {instance}", "Login on {instance}": "Einloggen auf {instance}",
"Login status": "Login-Status", "Login status": "Login-Status",
"Main languages you/your moderators speak": "Hauptsprache Ihres Moderators", "Main languages you/your moderators speak": "Hauptsprache deiner Moderator*innen",
"Manage participations": "Teilnahmen verwalten", "Manage participations": "Teilnahmen verwalten",
"Manually approve new followers": "Neue Follower*innen manuell genehmigen", "Manually approve new followers": "Neue Follower*innen manuell genehmigen",
"Manually invite new members": "Manuelles Einladen neuer Mitglieder", "Manually invite new members": "Manuelles Einladen neuer Mitglieder",
"Mark as resolved": "Als erledigt markieren", "Mark as resolved": "Als erledigt markieren",
"Member": "Mitglied", "Member": "Mitglied",
"Members": "Mitglieder:innen", "Members": "Mitglieder*innen",
"Members-only post": "Beitrag nur für Mitglieder", "Members-only post": "Beitrag nur für Mitglieder",
"Memberships": "Mitgliedschaften", "Memberships": "Mitgliedschaften",
"Mentions": "Erwähnungen", "Mentions": "Erwähnungen",
@ -551,7 +552,7 @@
"Moderation": "Moderation", "Moderation": "Moderation",
"Moderation log": "Moderationsprotokoll", "Moderation log": "Moderationsprotokoll",
"Moderation logs": "Moderationsprotokoll", "Moderation logs": "Moderationsprotokoll",
"Moderator": "Moderator:in", "Moderator": "Moderator*in",
"Move": "Verschieben", "Move": "Verschieben",
"Move \"{resourceName}\"": "„{resourceName}“ verschieben", "Move \"{resourceName}\"": "„{resourceName}“ verschieben",
"Move resource to the root folder": "Verschiebe Ressource in das root Verzeichnis", "Move resource to the root folder": "Verschiebe Ressource in das root Verzeichnis",
@ -606,7 +607,7 @@
"No open reports yet": "Bisher keine ausstehenden Berichte", "No open reports yet": "Bisher keine ausstehenden Berichte",
"No organized events found": "Keine erstellte Veranstaltung gefunden", "No organized events found": "Keine erstellte Veranstaltung gefunden",
"No organized events listed": "Keine erstellten Veranstaltungen gelistet", "No organized events listed": "Keine erstellten Veranstaltungen gelistet",
"No participant matches the filters": "Kein Teilnehmer entspricht den Filterkriterien", "No participant matches the filters": "Kein/e Teilnehmer*in entspricht den Filterkriterien",
"No participant to approve|Approve participant|Approve {number} participants": "Keine Teilnahme zu bestätigen|Bestätige Teilnahme|Bestätige {number} Teilnahmen", "No participant to approve|Approve participant|Approve {number} participants": "Keine Teilnahme zu bestätigen|Bestätige Teilnahme|Bestätige {number} Teilnahmen",
"No participant to reject|Reject participant|Reject {number} participants": "Keine Teilnahmen abzulehnen|Teilnahme ablehnen|Lehne {number} Teilnahmen ab", "No participant to reject|Reject participant|Reject {number} participants": "Keine Teilnahmen abzulehnen|Teilnahme ablehnen|Lehne {number} Teilnahmen ab",
"No participations listed": "Keine Teilnahmen gelistet", "No participations listed": "Keine Teilnahmen gelistet",
@ -621,8 +622,8 @@
"No results for \"{queryText}\"": "Kein Ergebnis für \"{queryText}\"", "No results for \"{queryText}\"": "Kein Ergebnis für \"{queryText}\"",
"No results for {search}": "Keine Ergebnisse für {search}", "No results for {search}": "Keine Ergebnisse für {search}",
"No rules defined yet.": "Noch keine Regeln definiert.", "No rules defined yet.": "Noch keine Regeln definiert.",
"No user matches the filter": "Kein Benutzer entspricht den Filterkriterien", "No user matches the filter": "Kein*e Benutzer*in entspricht den Filterkriterien",
"No user matches the filters": "Kein Benutzer entspricht den Filterkriterien", "No user matches the filters": "Kein*e Benutzer*in entspricht den Filterkriterien",
"None": "Keine", "None": "Keine",
"Not accessible with a wheelchair": "Für Rollstühle nicht barrierefrei", "Not accessible with a wheelchair": "Für Rollstühle nicht barrierefrei",
"Not approved": "Nicht freigegeben", "Not approved": "Nicht freigegeben",
@ -633,8 +634,8 @@
"Notification settings": "Benachrichtigungseinstellungen", "Notification settings": "Benachrichtigungseinstellungen",
"Notifications": "Benachrichtigungen", "Notifications": "Benachrichtigungen",
"Notifications for manually approved participations to an event": "Benachrichtigungen bei manuell bestätigten Teilnahmen an einer Veranstlatung", "Notifications for manually approved participations to an event": "Benachrichtigungen bei manuell bestätigten Teilnahmen an einer Veranstlatung",
"Notify participants": "Teilnehmer:innen benachrichtigen", "Notify participants": "Teilnehmer*innen benachrichtigen",
"Notify the user of the change": "Benachrichtige den Benutzer über die Änderung", "Notify the user of the change": "Benachrichtige die/den Benutzer*in über die Änderung",
"Now, create your first profile:": "Erstellen Sie jetzt Ihr erstes Profil:", "Now, create your first profile:": "Erstellen Sie jetzt Ihr erstes Profil:",
"Number of places": "Anzahl der Plätze", "Number of places": "Anzahl der Plätze",
"OK": "OK", "OK": "OK",
@ -654,10 +655,10 @@
"Only group members can access discussions": "Nur Gruppenmitglieder können die Diskussion anzeigen", "Only group members can access discussions": "Nur Gruppenmitglieder können die Diskussion anzeigen",
"Only group moderators can create, edit and delete events.": "Nur Gruppen-Moderator*innen können Veranstaltungen erstellen, bearbeiten und löschen.", "Only group moderators can create, edit and delete events.": "Nur Gruppen-Moderator*innen können Veranstaltungen erstellen, bearbeiten und löschen.",
"Only group moderators can create, edit and delete posts.": "Nur Gruppenmoderatoren können Beiträge erstellen, editieren oder löschen.", "Only group moderators can create, edit and delete posts.": "Nur Gruppenmoderatoren können Beiträge erstellen, editieren oder löschen.",
"Only registered users may fetch remote events from their URL.": "Nur registrierte Benutzer können Remote-Veranstaltungen über ihre URL abrufen.", "Only registered users may fetch remote events from their URL.": "Nur registrierte Benutzer*innen können Remote-Veranstaltungen über ihre URL abrufen.",
"Open": "Offen", "Open": "Offen",
"Open a topic on our forum": "Eröffne ein Thema in unserem Forum", "Open a topic on our forum": "Eröffne ein Thema in unserem Forum",
"Open an issue on our bug tracker (advanced users)": "Eröffne eine Meldung in unserem Fehlermeldesystem (Bug-Tracker für erfahrene Benutzer)", "Open an issue on our bug tracker (advanced users)": "Eröffne eine Meldung in unserem Fehlermeldesystem (Bug-Tracker für erfahrene Benutzer*innen)",
"Opened reports": "Geöffnete Meldungen", "Opened reports": "Geöffnete Meldungen",
"Or": "Oder", "Or": "Oder",
"Ordered list": "Nummerierte Liste", "Ordered list": "Nummerierte Liste",
@ -665,23 +666,23 @@
"Organized by": "Organisiert von", "Organized by": "Organisiert von",
"Organized by {name}": "Organisiert von {name}", "Organized by {name}": "Organisiert von {name}",
"Organized events": "Organisierte Veranstaltungen", "Organized events": "Organisierte Veranstaltungen",
"Organizer": "Organisator:in", "Organizer": "Organisator*in",
"Organizer notifications": "Benachrichtigungen für Organisatoren", "Organizer notifications": "Benachrichtigungen für Organisator*innen",
"Organizers": "Organisator", "Organizers": "Organisator*innen",
"Other": "Andere", "Other": "Andere",
"Other actions": "Weitere Aktionen", "Other actions": "Weitere Aktionen",
"Other notification options:": "Andere Benachrichtigungs-Optionen:", "Other notification options:": "Andere Benachrichtigungs-Optionen:",
"Other software may also support this.": "Andere Software unterstützt dies möglicherweise auch.", "Other software may also support this.": "Andere Software unterstützt dies möglicherweise auch.",
"Other users with the same IP address": "Andere Benutzer mit der gleichen IP-Adresse", "Other users with the same IP address": "Andere Benutzer*innen mit der gleichen IP-Adresse",
"Other users with the same email domain": "Andere Benutzer mit der gleichen E-Mail-Domäne", "Other users with the same email domain": "Andere Benutzer*innen mit der gleichen E-Mail-Domäne",
"Otherwise this identity will just be removed from the group administrators.": "Andernfalls wird diese Identität einfach aus der Liste der Gruppenadministrator:innen entfernt.", "Otherwise this identity will just be removed from the group administrators.": "Andernfalls wird diese Identität einfach aus der Liste der Gruppenadministrator*innen entfernt.",
"Page": "Seite", "Page": "Seite",
"Page limited to my group (asks for auth)": "Seite ist auf meine Gruppe beschränkt (nach Authentifizierung fragen)", "Page limited to my group (asks for auth)": "Seite ist auf meine Gruppe beschränkt (nach Authentifizierung fragen)",
"Page not found": "Seite nicht gefunden", "Page not found": "Seite nicht gefunden",
"Parent folder": "Übergeordneter Ordner", "Parent folder": "Übergeordneter Ordner",
"Partially accessible with a wheelchair": "Teilweise barrierefrei für Rollstühle", "Partially accessible with a wheelchair": "Teilweise barrierefrei für Rollstühle",
"Participant": "Teilnehmer:in", "Participant": "Teilnehmer*in",
"Participants": "Teilnehmer:innen", "Participants": "Teilnehmer*innen",
"Participate": "Teilnehmen", "Participate": "Teilnehmen",
"Participate using your email address": "Nehmen Sie mit Ihrer E-Mail Adresse teil", "Participate using your email address": "Nehmen Sie mit Ihrer E-Mail Adresse teil",
"Participation approval": "Teilnahmebestätigung", "Participation approval": "Teilnahmebestätigung",
@ -705,7 +706,7 @@
"Pick an instance": "Wähle eine Instanz", "Pick an instance": "Wähle eine Instanz",
"Please add as many details as possible to help identify the problem.": "Bitte geben Sie uns so viele Details wie möglich, die uns helfen könnten, das Problem zu analysieren.", "Please add as many details as possible to help identify the problem.": "Bitte geben Sie uns so viele Details wie möglich, die uns helfen könnten, das Problem zu analysieren.",
"Please check your spam folder if you didn't receive the email.": "Bitte sehen Sie auch in Ihrem Spam-Ordner nach, wenn Sie keine E-Mail erhalten haben.", "Please check your spam folder if you didn't receive the email.": "Bitte sehen Sie auch in Ihrem Spam-Ordner nach, wenn Sie keine E-Mail erhalten haben.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Bitte kontaktieren Sie den Administrator:in dieser Mobilizon-Instanz, wenn Sie denken, dass dies ein Fehler ist.", "Please contact this instance's Mobilizon admin if you think this is a mistake.": "Bitte kontaktieren Sie den Administrator*in dieser Mobilizon-Instanz, wenn Sie denken, dass dies ein Fehler ist.",
"Please do not use it in any real way.": "Bitte nicht im praktischen Einsatz verwenden.", "Please do not use it in any real way.": "Bitte nicht im praktischen Einsatz verwenden.",
"Please enter your password to confirm this action.": "Bitte geben Sie zur Bestätigung des Vorgangs Ihr Passwort ein.", "Please enter your password to confirm this action.": "Bitte geben Sie zur Bestätigung des Vorgangs Ihr Passwort ein.",
"Please make sure the address is correct and that the page hasn't been moved.": "Bitte stellen Sie sicher, dass die Adresse korrekt ist und die Seite nicht verschoben wurde.", "Please make sure the address is correct and that the page hasn't been moved.": "Bitte stellen Sie sicher, dass die Adresse korrekt ist und die Seite nicht verschoben wurde.",
@ -762,9 +763,9 @@
"Refresh profile": "Profil aktualisieren", "Refresh profile": "Profil aktualisieren",
"Regenerate new links": "Erstelle die Links neu", "Regenerate new links": "Erstelle die Links neu",
"Region": "Region", "Region": "Region",
"Register": "Registrieren", "Register": "Account erstellen",
"Register an account on {instanceName}!": "Erstelle einen Account auf {instanceName}!", "Register an account on {instanceName}!": "Erstelle einen Account auf {instanceName}!",
"Register on this instance": "Auf dieser Instanz registrieren", "Register on this instance": "Erstelle einen account auf dieser Instanz",
"Registration is allowed, anyone can register.": "Registrierung erlaubt, jeder kann teilnehmen.", "Registration is allowed, anyone can register.": "Registrierung erlaubt, jeder kann teilnehmen.",
"Registration is closed.": "Registrierung geschlossen.", "Registration is closed.": "Registrierung geschlossen.",
"Registration is currently closed.": "Registrierungen sind aktuell geschlossen.", "Registration is currently closed.": "Registrierungen sind aktuell geschlossen.",
@ -842,13 +843,13 @@
"Show map": "Karte anzeigen", "Show map": "Karte anzeigen",
"Show me where I am": "Zeig mir, wo ich bin", "Show me where I am": "Zeig mir, wo ich bin",
"Show remaining number of places": "Freie Plätze anzeigen", "Show remaining number of places": "Freie Plätze anzeigen",
"Show the time when the event begins": "Zeige mir die Zeit, zu der die Veranstaltung beginnt", "Show the time when the event begins": "Zeige die Zeit, zu der die Veranstaltung beginnt",
"Show the time when the event ends": "Zeige mir die Zeit, zu der die Veranstaltung endet", "Show the time when the event ends": "Zeige die Zeit, zu der die Veranstaltung endet",
"Showing events before": "Anzeigen von Ereignissen vor", "Showing events before": "Anzeigen von Ereignissen vor",
"Showing events starting on": "Anzeigen von Veranstaltungen ab", "Showing events starting on": "Anzeigen von Veranstaltungen ab",
"Sign Language": "Gebärdensprache", "Sign Language": "Gebärdensprache",
"Sign in with": "Anmelden mit", "Sign in with": "Einloggen mit",
"Sign up": "Registrieren", "Sign up": "Account erstellen",
"Since you are a new member, private content can take a few minutes to appear.": "Da Sie ein neues Mitglied sind, kann es ein paar Minuten dauern bis private Inhalte sichtbar sind.", "Since you are a new member, private content can take a few minutes to appear.": "Da Sie ein neues Mitglied sind, kann es ein paar Minuten dauern bis private Inhalte sichtbar sind.",
"Skip to main content": "Zum Hauptinhalt springen", "Skip to main content": "Zum Hauptinhalt springen",
"Social": "Sozial", "Social": "Sozial",
@ -861,8 +862,8 @@
"Subtitles": "Untertitel", "Subtitles": "Untertitel",
"Suspend": "Sperren", "Suspend": "Sperren",
"Suspend group": "Gruppe sperren", "Suspend group": "Gruppe sperren",
"Suspend the account": "Das Konto sperren", "Suspend the account": "Den Account sperren",
"Suspend the account?": "Das Konto sperren?", "Suspend the account?": "Den Account sperren?",
"Suspended": "Gesperrt", "Suspended": "Gesperrt",
"Tag search": "Schlagwort-Suche", "Tag search": "Schlagwort-Suche",
"Task lists": "Aufgabenliste", "Task lists": "Aufgabenliste",
@ -883,7 +884,7 @@
"The URL where the event live can be watched again after it has ended": "Die URL, unter der die Veranstaltung nach ihrem Ende noch einmal angesehen werden kann", "The URL where the event live can be watched again after it has ended": "Die URL, unter der die Veranstaltung nach ihrem Ende noch einmal angesehen werden kann",
"The Zoom video teleconference URL": "Der Zoom Videokonferenz Link", "The Zoom video teleconference URL": "Der Zoom Videokonferenz Link",
"The account's email address was changed. Check your emails to verify it.": "Die E-Mail-Adresse des Accounts wurde geändert. Bitte prüfen Sie Ihre E-Mails für eine Bestätigung.", "The account's email address was changed. Check your emails to verify it.": "Die E-Mail-Adresse des Accounts wurde geändert. Bitte prüfen Sie Ihre E-Mails für eine Bestätigung.",
"The actual number of participants may differ, as this event is hosted on another instance.": "Die tatsächliche Zahl der Teilnehmer kann variieren, da diese Veranstaltung auf einer anderen Instanz ausgerichtet wird.", "The actual number of participants may differ, as this event is hosted on another instance.": "Die tatsächliche Zahl der Teilnehmer*innen kann variieren, da diese Veranstaltung auf einer anderen Instanz ausgerichtet wird.",
"The content came from another server. Transfer an anonymous copy of the report?": "Der Inhalt kam von einem anderen Server. Möchten Sie eine anonyme Kopie der Meldung übertragen?", "The content came from another server. Transfer an anonymous copy of the report?": "Der Inhalt kam von einem anderen Server. Möchten Sie eine anonyme Kopie der Meldung übertragen?",
"The draft event has been updated": "Der Entwurf wurde aktualisiert", "The draft event has been updated": "Der Entwurf wurde aktualisiert",
"The event has a sign language interpreter": "Für die Veranstaltung gibt es Dolmetschen in Gebärdensprache", "The event has a sign language interpreter": "Für die Veranstaltung gibt es Dolmetschen in Gebärdensprache",
@ -896,18 +897,18 @@
"The event live video contains subtitles": "Der Livestream der Veranstaltung enthält Untertitel", "The event live video contains subtitles": "Der Livestream der Veranstaltung enthält Untertitel",
"The event live video does not contain subtitles": "Der Livestream der Veranstaltung enthält keine Untertitel", "The event live video does not contain subtitles": "Der Livestream der Veranstaltung enthält keine Untertitel",
"The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "Der Veranstalter hat sich dafür entschieden, Teilnahmeanfragen manuell zu überprüfen. Möchten Sie eine kurze Nachricht hinterlassen, in der Sie erklären, warum Sie an der Veranstaltung teilnehmen möchten?", "The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "Der Veranstalter hat sich dafür entschieden, Teilnahmeanfragen manuell zu überprüfen. Möchten Sie eine kurze Nachricht hinterlassen, in der Sie erklären, warum Sie an der Veranstaltung teilnehmen möchten?",
"The event organizer didn't add any description.": "Der Organisator hat keine Beschreibung hinzugefügt.", "The event organizer didn't add any description.": "Die/Der Organisator*in hat keine Beschreibung hinzugefügt.",
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "Der Organisator möchte Teilnahmen manuell bestätigen. Wenn Sie ohne Konto teilnehmen möchten, erklären Sie bitte warum Sie an der Veranstaltung interessiert sind.", "The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "Die/Der Organisator*in möchte Teilnahmen manuell bestätigen. Wenn du ohne Account teilnehmen möchtest, erkläre bitte warum du an der Veranstaltung interessiert bist.",
"The event title will be ellipsed.": "Der Titel der Veranstaltung wird verkürzt dargestellt.", "The event title will be ellipsed.": "Der Titel der Veranstaltung wird verkürzt dargestellt.",
"The event will show as attributed to this group.": "Die Veranstaltung wird als dieser Gruppe zugehörig angezeigt.", "The event will show as attributed to this group.": "Die Veranstaltung wird als dieser Gruppe zugehörig angezeigt.",
"The event will show as attributed to this profile.": "Die Veranstaltung wird als Ihrem Profil zugewiesen angezeigt.", "The event will show as attributed to this profile.": "Die Veranstaltung wird als Ihrem Profil zugewiesen angezeigt.",
"The event will show as attributed to your personal profile.": "Die Veranstaltung wird als Ihrem persönlichen Profil zugewiesen angezeigt.", "The event will show as attributed to your personal profile.": "Die Veranstaltung wird als Ihrem persönlichen Profil zugewiesen angezeigt.",
"The event {event} was created by {profile}.": "Die Veranstaltung {event} wurde erstellt von {profile}.", "The event {event} was created by {profile}.": "Die Veranstaltung {event} wurde von {profile} erstellt.",
"The event {event} was deleted by {profile}.": "Die Veranstaltung {event} wurde von {profile} gelöscht.", "The event {event} was deleted by {profile}.": "Die Veranstaltung {event} wurde von {profile} gelöscht.",
"The event {event} was updated by {profile}.": "Die Veranstaltung {event} wurde von {profile} aktualisiert.", "The event {event} was updated by {profile}.": "Die Veranstaltung {event} wurde von {profile} aktualisiert.",
"The events you created are not shown here.": "Die Veranstaltung, die Sie erstellt haben ist hier nicht gelistet.", "The events you created are not shown here.": "Die Veranstaltung, die Sie erstellt haben ist hier nicht gelistet.",
"The geolocation prompt was denied.": "Die Abfrage der Geolokalisierung wurde verweigert.", "The geolocation prompt was denied.": "Die Abfrage der Geolokalisierung wurde verweigert.",
"The group can now be joined by anyone, but new members need to be approved by an administrator.": "Der Gruppe kann nun jeder beitreten, aber neue Mitglieder müssen von einem Administrator genehmigt werden.", "The group can now be joined by anyone, but new members need to be approved by an administrator.": "Der Gruppe kann nun jeder beitreten, aber neue Mitglieder müssen von einer/m Administrator*in genehmigt werden.",
"The group can now be joined by anyone.": "Der Gruppe können nun alle beitreten.", "The group can now be joined by anyone.": "Der Gruppe können nun alle beitreten.",
"The group can now only be joined with an invite.": "Der Gruppe kann nun ausschließlich durch Einladung beigetreten werden.", "The group can now only be joined with an invite.": "Der Gruppe kann nun ausschließlich durch Einladung beigetreten werden.",
"The group will be publicly listed in search results and may be suggested in the explore section. Only public informations will be shown on it's page.": "Diese Gruppe wird öffentlich in Suchergebnissen sichtbar sein und könnte im Bereich „Entdecken“ auftauchen. Nur öffentliche Informationen werden auf der Gruppenseite angezeigt.", "The group will be publicly listed in search results and may be suggested in the explore section. Only public informations will be shown on it's page.": "Diese Gruppe wird öffentlich in Suchergebnissen sichtbar sein und könnte im Bereich „Entdecken“ auftauchen. Nur öffentliche Informationen werden auf der Gruppenseite angezeigt.",
@ -915,11 +916,11 @@
"The group's banner was changed.": "Das Gruppenbanner wurde geändert.", "The group's banner was changed.": "Das Gruppenbanner wurde geändert.",
"The group's physical address was changed.": "Die physische Adresse der Gruppe wurde geändert.", "The group's physical address was changed.": "Die physische Adresse der Gruppe wurde geändert.",
"The group's short description was changed.": "Die Kurzbeschreibung der Gruppe wurde geändert.", "The group's short description was changed.": "Die Kurzbeschreibung der Gruppe wurde geändert.",
"The instance administrator is the person or entity that runs this Mobilizon instance.": "Der Administrator der Instanz ist die Person oder Organisation, die diese Mobilizon-Instanz betreibt.", "The instance administrator is the person or entity that runs this Mobilizon instance.": "Die/Der Administrator*in der Instanz ist die Person oder Organisation, die diese Mobilizon-Instanz betreibt.",
"The member was approved": "Das Mitglied wurde zugelassen", "The member was approved": "Das Mitglied wurde zugelassen",
"The member was removed from the group {group}": "Das Mitglied wurde aus der Gruppe {group} entfernt", "The member was removed from the group {group}": "Das Mitglied wurde aus der Gruppe {group} entfernt",
"The membership request from {profile} was rejected": "Der Mitgliedsantrag von {profile} wurde abgelehnt", "The membership request from {profile} was rejected": "Der Mitgliedsantrag von {profile} wurde abgelehnt",
"The only way for your group to get new members is if an admininistrator invites them.": "Die einzige Möglichkeit für Ihre Gruppe, neue Mitglieder zu bekommen, ist, wenn ein Administrator Sie einlädt.", "The only way for your group to get new members is if an admininistrator invites them.": "Die einzige Möglichkeit für Ihre Gruppe, neue Mitglieder zu bekommen, ist, wenn ein/e Administrator*in Sie einlädt.",
"The organiser has chosen to close comments.": "Der Veranstalter hat beschlossen, die Kommentare zu schließen.", "The organiser has chosen to close comments.": "Der Veranstalter hat beschlossen, die Kommentare zu schließen.",
"The page you're looking for doesn't exist.": "Die Seite, nach der Sie suchen existiert nicht.", "The page you're looking for doesn't exist.": "Die Seite, nach der Sie suchen existiert nicht.",
"The password was successfully changed": "Das Passwort wurde erfolgreich geändert", "The password was successfully changed": "Das Passwort wurde erfolgreich geändert",
@ -929,8 +930,8 @@
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Die Meldung wird an die Moderatoren Ihrer Instanz gesendet. Sie können unten erläutern, warum Sie diesen Inhalt melden.", "The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Die Meldung wird an die Moderatoren Ihrer Instanz gesendet. Sie können unten erläutern, warum Sie diesen Inhalt melden.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "Die ausgewählte Bilddatei ist zu groß. Die Datei darf höchstens {size} groß sein.", "The selected picture is too heavy. You need to select a file smaller than {size}.": "Die ausgewählte Bilddatei ist zu groß. Die Datei darf höchstens {size} groß sein.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Die technischen Details des Fehlers können den Entwickler*innen helfen, das Problem einfacher zu lösen. Bitte füge sie der Rückmeldung hinzu.", "The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Die technischen Details des Fehlers können den Entwickler*innen helfen, das Problem einfacher zu lösen. Bitte füge sie der Rückmeldung hinzu.",
"The user has been disabled": "Der Benutzer wurde deaktiviert", "The user has been disabled": "Die/Der Benutzer*in wurde deaktiviert",
"The {default_privacy_policy} will be used. They will be translated in the user's language.": "Die {default_privacy_policy} wird verwendet. Sie wird in die Sprache des Nutzers übersetzt.", "The {default_privacy_policy} will be used. They will be translated in the user's language.": "Die {default_privacy_policy} wird verwendet. Sie wird in die Sprache der/des Nutzer*in übersetzt.",
"The {default_terms} will be used. They will be translated in the user's language.": "Die {default_terms} werden verwendet. Sie werden in die Sprache der Nuttzer übersetzt.", "The {default_terms} will be used. They will be translated in the user's language.": "Die {default_terms} werden verwendet. Sie werden in die Sprache der Nuttzer übersetzt.",
"There are {participants} participants.": "Es gibt {participants} Teilnehmer.", "There are {participants} participants.": "Es gibt {participants} Teilnehmer.",
"There is no activity yet. Start doing some things to see activity appear here.": "Es gibt noch keine Ereignisse. Mache Dinge damit hier Ereignisse auftauchen.", "There is no activity yet. Start doing some things to see activity appear here.": "Es gibt noch keine Ereignisse. Mache Dinge damit hier Ereignisse auftauchen.",
@ -939,7 +940,7 @@
"These events may interest you": "Diese Veranstaltungen könnten Sie interessieren", "These events may interest you": "Diese Veranstaltungen könnten Sie interessieren",
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Diese Feeds enthalten Daten aller Veranstaltungen, die mit einem ihrer Profile erstellt wurden, oder an denen eins ihrer Profile teilnimmt. Sie sollten diese nicht weitergeben. Feeds einzelner Profile finden Sie auf der jeweiligen Profil-Einstellungsseite.", "These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Diese Feeds enthalten Daten aller Veranstaltungen, die mit einem ihrer Profile erstellt wurden, oder an denen eins ihrer Profile teilnimmt. Sie sollten diese nicht weitergeben. Feeds einzelner Profile finden Sie auf der jeweiligen Profil-Einstellungsseite.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Diese Feeds enthalten Daten aller Veranstaltungen, die mit diesem Profil erstellt wurden, oder an denen es teilnimmt. Sie sollten diese nicht weitergeben. Feeds aller ihrer Profile finden Sie in ihren Benachrichtigungseinstellungen.", "These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Diese Feeds enthalten Daten aller Veranstaltungen, die mit diesem Profil erstellt wurden, oder an denen es teilnimmt. Sie sollten diese nicht weitergeben. Feeds aller ihrer Profile finden Sie in ihren Benachrichtigungseinstellungen.",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Diese Mobilizon-Instanz und der Organisator akzeptieren anonyme Teilnahmen, aber eine Bestätigung per E-Mail ist erforderlich.", "This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Diese Mobilizon-Instanz und die/der Organisator*in akzeptieren anonyme Teilnahmen, aber eine Bestätigung per E-Mail ist erforderlich.",
"This URL doesn't seem to be valid": "Diese Webadresse scheint ungültig", "This URL doesn't seem to be valid": "Diese Webadresse scheint ungültig",
"This URL is not supported": "Diese URL wird nicht unterstützt", "This URL is not supported": "Diese URL wird nicht unterstützt",
"This event has been cancelled.": "Diese Veranstaltung wurde abgesagt.", "This event has been cancelled.": "Diese Veranstaltung wurde abgesagt.",
@ -955,16 +956,16 @@
"This instance isn't opened to registrations, but you can register on other instances.": "Diese Instanz lässt keine Registrierungen zu, aber Sie können sich auf anderen Instanzen registrieren.", "This instance isn't opened to registrations, but you can register on other instances.": "Diese Instanz lässt keine Registrierungen zu, aber Sie können sich auf anderen Instanzen registrieren.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Diese Instanz, <b>{instanceName} ({domain})</b>, hostet Ihr Profil, merken Sie sich also ihren Namen.", "This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Diese Instanz, <b>{instanceName} ({domain})</b>, hostet Ihr Profil, merken Sie sich also ihren Namen.",
"This is a demonstration site to test Mobilizon.": "Dies ist eine Demonstrationsseite, um Mobilizon zu testen.", "This is a demonstration site to test Mobilizon.": "Dies ist eine Demonstrationsseite, um Mobilizon zu testen.",
"This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.": "Dies ist wie Ihr föderierter Benutzername (<code>{username}</code>), aber für Gruppen. Damit kann die Gruppe auch auf anderen Instanzen gefunden werden und ist garantiert eindeutig.", "This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.": "Dies ist wie Ihr föderierter Benutzer*inname (<code>{username}</code>), aber für Gruppen. Damit kann die Gruppe auch auf anderen Instanzen gefunden werden und ist garantiert eindeutig.",
"This month": "Diesen Monat", "This month": "Diesen Monat",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Dieser Beitrag ist nur für Mitglieder verfügbar. Du hast Zugriff nur für Moderationszwecke, denn du bist Moderator:in dieser Instanz.", "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Dieser Beitrag ist nur für Mitglieder verfügbar. Du hast Zugriff nur für Moderationszwecke, denn du bist Moderator*in dieser Instanz.",
"This post is accessible only through it's link. Be careful where you post this link.": "Dieser Beitrag ist nur über dessen Link zugänglich. Seien Sie vorsichtig, wo Sie diesen Link posten.", "This post is accessible only through it's link. Be careful where you post this link.": "Dieser Beitrag ist nur über dessen Link zugänglich. Seien Sie vorsichtig, wo Sie diesen Link posten.",
"This profile is from another instance, the informations shown here may be incomplete.": "Dieses Profil ist von einer anderen Instanz, die Informationen hierzu können unvollständig sein.", "This profile is from another instance, the informations shown here may be incomplete.": "Dieses Profil ist von einer anderen Instanz, die Informationen hierzu können unvollständig sein.",
"This profile is located on this instance, so you need to {access_the_corresponding_account} to suspend it.": "Dieses Profil befindet sich auf dieser Instanz, daher müssen Sie {auf_das_entsprechende_Konto} zugreifen, um es zu sperren.", "This profile is located on this instance, so you need to {access_the_corresponding_account} to suspend it.": "Dieses Profil befindet sich auf dieser Instanz, daher müssen Sie {auf_das_entsprechende_Konto} zugreifen, um es zu sperren.",
"This profile was not found": "Dieses Profil wurde nicht gefunden", "This profile was not found": "Dieses Profil wurde nicht gefunden",
"This setting will be used to display the website and send you emails in the correct language.": "Diese Einstellung wird verwendet, um die Website anzuzeigen und Ihnen E-Mails in der richtigen Sprache zu senden.", "This setting will be used to display the website and send you emails in the correct language.": "Diese Einstellung wird verwendet, um die Website anzuzeigen und Ihnen E-Mails in der richtigen Sprache zu senden.",
"This user doesn't have any profiles": "Dieser Benutzer hat keine Profile", "This user doesn't have any profiles": "Diese*r Benutzer*in hat keine Profile",
"This user was not found": "Dieser Benutzer wurde nicht gefunden", "This user was not found": "Diese*r Benutzer*in wurde nicht gefunden",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Diese Website wird nicht moderiert und die Daten, die Sie eingeben, werden jeden Tag um 00:01 Uhr (Pariser Zeitzone) automatisch gelöscht.", "This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Diese Website wird nicht moderiert und die Daten, die Sie eingeben, werden jeden Tag um 00:01 Uhr (Pariser Zeitzone) automatisch gelöscht.",
"This week": "Diese Woche", "This week": "Diese Woche",
"This weekend": "Dieses Wochenende", "This weekend": "Dieses Wochenende",
@ -976,7 +977,7 @@
"Title": "Titel", "Title": "Titel",
"To activate more notifications, head over to the notification settings.": "Um mehr Benachrichtigungen zu aktivieren, sehen Sie in den Benachrichtigungs-Einstellungen vorbei.", "To activate more notifications, head over to the notification settings.": "Um mehr Benachrichtigungen zu aktivieren, sehen Sie in den Benachrichtigungs-Einstellungen vorbei.",
"To confirm, type your event title \"{eventTitle}\"": "Geben Sie zur Bestätigung Ihren Veranstaltungstitel „{eventTitle}“ ein", "To confirm, type your event title \"{eventTitle}\"": "Geben Sie zur Bestätigung Ihren Veranstaltungstitel „{eventTitle}“ ein",
"To confirm, type your identity username \"{preferredUsername}\"": "Geben Sie zur Bestätigung den Nutzernamen Ihrer Identität „{preferredUsername}“ ein", "To confirm, type your identity username \"{preferredUsername}\"": "Geben Sie zur Bestätigung den Nutzer*innamen Ihrer Identität „{preferredUsername}“ ein",
"To create and manage multiples identities from a same account": "So erstellen und verwalten Sie mehrere Identitäten über ein und dasselbe Konto", "To create and manage multiples identities from a same account": "So erstellen und verwalten Sie mehrere Identitäten über ein und dasselbe Konto",
"To create and manage your events": "So erstellen und verwalten Sie Ihre Ereignisse", "To create and manage your events": "So erstellen und verwalten Sie Ihre Ereignisse",
"To create or join an group and start organizing with other people": "So erstellen Sie eine Gruppe oder treten ihr bei und beginnen, sich mit anderen Personen zu organisieren", "To create or join an group and start organizing with other people": "So erstellen Sie eine Gruppe oder treten ihr bei und beginnen, sich mit anderen Personen zu organisieren",
@ -1005,7 +1006,7 @@
"Underline": "Unterstreichen", "Underline": "Unterstreichen",
"Undo": "Rückgängig macheb", "Undo": "Rückgängig macheb",
"Unfollow": "Nicht mehr folgen", "Unfollow": "Nicht mehr folgen",
"Unfortunately, your participation request was rejected by the organizers.": "Leider wurde Ihre Teilnahmeanfrage vom Organisator abgelehnt.", "Unfortunately, your participation request was rejected by the organizers.": "Leider wurde deine Teilnahmeanfrage von der/dem Organisator*in abgelehnt.",
"Unknown": "Unbekannt", "Unknown": "Unbekannt",
"Unknown actor": "Unbekannter Akteur", "Unknown actor": "Unbekannter Akteur",
"Unknown error.": "Unbekannter Fehler.", "Unknown error.": "Unbekannter Fehler.",
@ -1028,20 +1029,20 @@
"Uploaded media size": "Größe der hochgeladenen Medien", "Uploaded media size": "Größe der hochgeladenen Medien",
"Uploaded media total size": "Gesamtgröße der hochgeladenen Medien", "Uploaded media total size": "Gesamtgröße der hochgeladenen Medien",
"Use my location": "Nutzte meinen Standort", "Use my location": "Nutzte meinen Standort",
"User": "Nutzer", "User": "Nutzer*in",
"User settings": "Nutzer*inneneinstellungen", "User settings": "Nutzer*ineinstellungen",
"Username": "Nutzername", "Username": "Nutzer*inname",
"Users": "Nutzer", "Users": "Nutzer*innen",
"Validating account": "Konto bestätigen", "Validating account": "Account bestätigen",
"Validating email": "E-Mail bestätigen", "Validating email": "E-Mail bestätigen",
"Video Conference": "Videokonferenz", "Video Conference": "Videokonferenz",
"View a reply": "|Zeige eine Antwort|Zeige {totalReplies} Antworten", "View a reply": "|Zeige eine Antwort|Zeige {totalReplies} Antworten",
"View account on {hostname} (in a new window)": "Konto unter {hostname} aufrufen (neues Fenster)", "View account on {hostname} (in a new window)": "Account unter {hostname} aufrufen (neues Fenster)",
"View all": "Zeige alles", "View all": "Zeige alles",
"View all events": "Zeige alle Veranstaltungen", "View all events": "Zeige alle Veranstaltungen",
"View all posts": "Zeige alle Beiträge", "View all posts": "Zeige alle Beiträge",
"View event page": "Veranstaltungsseite anzeigen", "View event page": "Veranstaltungsseite anzeigen",
"View everything": "Alles anzeigen", "View everything": "Alle Veranstaltungen >>",
"View full profile": "Das gesamte Profil ansehen", "View full profile": "Das gesamte Profil ansehen",
"View less": "Weniger anzeigen", "View less": "Weniger anzeigen",
"View more": "Mehr anzeigen", "View more": "Mehr anzeigen",
@ -1053,11 +1054,11 @@
"Visible everywhere on the web (public)": "Sichtbar im ganzen Internet (öffentlich)", "Visible everywhere on the web (public)": "Sichtbar im ganzen Internet (öffentlich)",
"Waiting for organization team approval.": "Warte auf die Bestätigung des Organisationsteams.", "Waiting for organization team approval.": "Warte auf die Bestätigung des Organisationsteams.",
"Warning": "Warnung", "Warning": "Warnung",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Wir konnten die Teilnahme in diesem Browser nicht speichern. Doch keine Sorge, du hast die Teilnahme bestätigt. Wir könnten nur den Status in diesem Browser nicht setzen aufgrund eines technischen Problems.", "We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Wir konnten die Teilnahme in diesem Browser nicht speichern. Doch keine Sorge, Du hast die Teilnahme bestätigt. Wir könnten nur den Status in diesem Browser nicht setzen aufgrund eines technischen Problems.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Vielen Dank für Deine Rückmeldung. Wir werden die Software verbessern. Es gibt zwei Möglichkeiten, um uns dieses Problem mitzuteilen (beider erfordern leider das Anlegen eines eigenen Benutzerkontos):", "We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Vielen Dank für Deine Rückmeldung. Wir werden die Software verbessern. Es gibt zwei Möglichkeiten, um uns dieses Problem mitzuteilen (beider erfordern leider das Anlegen eines eigenen Benutzer*inkontos):",
"We just sent an email to {email}": "Wir haben gerade eine E-Mail an {email} gesendet", "We just sent an email to {email}": "Wir haben gerade eine E-Mail an {email} gesendet",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Wir nutzen Ihre Zeitzone, um sicherzustellen, dass Sie Benachrichtigungen für eine Veranstaltung zur richtigen Zeit erhalten.", "We use your timezone to make sure you get notifications for an event at the correct time.": "Wir nutzen Ihre Zeitzone, um sicherzustellen, dass du Benachrichtigungen für eine Veranstaltung zur richtigen Zeit erhältst.",
"We will redirect you to your instance in order to interact with this event": "Wir werden Sie auf Ihre Instanz weiterleiten, damit Sie die Veranstaltung bearbeiten können", "We will redirect you to your instance in order to interact with this event": "Wir werden Sie auf Ihre Instanz weiterleiten, damit du die Veranstaltung bearbeiten kannst",
"We will redirect you to your instance in order to interact with this group": "Wir leiten Sie zu Ihrer Instanz weiter, um mit dieser Gruppe zu interagieren", "We will redirect you to your instance in order to interact with this group": "Wir leiten Sie zu Ihrer Instanz weiter, um mit dieser Gruppe zu interagieren",
"We'll send you an email one hour before the event begins, to be sure you won't forget about it.": "Wir senden Ihnen eine Stunde vor Beginn der Veranstaltung eine E-Mail, um sicherzugehen, dass Sie diese nicht vergessen.", "We'll send you an email one hour before the event begins, to be sure you won't forget about it.": "Wir senden Ihnen eine Stunde vor Beginn der Veranstaltung eine E-Mail, um sicherzugehen, dass Sie diese nicht vergessen.",
"We'll use your timezone settings to send a recap of the morning of the event.": "Wir nutzen Ihre Zeitzonen-Einstellung, um Ihnen am Morgen der Veranstaltung eine Erinnerung zu senden.", "We'll use your timezone settings to send a recap of the morning of the event.": "Wir nutzen Ihre Zeitzonen-Einstellung, um Ihnen am Morgen der Veranstaltung eine Erinnerung zu senden.",
@ -1079,82 +1080,82 @@
"Who can view this event and participate": "Wer kann diese Veranstaltung sehen und daran teilnehmen kann", "Who can view this event and participate": "Wer kann diese Veranstaltung sehen und daran teilnehmen kann",
"Who can view this post": "Wer kann diesen Beitrag sehen", "Who can view this post": "Wer kann diesen Beitrag sehen",
"Who published {number} events": "Die {number} Veranstaltungen angelegt haben", "Who published {number} events": "Die {number} Veranstaltungen angelegt haben",
"Why create an account?": "Warum ein Konto erstellen?", "Why create an account?": "Warum ein Account erstellen?",
"Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.": "Ermöglicht die Anzeige und Verwaltung Ihres Teilnahmestatus auf der Veranstaltungsseite, wenn Sie dieses Gerät verwenden. Deaktivieren Sie diese Option, wenn Sie ein öffentliches Gerät verwenden.", "Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.": "Ermöglicht die Anzeige und Verwaltung Ihres Teilnahmestatus auf der Veranstaltungsseite, wenn Sie dieses Gerät verwenden. Deaktivieren Sie diese Option, wenn Sie ein öffentliches Gerät verwenden.",
"Within {number} kilometers of {place}": "|Innerhalb eines Kilometers von {place}|Innerhalb von {number} Kilometer von {place}", "Within {number} kilometers of {place}": "|Innerhalb eines Kilometers von {place}|Innerhalb von {number} Kilometer von {place}",
"Yesterday": "Gestern", "Yesterday": "Gestern",
"You accepted the invitation to join the group.": "Sie haben die Einladung zur Gruppe angenommen.", "You accepted the invitation to join the group.": "Du hast die Einladung zur Gruppe angenommen.",
"You added the member {member}.": "Sie haben das Mitglied {member} hinzugefügt.", "You added the member {member}.": "Du hast das Mitglied {member} hinzugefügt.",
"You approved {member}'s membership.": "Sie haben die Mitgliedschaft von {member} genehmigt.", "You approved {member}'s membership.": "Du hast die Mitgliedschaft von {member} genehmigt.",
"You archived the discussion {discussion}.": "Sie haben die Diskussion {discussion} archiviert.", "You archived the discussion {discussion}.": "Du hast die Diskussion {discussion} archiviert.",
"You are not an administrator for this group.": "Sie sind kein Administrator dieser Gruppe.", "You are not an administrator for this group.": "Sie sind kein/e Administrator*in dieser Gruppe.",
"You are not part of any group.": "Sie sind kein Teil einer Gruppe.", "You are not part of any group.": "Sie sind kein Teil einer Gruppe.",
"You are offline": "Sie sind offline", "You are offline": "Sie sind offline",
"You are participating in this event anonymously": "Sie nehmen anonym an dieser Veranstaltung teil", "You are participating in this event anonymously": "Du nimmst anonym an dieser Veranstaltung teil",
"You are participating in this event anonymously but didn't confirm participation": "Sie nehmen an dieser Veranstaltung anonym teil, haben aber Ihre Teilnahme noch nicht bestätigt", "You are participating in this event anonymously but didn't confirm participation": "Du nimmst an dieser Veranstaltung anonym teil, hast aber deine Teilnahme noch nicht bestätigt",
"You can add tags by hitting the Enter key or by adding a comma": "Sie können Schlagworte hinzufügen, indem Sie Komma oder Enter drücken", "You can add tags by hitting the Enter key or by adding a comma": "Sie können Schlagworte hinzufügen, indem Sie Komma oder Enter drücken",
"You can pick your timezone into your preferences.": "Sie können Ihre Zeitzone in den Einstellungen festlegen.", "You can pick your timezone into your preferences.": "Sie können Ihre Zeitzone in den Einstellungen festlegen.",
"You can try another search term or drag and drop the marker on the map": "Sie können einen anderen Suchbegriff verwenden oder die Markierung auf der Karte verschieben", "You can try another search term or drag and drop the marker on the map": "Sie können einen anderen Suchbegriff verwenden oder die Markierung auf der Karte verschieben",
"You can't change your password because you are registered through {provider}.": "Sie können Ihr Passwort nicht ändern, weil Sie über {provider} angemeldet sind.", "You can't change your password because you are registered through {provider}.": "Sie können Ihr Passwort nicht ändern, weil Sie über {provider} angemeldet sind.",
"You can't use push notifications in this browser.": "Du kannst keine Push-Benachrichtigungen in diesem Browser nutzen.", "You can't use push notifications in this browser.": "Du kannst keine Push-Benachrichtigungen in diesem Browser nutzen.",
"You changed your email or password": "Sie haben Ihre Email-Adresse oder Ihr Passwort geändert", "You changed your email or password": "Du hast Ihre Email-Adresse oder Ihr Passwort geändert",
"You created the discussion {discussion}.": "Sie haben die Diskussion {discussion} erstellt.", "You created the discussion {discussion}.": "Du hast die Diskussion {discussion} erstellt.",
"You created the event {event}.": "Sie haben die Veranstaltung {event} erstellt.", "You created the event {event}.": "Du hast die Veranstaltung {event} erstellt.",
"You created the folder {resource}.": "Sie haben den Ordner {resource} erstellt.", "You created the folder {resource}.": "Du hast den Ordner {resource} erstellt.",
"You created the group {group}.": "Sie haben die Gruppe {group} erstellt.", "You created the group {group}.": "Du hast die Gruppe {group} erstellt.",
"You created the post {post}.": "Sie haben den Beitrag {post} erstellt.", "You created the post {post}.": "Du hast den Beitrag {post} erstellt.",
"You created the resource {resource}.": "Sie haben die Ressource {resource} erstellt.", "You created the resource {resource}.": "Du hast die Ressource {resource} erstellt.",
"You deleted the discussion {discussion}.": "Sie haben die Diskussion {discussion} gelöscht.", "You deleted the discussion {discussion}.": "Du hast die Diskussion {discussion} gelöscht.",
"You deleted the event {event}.": "Sie haben die Veranstaltung {event} gelöscht.", "You deleted the event {event}.": "Du hast die Veranstaltung {event} gelöscht.",
"You deleted the folder {resource}.": "Sie haben den Ordner {resource} gelöscht.", "You deleted the folder {resource}.": "Du hast den Ordner {resource} gelöscht.",
"You deleted the post {post}.": "Sie haben den Beitrag {post} gelöscht.", "You deleted the post {post}.": "Du hast den Beitrag {post} gelöscht.",
"You deleted the resource {resource}.": "Sie haben die Ressource {resource} gelöscht.", "You deleted the resource {resource}.": "Du hast die Ressource {resource} gelöscht.",
"You demoted the member {member} to an unknown role.": "Sie haben {member} zu einer unbekannten Rolle zurückgestuft.", "You demoted the member {member} to an unknown role.": "Du hast {member} zu einer unbekannten Rolle zurückgestuft.",
"You demoted {member} to moderator.": "Sie haben {member} zum/zur Moderator/in zurückgestuft.", "You demoted {member} to moderator.": "Du hast {member} zum/zur Moderator*in zurückgestuft.",
"You demoted {member} to simple member.": "Sie haben {member} zu einem einfachen Mitglied zurückgestuft.", "You demoted {member} to simple member.": "Du hast {member} zu einem einfachen Mitglied zurückgestuft.",
"You didn't create or join any event yet.": "Sie haben keine Veranstaltung erstellt oder nehmen an einer teil.", "You didn't create or join any event yet.": "Du hast keine Veranstaltung erstellt oder nehmen an einer teil.",
"You don't follow any instances yet.": "Sie folgen noch keinen Instanzen.", "You don't follow any instances yet.": "Sie folgen noch keinen Instanzen.",
"You don't have any upcoming events. Maybe try another filter?": "Es gibt keine anstehenden Veranstaltungen. Probiere doch einen anderen Filter.", "You don't have any upcoming events. Maybe try another filter?": "Es gibt keine anstehenden Veranstaltungen. Probiere doch einen anderen Filter.",
"You excluded member {member}.": "Sie haben {member} ausgeschlossen.", "You excluded member {member}.": "Du hast {member} ausgeschlossen.",
"You have attended {count} events in the past.": "Sie haben in der Vergangenheit keine Veranstaltungen besucht.|Sie haben in der Vergangenheit eine Veranstaltung besucht.|Sie haben in der Vergangenheit {count} Veranstaltungen besucht.", "You have attended {count} events in the past.": "Du hast in der Vergangenheit keine Veranstaltungen besucht.|Du hast in der Vergangenheit eine Veranstaltung besucht.|Du hast in der Vergangenheit {count} Veranstaltungen besucht.",
"You have been disconnected": "Ihre Verbindung wurde getrennt", "You have been disconnected": "Ihre Verbindung wurde getrennt",
"You have been invited by {invitedBy} to the following group:": "Sie wurden von {invitedBy} zu dieser Gruppe eingeladen:", "You have been invited by {invitedBy} to the following group:": "Sie wurden von {invitedBy} zu dieser Gruppe eingeladen:",
"You have been removed from this group's members.": "Sie wurden von diesen Gruppenmitgliedern entfernt.", "You have been removed from this group's members.": "Sie wurden von diesen Gruppenmitgliedern entfernt.",
"You have cancelled your participation": "Sie haben Ihre Teilnahme abgesagt", "You have cancelled your participation": "Du hast deine Teilnahme abgesagt",
"You have one event in {days} days.": "Sie haben keine Veranstaltung in {days} Tagen | Sie haben eine Veranstaltung in {days} Tagen | Sie haben {count} Veranstaltungen in {days} Tagen", "You have one event in {days} days.": "Du hast keine Veranstaltung in {days} Tagen | Du hast eine Veranstaltung in {days} Tagen | Du hast {count} Veranstaltungen in {days} Tagen",
"You have one event today.": "Sie haben heute keine Veranstaltungen | Sie haben heute eine Veranstaltung. | Sie haben heute {count} Veranstaltungen", "You have one event today.": "Du hast heute keine Veranstaltungen | Du hast heute eine Veranstaltung. | Du hast heute {count} Veranstaltungen",
"You have one event tomorrow.": "Sie haben morgen keine Veranstaltungen | Sie haben morgen eine Veranstaltung. | Sie haben morgen {count} Veranstaltungen", "You have one event tomorrow.": "Du hast morgen keine Veranstaltungen | Du hast morgen eine Veranstaltung. | Du hast morgen {count} Veranstaltungen",
"You haven't interacted with other instances yet.": "Sie haben noch nicht mit anderen Instanzen interagiert.", "You haven't interacted with other instances yet.": "Du hast noch nicht mit anderen Instanzen interagiert.",
"You invited {member}.": "Sie haben {member} eingeladen.", "You invited {member}.": "Du hast {member} eingeladen.",
"You may clear all participation information for this device with the buttons below.": "Sie können alle Teilnahmeinformationen für dieses Gerät mit den Schaltflächen unten löschen.", "You may clear all participation information for this device with the buttons below.": "Sie können alle Teilnahmeinformationen für dieses Gerät mit den Schaltflächen unten löschen.",
"You may now close this window, or {return_to_event}.": "Sie können das Fenster jetzt schließen oder {return_to_event}.", "You may now close this window, or {return_to_event}.": "Sie können das Fenster jetzt schließen oder {return_to_event}.",
"You may show some members as contacts.": "Sie können einzelne Mitglieder als Kontakte anzeigen lassen.", "You may show some members as contacts.": "Sie können einzelne Mitglieder als Kontakte anzeigen lassen.",
"You moved the folder {resource} into {new_path}.": "Sie haben den Ordner {resource} nach {new_path} verschoben.", "You moved the folder {resource} into {new_path}.": "Du hast den Ordner {resource} nach {new_path} verschoben.",
"You moved the folder {resource} to the root folder.": "Sie haben den Ordner {resource} in das Root-Verzeichnis verschoben.", "You moved the folder {resource} to the root folder.": "Du hast den Ordner {resource} in das Root-Verzeichnis verschoben.",
"You moved the resource {resource} into {new_path}.": "Sie haben die Ressource {resource} nach {new_path} verschoben.", "You moved the resource {resource} into {new_path}.": "Du hast die Ressource {resource} nach {new_path} verschoben.",
"You moved the resource {resource} to the root folder.": "Sie haben die Ressource {resource} in das Root-Verzeichnis verschoben.", "You moved the resource {resource} to the root folder.": "Du hast die Ressource {resource} in das Root-Verzeichnis verschoben.",
"You need to login.": "Sie müssen sich einloggen.", "You need to login.": "Sie müssen sich einloggen.",
"You posted a comment on the event {event}.": "Sie haben die Veranstaltung {event} kommentiert.", "You posted a comment on the event {event}.": "Du hast die Veranstaltung {event} kommentiert.",
"You promoted the member {member} to an unknown role.": "Sie haben {member} einer unbekannten Rolle zugewiesen.", "You promoted the member {member} to an unknown role.": "Du hast {member} einer unbekannten Rolle zugewiesen.",
"You promoted {member} to administrator.": "Sie haben {member} zum/zur Administrator/in befördert.", "You promoted {member} to administrator.": "Du hast {member} zum/zur Administrator*in befördert.",
"You promoted {member} to moderator.": "Sie haben {member} zum/zur Moderator/in befördert.", "You promoted {member} to moderator.": "Du hast {member} zum/zur Moderator*in befördert.",
"You rejected {member}'s membership request.": "Sie haben den Mitgliedsantrag von {member} abgelehnt.", "You rejected {member}'s membership request.": "Du hast den Mitgliedsantrag von {member} abgelehnt.",
"You renamed the discussion from {old_discussion} to {discussion}.": "Sie haben die Diskussion {old_discussion} in {discussion} umbenannt.", "You renamed the discussion from {old_discussion} to {discussion}.": "Du hast die Diskussion {old_discussion} in {discussion} umbenannt.",
"You renamed the folder from {old_resource_title} to {resource}.": "Sie haben den Ordner {old_resource_title} in {resource} umbenannt.", "You renamed the folder from {old_resource_title} to {resource}.": "Du hast den Ordner {old_resource_title} in {resource} umbenannt.",
"You renamed the resource from {old_resource_title} to {resource}.": "Sie haben die Ressource {old_resource_title} in {resource} umbenannt.", "You renamed the resource from {old_resource_title} to {resource}.": "Du hast die Ressource {old_resource_title} in {resource} umbenannt.",
"You replied to a comment on the event {event}.": "Sie haben auf ein Kommentar in der Veranstaltung {event} geantwortet.", "You replied to a comment on the event {event}.": "Du hast auf ein Kommentar in der Veranstaltung {event} geantwortet.",
"You replied to the discussion {discussion}.": "Sie haben auf die Diskussion {discussion} geantwortet.", "You replied to the discussion {discussion}.": "Du hast auf die Diskussion {discussion} geantwortet.",
"You requested to join the group.": "Sie haben die angefragt der Gruppe beizutreten.", "You requested to join the group.": "Du hast die angefragt der Gruppe beizutreten.",
"You updated the event {event}.": "Sie haben die Veranstaltung {event} aktualisiert.", "You updated the event {event}.": "Du hast die Veranstaltung {event} aktualisiert.",
"You updated the group {group}.": "Sie haben die Gruppe {group} aktualisiert.", "You updated the group {group}.": "Du hast die Gruppe {group} aktualisiert.",
"You updated the member {member}.": "Sie haben {member} aktualisiert.", "You updated the member {member}.": "Du hast {member} aktualisiert.",
"You updated the post {post}.": "Sie haben den Beitrag {post} aktualisiert.", "You updated the post {post}.": "Du hast den Beitrag {post} aktualisiert.",
"You were demoted to an unknown role by {profile}.": "Sie wurden von {profile} in eine unbekannte Rolle zurückgestuft.", "You were demoted to an unknown role by {profile}.": "Sie wurden von {profile} in eine unbekannte Rolle zurückgestuft.",
"You were demoted to moderator by {profile}.": "Sie wurden von {profile} zum/zur Moderator/in zurückgestuft.", "You were demoted to moderator by {profile}.": "Sie wurden von {profile} zum/zur Moderator*in zurückgestuft.",
"You were demoted to simple member by {profile}.": "Sie wurden von {profile} zum einfachen Mitglied zurückgestuft.", "You were demoted to simple member by {profile}.": "Sie wurden von {profile} zum einfachen Mitglied zurückgestuft.",
"You were promoted to administrator by {profile}.": "Sie wurden von {profile} zum/zur Administrator/in befördert.", "You were promoted to administrator by {profile}.": "Sie wurden von {profile} zum/zur Administrator*in befördert.",
"You were promoted to an unknown role by {profile}.": "Sie wurden von {profile} zu einer unbekannten Rolle befördert.", "You were promoted to an unknown role by {profile}.": "Sie wurden von {profile} zu einer unbekannten Rolle befördert.",
"You were promoted to moderator by {profile}.": "Sie wurden von {profile} zum/zur Moderator/in befördert.", "You were promoted to moderator by {profile}.": "Sie wurden von {profile} zum/zur Moderator*in befördert.",
"You will be able to add an avatar and set other options in your account settings.": "In Ihren Kontoeinstellungen können Sie einen Avatar hinzufügen und weitere Optionen festlegen.", "You will be able to add an avatar and set other options in your account settings.": "In Ihren Kontoeinstellungen können Sie einen Avatar hinzufügen und weitere Optionen festlegen.",
"You will be redirected to the original instance": "Sie werden auf die ursprüngliche Instanz weitergeleitet", "You will be redirected to the original instance": "Sie werden auf die ursprüngliche Instanz weitergeleitet",
"You will find here all the events you have created or of which you are a participant, as well as events organized by groups you follow or are a member of.": "Hier finden Sie alle Veranstaltungen, die Sie erstellt haben, an denen Sie teilnehmen, und von Gruppen, denen Sie folgen oder bei denen Sie Mitglied sind.", "You will find here all the events you have created or of which you are a participant, as well as events organized by groups you follow or are a member of.": "Hier finden Sie alle Veranstaltungen, die Sie erstellt haben, an denen Sie teilnehmen, und von Gruppen, denen Sie folgen oder bei denen Sie Mitglied sind.",
@ -1166,17 +1167,17 @@
"You'll receive a confirmation email.": "Sie erhalten eine Bestätigungsmail.", "You'll receive a confirmation email.": "Sie erhalten eine Bestätigungsmail.",
"YouTube live": "YouTube live", "YouTube live": "YouTube live",
"YouTube replay": "YouTube-Wiedergabe", "YouTube replay": "YouTube-Wiedergabe",
"Your account has been successfully deleted": "Ihr Konto wurde erfolgreich gelöscht", "Your account has been successfully deleted": "Dein Account wurde erfolgreich gelöscht",
"Your account has been validated": "Ihr Account wurde validiert", "Your account has been validated": "Ihr Account wurde validiert",
"Your account is being validated": "Ihr Account wird validiert", "Your account is being validated": "Ihr Account wird validiert",
"Your account is nearly ready, {username}": "Ihr Account ist fast bereit, {username}", "Your account is nearly ready, {username}": "Ihr Account ist fast bereit, {username}",
"Your city or region and the radius will only be used to suggest you events nearby. The event radius will consider the administrative center of the area.": "Ihr Ort, Landkreis oder Bundesland wird nur genutzt um Ihnen Veranstaltungen in Ihrer Nähe anzuzeigen. Der Veranstaltungsradius berücksichtigt den Verwaltungssitz des Gebietes.", "Your city or region and the radius will only be used to suggest you events nearby. The event radius will consider the administrative center of the area.": "Ihr Ort, Landkreis oder Bundesland wird nur genutzt um Ihnen Veranstaltungen in Ihrer Nähe anzuzeigen. Der Veranstaltungsradius berücksichtigt den Verwaltungssitz des Gebietes.",
"Your current email is {email}. You use it to log in.": "Ihre aktuelle E-Mail-Adresse ist {email}. Diese kann zum Anmelden verwendet werden.", "Your current email is {email}. You use it to log in.": "Ihre aktuelle E-Mail-Adresse ist {email}. Diese kann zum Einloggen verwendet werden.",
"Your email": "Ihre E-Mail", "Your email": "Ihre E-Mail",
"Your email address was automatically set based on your {provider} account.": "Ihre E-Mail-Adresse wurde automatisch basierend auf Ihrem {provider}-Account gesetzt.", "Your email address was automatically set based on your {provider} account.": "Ihre E-Mail-Adresse wurde automatisch basierend auf Ihrem {provider}-Account gesetzt.",
"Your email has been changed": "Deine E-Mail-Adresse wurde geändert", "Your email has been changed": "Deine E-Mail-Adresse wurde geändert",
"Your email is being changed": "Ihre E-Mail wird geändert", "Your email is being changed": "Ihre E-Mail wird geändert",
"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.": "Ihre E-Mail-Adresse wird nur verwendet, um zu bestätigen, dass Sie eine reale Person sind und um Ihnen Neuigkeiten zu dieser Veranstaltung zuzusenden. Sie wird nicht an andere Instanzen oder an die Organisatoren weitergegeben.", "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.": "Ihre E-Mail-Adresse wird nur verwendet, um zu bestätigen, dass Sie eine reale Person sind und um Ihnen Neuigkeiten zu dieser Veranstaltung zuzusenden. Sie wird nicht an andere Instanzen oder an die Organisator*innen weitergegeben.",
"Your federated identity": "Ihre föderierte Identität", "Your federated identity": "Ihre föderierte Identität",
"Your membership was approved by {profile}.": "Ihre Mitgliedschaft wurde von {profile} genehmigt.", "Your membership was approved by {profile}.": "Ihre Mitgliedschaft wurde von {profile} genehmigt.",
"Your participation has been confirmed": "Ihre Teilnahme wurde bestätigt", "Your participation has been confirmed": "Ihre Teilnahme wurde bestätigt",
@ -1186,7 +1187,7 @@
"Your participation request is being validated": "Ihre Teilnahme wird überprüft", "Your participation request is being validated": "Ihre Teilnahme wird überprüft",
"Your participation status has been changed": "Ihr Teilnahmestatus hat sich geändert", "Your participation status has been changed": "Ihr Teilnahmestatus hat sich geändert",
"Your participation status is saved only on this device and will be deleted one month after the event's passed.": "Ihr Teilnahmestatus wird nur auf diesem Gerät gespeichert und wird einen Monat nach Ablauf der Veranstaltung wieder gelöscht.", "Your participation status is saved only on this device and will be deleted one month after the event's passed.": "Ihr Teilnahmestatus wird nur auf diesem Gerät gespeichert und wird einen Monat nach Ablauf der Veranstaltung wieder gelöscht.",
"Your participation still has to be approved by the organisers.": "Ihre Teilnahme muss noch von den Organisatoren genehmigt werden.", "Your participation still has to be approved by the organisers.": "Ihre Teilnahme muss noch von den Organisator*innen genehmigt werden.",
"Your participation will be validated once you click the confirmation link into the email, and after the organizer manually validates your participation.": "Ihre Teilnahme wird bestätigt, sobald Sie auf den Bestätigungslink in der E-Mail klicken und nachdem der Veranstalter Ihre Teilnahme manuell bestätigt hat.", "Your participation will be validated once you click the confirmation link into the email, and after the organizer manually validates your participation.": "Ihre Teilnahme wird bestätigt, sobald Sie auf den Bestätigungslink in der E-Mail klicken und nachdem der Veranstalter Ihre Teilnahme manuell bestätigt hat.",
"Your participation will be validated once you click the confirmation link into the email.": "Ihre Teilnahme wird bestätigt, sobald Sie den Bestätigungslink in der E-Mail anklicken.", "Your participation will be validated once you click the confirmation link into the email.": "Ihre Teilnahme wird bestätigt, sobald Sie den Bestätigungslink in der E-Mail anklicken.",
"Your position was not available.": "Deine Position ist nicht verfügbar.", "Your position was not available.": "Deine Position ist nicht verfügbar.",
@ -1194,7 +1195,7 @@
"Your timezone is currently set to {timezone}.": "Ihre Zeitzone ist aktuell {timezone}.", "Your timezone is currently set to {timezone}.": "Ihre Zeitzone ist aktuell {timezone}.",
"Your timezone was detected as {timezone}.": "Ihre Zeitzone wurde erkannt als {timezone}.", "Your timezone was detected as {timezone}.": "Ihre Zeitzone wurde erkannt als {timezone}.",
"Your timezone {timezone} isn't supported.": "Ihre Zeitzone {timezone} wird nicht unterstützt.", "Your timezone {timezone} isn't supported.": "Ihre Zeitzone {timezone} wird nicht unterstützt.",
"Your upcoming events": "Ihre bevorstehenden Veranstaltungen", "Your upcoming events": "Deine bevorstehenden Veranstaltungen",
"Zoom": "Zoom", "Zoom": "Zoom",
"Zoom in": "Vergrößern", "Zoom in": "Vergrößern",
"Zoom out": "Verkleinern", "Zoom out": "Verkleinern",
@ -1223,7 +1224,7 @@
"iCal Feed": "iCal-Feed", "iCal Feed": "iCal-Feed",
"instance rules": "Instanz-Regeln", "instance rules": "Instanz-Regeln",
"mobilizon-instance.tld": "mobilizon-instance.tld", "mobilizon-instance.tld": "mobilizon-instance.tld",
"more than 1360 contributors": "mehr als 1360 Spender:innen", "more than 1360 contributors": "mehr als 1360 Spender*innen",
"new@email.com": "new@email.com", "new@email.com": "new@email.com",
"profile@instance": "profil@instanz", "profile@instance": "profil@instanz",
"report #{report_number}": "Meldung #{report_number}", "report #{report_number}": "Meldung #{report_number}",
@ -1236,7 +1237,7 @@
"{count} km": "{count} km", "{count} km": "{count} km",
"{count} members": "Keine Mitglieder|Ein Mitglied|{count} Mitglieder", "{count} members": "Keine Mitglieder|Ein Mitglied|{count} Mitglieder",
"{count} members or followers": "Keine Mitglieder oder Follower|Ein Mitglied oder Follower|{count} Mitglieder oder Follower", "{count} members or followers": "Keine Mitglieder oder Follower|Ein Mitglied oder Follower|{count} Mitglieder oder Follower",
"{count} participants": "Noch keine Teilnehmer | Ein Teilnehmer | {count} Teilnehmer", "{count} participants": "Noch keine Teilnehmer*innen | Ein/e Teilnehmer*in | {count} Teilnehmer*innen",
"{count} requests waiting": "{count} Anfragen ausstehend", "{count} requests waiting": "{count} Anfragen ausstehend",
"{folder} - Resources": "{folder} - Ressourcen", "{folder} - Resources": "{folder} - Ressourcen",
"{group} activity timeline": "Auflistungen der Ereignisse in {group}", "{group} activity timeline": "Auflistungen der Ereignisse in {group}",
@ -1256,7 +1257,7 @@
"{moderator} deleted an event named \"{title}\"": "{moderator} hat eine Veranstaltung namens „{title}“ gelöscht", "{moderator} deleted an event named \"{title}\"": "{moderator} hat eine Veranstaltung namens „{title}“ gelöscht",
"{moderator} has deleted a comment from {author}": "{moderator} hat einen Kommentar von {author} gelöscht", "{moderator} has deleted a comment from {author}": "{moderator} hat einen Kommentar von {author} gelöscht",
"{moderator} has deleted a comment from {author} under the event {event}": "{moderator} hat einen Kommentar von {author} unter der Veranstaltung {event} gelöscht", "{moderator} has deleted a comment from {author} under the event {event}": "{moderator} hat einen Kommentar von {author} unter der Veranstaltung {event} gelöscht",
"{moderator} has deleted user {user}": "{moderator} hat den Nutzer {user} gelöscht", "{moderator} has deleted user {user}": "{moderator} hat die/den Nutzer*in {user} gelöscht",
"{moderator} has done an unknown action": "{moderator} hat eine unbekannte Handlung vorgenommen", "{moderator} has done an unknown action": "{moderator} hat eine unbekannte Handlung vorgenommen",
"{moderator} has unsuspended group {profile}": "{moderator} hat die Sperrung der Gruppe {profile} aufgehoben", "{moderator} has unsuspended group {profile}": "{moderator} hat die Sperrung der Gruppe {profile} aufgehoben",
"{moderator} has unsuspended profile {profile}": "{moderator} hat das Profil {profil} gesperrt", "{moderator} has unsuspended profile {profile}": "{moderator} hat das Profil {profil} gesperrt",
@ -1284,7 +1285,7 @@
"{profile} deleted the folder {resource}.": "{profile} hat den Ordner {resource} gelöscht.", "{profile} deleted the folder {resource}.": "{profile} hat den Ordner {resource} gelöscht.",
"{profile} deleted the resource {resource}.": "{profile} hat die Ressource {resource} gelöscht.", "{profile} deleted the resource {resource}.": "{profile} hat die Ressource {resource} gelöscht.",
"{profile} demoted {member} to an unknown role.": "{profile} hat {member} zu einer unbekannten Rolle zurückgestuft.", "{profile} demoted {member} to an unknown role.": "{profile} hat {member} zu einer unbekannten Rolle zurückgestuft.",
"{profile} demoted {member} to moderator.": "{profile} hat {member} zum/zur Moderator/in zurückgestuft.", "{profile} demoted {member} to moderator.": "{profile} hat {member} zum/zur Moderator*in zurückgestuft.",
"{profile} demoted {member} to simple member.": "{profile} hat {member} zu einem einfachen Mitglied zurückgestuft.", "{profile} demoted {member} to simple member.": "{profile} hat {member} zu einem einfachen Mitglied zurückgestuft.",
"{profile} excluded member {member}.": "{profile} hat {member} ausgeschlossen.", "{profile} excluded member {member}.": "{profile} hat {member} ausgeschlossen.",
"{profile} moved the folder {resource} into {new_path}.": "{profile} hat den Ordner {resource} nach {new_path} verschoben.", "{profile} moved the folder {resource} into {new_path}.": "{profile} hat den Ordner {resource} nach {new_path} verschoben.",
@ -1292,9 +1293,9 @@
"{profile} moved the resource {resource} into {new_path}.": "{profile} hat die Ressource {resource} nach {new_path} verschoben.", "{profile} moved the resource {resource} into {new_path}.": "{profile} hat die Ressource {resource} nach {new_path} verschoben.",
"{profile} moved the resource {resource} to the root folder.": "{profile} hat die Ressource {resource} in das Root-Verzeichnis verschoben.", "{profile} moved the resource {resource} to the root folder.": "{profile} hat die Ressource {resource} in das Root-Verzeichnis verschoben.",
"{profile} posted a comment on the event {event}.": "{profile} hat die Veranstaltung {event} kommentiert.", "{profile} posted a comment on the event {event}.": "{profile} hat die Veranstaltung {event} kommentiert.",
"{profile} promoted {member} to administrator.": "{profile} hat {member} zum/zur Administrator/in befördert.", "{profile} promoted {member} to administrator.": "{profile} hat {member} zum/zur Administrator*in befördert.",
"{profile} promoted {member} to an unknown role.": "{profile} hat {member} zu einer unbekannten Rolle befördert.", "{profile} promoted {member} to an unknown role.": "{profile} hat {member} zu einer unbekannten Rolle befördert.",
"{profile} promoted {member} to moderator.": "{profile} hat {member} zum/zur Moderator/in befördert.", "{profile} promoted {member} to moderator.": "{profile} hat {member} zum/zur Moderator*in befördert.",
"{profile} quit the group.": "{profile} hat die Gruppe verlassen.", "{profile} quit the group.": "{profile} hat die Gruppe verlassen.",
"{profile} rejected {member}'s membership request.": "{profile} hat den Mitgliedsantrag von {member} abgelehnt.", "{profile} rejected {member}'s membership request.": "{profile} hat den Mitgliedsantrag von {member} abgelehnt.",
"{profile} renamed the discussion from {old_discussion} to {discussion}.": "{profile} hat die Diskussion {old_discussion} in {discussion} umbenannt.", "{profile} renamed the discussion from {old_discussion} to {discussion}.": "{profile} hat die Diskussion {old_discussion} in {discussion} umbenannt.",

View file

@ -3,44 +3,44 @@
@import "~bulma/sass/utilities/derived-variables.sass"; @import "~bulma/sass/utilities/derived-variables.sass";
$bleuvert: #1e7d97; $bleuvert: #1e7d97;
$jaune: #ffd599; $jaune: #fff;
$violet: #424056; $violet: #3d8770;
/** /**
* Text body, paragraphs * Text body, paragraphs
*/ */
$violet-1: #3a384c; $violet-1: #000000;
$violet-2: #474467; $violet-2: #000000;
/** /**
* Titles, dark borders, buttons * Titles, dark borders, buttons
*/ */
$violet-3: #3c376e; $violet-3: #000000;
/** /**
* Borders * Borders
*/ */
$borders: #d7d6de; $borders: #b6abff;
$backgrounds: #ecebf2; $backgrounds: #745bff;
/** /**
* Text * Text
*/ */
$purple-1: #757199; $purple-1: #83ff72;
/** /**
* Background * Background
*/ */
$purple-2: #cdcaea; $purple-2: #ff9543;
$purple-3: #e6e4f4; $purple-3: #77e4ff;
$orange-2: #ed8d07; $orange-2: #b90097;
$orange-3: #d35204; $orange-3: #95d7ac;
$yellow-1: #ffd599; $yellow-1: #51ff00;
$yellow-2: #fff1de; $yellow-2: #00ff99;
$yellow-3: #fbd5cb; $yellow-3: #0099ff;
$yellow-4: #f7ba30; $yellow-4: #ffebc1;
$primary: $bleuvert; $primary: $bleuvert;
$primary-invert: findColorInvert($primary); $primary-invert: findColorInvert($primary);
@ -95,8 +95,8 @@ $colors: map-merge(
); );
// Navbar // Navbar
$navbar-background-color: $secondary; $navbar-background-color: #1d1d1d;
$navbar-item-color: $background-color; $navbar-item-color: $bleuvert;
$navbar-height: 4rem; $navbar-height: 4rem;
// Footer // Footer
@ -112,7 +112,7 @@ main > .container {
min-height: 70vh; min-height: 70vh;
} }
$title-color: #3c376e; $title-color: #ffffff;
$title-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, $title-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
serif; serif;
$title-weight: 700; $title-weight: 700;
@ -120,7 +120,7 @@ $title-size: 40px;
$title-sub-size: 45px; $title-sub-size: 45px;
$title-sup-size: 30px; $title-sup-size: 30px;
$subtitle-color: #3a384c; $subtitle-color: #000000;
$subtitle-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, $subtitle-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
serif; serif;
$subtitle-weight: 400; $subtitle-weight: 400;
@ -129,7 +129,7 @@ $subtitle-sub-size: 30px;
$subtitle-sup-size: 15px; $subtitle-sup-size: 15px;
.subtitle { .subtitle {
background: $secondary; background: #ffffff;
display: inline; display: inline;
padding: 3px 8px; padding: 3px 8px;
margin: 15px auto 30px; margin: 15px auto 30px;

View file

@ -0,0 +1,155 @@
@import "~bulma/sass/utilities/functions.sass";
@import "~bulma/sass/utilities/initial-variables.sass";
@import "~bulma/sass/utilities/derived-variables.sass";
$bleuvert: #1e7d97;
$jaune: #1d1d1d;
$violet: #424056;
/**
* Text body, paragraphs
*/
$violet-1: #3a384c;
$violet-2: #474467;
/**
* Titles, dark borders, buttons
*/
$violet-3: #3c376e;
/**
* Borders
*/
$borders: #d7d6de;
$backgrounds: #ecebf2;
/**
* Text
*/
$purple-1: #757199;
/**
* Background
*/
$purple-2: #cdcaea;
$purple-3: #e6e4f4;
$orange-2: #ffffff;
$orange-3: #ffffff;
$yellow-1: #b2f5ff;
$yellow-2: #ef9fff;
$yellow-3: #6ac3ff;
$yellow-4: #91cbff;
$primary: $bleuvert;
$primary-invert: findColorInvert($primary);
$secondary: $jaune;
$secondary-invert: findColorInvert($secondary);
$background-color: $violet-2;
$success: #0d8758;
$success-invert: findColorInvert($success);
$info: #36bcd4;
$info-invert: findColorInvert($info);
$danger: #cd2026;
$danger-invert: findColorInvert($danger);
$link: $primary;
$link-invert: $primary-invert;
$text: $violet-1;
$grey: #757575;
$colors: map-merge(
$colors,
(
"primary": (
$primary,
$primary-invert,
),
"secondary": (
$secondary,
$secondary-invert,
),
"success": (
$success,
$success-invert,
),
"info": (
$info,
$info-invert,
),
"danger": (
$danger,
$danger-invert,
),
"link": (
$link,
$link-invert,
),
"grey": (
$grey,
findColorInvert($grey),
),
)
);
// Navbar
$navbar-background-color: $secondary;
$navbar-item-color: $background-color;
$navbar-height: 4rem;
// Footer
$footer-padding: 3rem 1.5rem 1rem;
$footer-background-color: $background-color;
$body-background-color: #efeef4;
$fullhd-enabled: false;
$hero-body-padding-medium: 6rem 1.5rem;
main > .container {
background: $body-background-color;
min-height: 70vh;
}
$title-color: #3c376e;
$title-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
serif;
$title-weight: 700;
$title-size: 40px;
$title-sub-size: 45px;
$title-sup-size: 30px;
$subtitle-color: #3a384c;
$subtitle-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
serif;
$subtitle-weight: 400;
$subtitle-size: 32px;
$subtitle-sub-size: 30px;
$subtitle-sup-size: 15px;
.subtitle {
background: $secondary;
display: inline;
padding: 3px 8px;
margin: 15px auto 30px;
}
//$input-border-color: #dbdbdb;
$breadcrumb-item-color: $primary;
$checkbox-background-color: #fff;
$title-color: $violet-3;
:root {
--color-primary: 30 125 151;
--color-secondary: 255 213 153;
--color-violet-title: 66 64 86;
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: 30 125 151;
--color-secondary: 255 213 153;
--color-violet-title: 66 64 86;
}
}

View file

@ -24,7 +24,13 @@
@input="autoUpdateUsername($event)" @input="autoUpdateUsername($event)"
/> />
</b-field> </b-field>
<p style="color: red">
{{
$t(
"Attention! The username below is unique! If you want to create a group with the same name, change your username below now as you can't later on. (With your group name you'll be able to link to your group directly eg. fomobremen.info/@groupname)"
)
}}
</p>
<b-field <b-field
:label="$t('Username')" :label="$t('Username')"
:type="errors.preferred_username ? 'is-danger' : null" :type="errors.preferred_username ? 'is-danger' : null"

View file

@ -555,7 +555,7 @@ section {
nav.navbar { nav.navbar {
min-height: 2rem !important; min-height: 2rem !important;
background: lighten($secondary, 10%); background: lighten($primary, 10%);
.container { .container {
min-height: 2rem; min-height: 2rem;
@ -563,7 +563,7 @@ section {
.navbar-menu, .navbar-menu,
.navbar-end { .navbar-end {
display: flex !important; display: flex !important;
background: lighten($secondary, 10%); background: lighten($primary, 10%);
} }
.navbar-end { .navbar-end {

View file

@ -1310,8 +1310,8 @@ div.sidebar {
a { a {
display: inline-block; display: inline-block;
padding: 0.3rem; padding: 0.3rem;
background: $secondary; background: #fff;
color: #111; color: #1e7d97;
&:empty { &:empty {
display: none; display: none;

View file

@ -1210,19 +1210,19 @@ div.container {
margin-top: 15px; margin-top: 15px;
&.presentation { &.presentation {
border: 2px solid $purple-2; border: 2px solid $violet-1;
padding: 0 0 10px; padding: 0 0 10px;
position: relative; position: relative;
flex-direction: column; flex-direction: column;
h1 { h1 {
color: $purple-1; color: $violet-1;
font-size: 2rem; font-size: 2rem;
font-weight: 500; font-weight: 500;
} }
.button.is-outlined { .button.is-outlined {
border-color: $purple-2; border-color: $violet-1;
} }
& > *:not(img) { & > *:not(img) {

File diff suppressed because it is too large Load diff

View file

@ -51,18 +51,26 @@
> >
<section class="events-recent"> <section class="events-recent">
<h2 class="title"> <h2 class="title">
{{ $t("Last published events") }} {{ $t("Upcoming events") }}
</h2> </h2>
<p> <p>
<i18n tag="span" path="On {instance} and other federated instances"> <i18n tag="span" path="On {instance} and other federated instances">
<b slot="instance">{{ config.name }}</b> <b slot="instance">{{ config.name }}</b>
</i18n> </i18n>
</p> </p>
<div v-if="this.events.total > 0"> <div v-if="events.elements.length > 0">
<multi-card :events="events.elements.slice(0, 6)" /> <multi-card class="my-4" :events="events.elements" />
<div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="events.total"
v-model="featuredEventPage"
:per-page="EVENT_PAGE_LIMIT"
>
</b-pagination>
</div>
<span class="view-all"> <span class="view-all">
<router-link :to="{ name: RouteName.SEARCH }" <router-link :to="{ name: RouteName.SEARCH }"
>{{ $t("View everything") }} >></router-link >{{ $t("Filter") }} >></router-link
> >
</span> </span>
</div> </div>
@ -71,104 +79,6 @@
}}</b-message> }}</b-message>
</section> </section>
</div> </div>
<div id="picture" v-if="config && (!currentUser.id || !currentActor.id)">
<div class="picture-container">
<picture>
<source
media="(max-width: 799px)"
srcset="/img/pics/homepage-480w.webp"
type="image/webp"
/>
<source
media="(max-width: 799px)"
srcset="/img/pics/homepage-480w.jpg"
type="image/jpeg"
/>
<source
media="(max-width: 1024px)"
srcset="/img/pics/homepage-1024w.webp"
type="image/webp"
/>
<source
media="(max-width: 1024px)"
srcset="/img/pics/homepage-1024w.jpg"
type="image/jpeg"
/>
<source
media="(max-width: 1920px)"
srcset="/img/pics/homepage-1920w.webp"
type="image/webp"
/>
<source
media="(max-width: 1920px)"
srcset="/img/pics/homepage-1920w.jpg"
type="image/jpeg"
/>
<source
media="(min-width: 1921px)"
srcset="/img/pics/homepage.webp"
type="image/webp"
/>
<source
media="(min-width: 1921px)"
srcset="/img/pics/homepage.jpg"
type="image/jpeg"
/>
<img
src="/img/pics/homepage-1024w.jpg"
width="3840"
height="2719"
alt=""
loading="lazy"
/>
</picture>
</div>
<div class="container section">
<div class="columns">
<div class="column">
<h3 class="title">{{ $t("A practical tool") }}</h3>
<p
v-html="
$t(
'Mobilizon is a tool that helps you <b>find, create and organise events</b>.'
)
"
/>
</div>
<div class="column">
<h3 class="title">{{ $t("An ethical alternative") }}</h3>
<p
v-html="
$t(
'Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.'
)
"
/>
</div>
<div class="column">
<h3 class="title">{{ $t("A federated software") }}</h3>
<p
v-html="
$t(
'Mobilizon is not a giant platform, but a <b>multitude of interconnected Mobilizon websites</b>.'
)
"
/>
</div>
</div>
<div class="buttons">
<a
class="button is-primary is-large"
href="https://joinmobilizon.org"
>{{ $t("Learn more about Mobilizon") }}</a
>
</div>
</div>
</div>
<div class="container section" v-if="config && loggedUserSettings"> <div class="container section" v-if="config && loggedUserSettings">
<section v-if="currentActor.id && (welcomeBack || newRegisteredUser)"> <section v-if="currentActor.id && (welcomeBack || newRegisteredUser)">
<b-message type="is-info" v-if="welcomeBack">{{ <b-message type="is-info" v-if="welcomeBack">{{
@ -289,7 +199,7 @@
/> />
<section class="events-recent"> <section class="events-recent">
<h2 class="title"> <h2 class="title">
{{ $t("Last published events") }} {{ $t("Upcoming events") }}
</h2> </h2>
<p> <p>
<i18n tag="span" path="On {instance} and other federated instances"> <i18n tag="span" path="On {instance} and other federated instances">
@ -298,10 +208,10 @@
</p> </p>
<div v-if="events.total > 0"> <div v-if="events.total > 0">
<multi-card :events="events.elements.slice(0, 8)" /> <multi-card :events="events.elements" />
<span class="view-all"> <span class="view-all">
<router-link :to="{ name: RouteName.SEARCH }" <router-link :to="{ name: RouteName.SEARCH }"
>{{ $t("View everything") }} >></router-link >{{ $t("Filter") }} >></router-link
> >
</span> </span>
</div> </div>
@ -345,13 +255,16 @@ import { IConfig } from "../types/config.model";
import { IFollowedGroupEvent } from "../types/followedGroupEvent.model"; import { IFollowedGroupEvent } from "../types/followedGroupEvent.model";
import Subtitle from "../components/Utils/Subtitle.vue"; import Subtitle from "../components/Utils/Subtitle.vue";
const EVENT_PAGE_LIMIT = 99;
@Component({ @Component({
apollo: { apollo: {
events: { events: {
query: FETCH_EVENTS, query: FETCH_EVENTS,
variables: { variables: {
orderBy: EventSortField.INSERTED_AT, orderBy: EventSortField.BEGINS_ON,
direction: SortDirection.DESC, direction: SortDirection.ASC,
limit: EVENT_PAGE_LIMIT,
}, },
}, },
currentActor: { currentActor: {
@ -663,7 +576,6 @@ section.hero {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
opacity: 0.3;
z-index: -1; z-index: -1;
background: url("../../public/img/pics/homepage_background-1024w.png"); background: url("../../public/img/pics/homepage_background-1024w.png");
background-size: cover; background-size: cover;

View file

@ -0,0 +1,766 @@
<template>
<div id="homepage">
<b-loading :active.sync="$apollo.loading" />
<section
class="hero"
:class="{ webp: supportsWebPFormat }"
v-if="config && (!currentUser.id || !currentActor.id)"
>
<div class="hero-body">
<div class="container">
<h1 class="title">
{{ config.slogan || $t("Gather ⋅ Organize ⋅ Mobilize") }}
</h1>
<p
v-html="
$t('Join <b>{instance}</b>, a Mobilizon instance', {
instance: config.name,
})
"
/>
<p class="instance-description">{{ config.description }}</p>
<!-- We don't invite to find other instances yet -->
<!-- <p v-if="!config.registrationsOpen">
{{ $t("This instance isn't opened to registrations, but you can register on other instances.") }}
</p>-->
<div class="buttons">
<b-button
type="is-primary"
tag="router-link"
:to="{ name: RouteName.REGISTER }"
v-if="config.registrationsOpen"
>{{ $t("Create an account") }}</b-button
>
<!-- We don't invite to find other instances yet -->
<!-- <b-button v-else type="is-link" tag="a" href="https://joinmastodon.org">{{ $t('Find an instance') }}</b-button> -->
<b-button
type="is-text"
tag="router-link"
:to="{ name: RouteName.ABOUT }"
>
{{ $t("Learn more about {instance}", { instance: config.name }) }}
</b-button>
</div>
</div>
</div>
</section>
<div
id="recent_events"
class="container section"
v-if="config && (!currentUser.id || !currentActor.id)"
>
<section class="events-recent">
<h2 class="title">
{{ $t("Last published events") }}
</h2>
<p>
<i18n tag="span" path="On {instance} and other federated instances">
<b slot="instance">{{ config.name }}</b>
</i18n>
</p>
<div v-if="this.events.total > 0">
<multi-card :events="events.elements.slice(0, 6)" />
<span class="view-all">
<router-link :to="{ name: RouteName.SEARCH }"
>{{ $t("View everything") }} >></router-link
>
</span>
</div>
<b-message v-else type="is-danger">{{
$t("No events found")
}}</b-message>
</section>
</div>
<div id="picture" v-if="config && (!currentUser.id || !currentActor.id)">
<div class="picture-container">
<picture>
<source
media="(max-width: 799px)"
srcset="/img/pics/homepage-480w.webp"
type="image/webp"
/>
<source
media="(max-width: 799px)"
srcset="/img/pics/homepage-480w.jpg"
type="image/jpeg"
/>
<source
media="(max-width: 1024px)"
srcset="/img/pics/homepage-1024w.webp"
type="image/webp"
/>
<source
media="(max-width: 1024px)"
srcset="/img/pics/homepage-1024w.jpg"
type="image/jpeg"
/>
<source
media="(max-width: 1920px)"
srcset="/img/pics/homepage-1920w.webp"
type="image/webp"
/>
<source
media="(max-width: 1920px)"
srcset="/img/pics/homepage-1920w.jpg"
type="image/jpeg"
/>
<source
media="(min-width: 1921px)"
srcset="/img/pics/homepage.webp"
type="image/webp"
/>
<source
media="(min-width: 1921px)"
srcset="/img/pics/homepage.jpg"
type="image/jpeg"
/>
<img
src="/img/pics/homepage-1024w.jpg"
width="3840"
height="2719"
alt=""
loading="lazy"
/>
</picture>
</div>
<div class="container section">
<div class="columns">
<div class="column">
<h3 class="title">{{ $t("A practical tool") }}</h3>
<p
v-html="
$t(
'Mobilizon is a tool that helps you <b>find, create and organise events</b>.'
)
"
/>
</div>
<div class="column">
<h3 class="title">{{ $t("An ethical alternative") }}</h3>
<p
v-html="
$t(
'Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.'
)
"
/>
</div>
<div class="column">
<h3 class="title">{{ $t("A federated software") }}</h3>
<p
v-html="
$t(
'Mobilizon is not a giant platform, but a <b>multitude of interconnected Mobilizon websites</b>.'
)
"
/>
</div>
</div>
<div class="buttons">
<a
class="button is-primary is-large"
href="https://joinmobilizon.org"
>{{ $t("Learn more about Mobilizon") }}</a
>
</div>
</div>
</div>
<div class="container section" v-if="config && loggedUserSettings">
<section v-if="currentActor.id && (welcomeBack || newRegisteredUser)">
<b-message type="is-info" v-if="welcomeBack">{{
$t("Welcome back {username}!", {
username: currentActor.displayName(),
})
}}</b-message>
<b-message type="is-info" v-if="newRegisteredUser">{{
$t("Welcome to Mobilizon, {username}!", {
username: currentActor.displayName(),
})
}}</b-message>
</section>
<!-- Your upcoming events -->
<section v-if="canShowMyUpcomingEvents">
<h2 class="title">{{ $t("Your upcoming events") }}</h2>
<div v-for="row of goingToEvents" class="upcoming-events" :key="row[0]">
<p
class="date-component-container"
v-if="isInLessThanSevenDays(row[0])"
>
<span v-if="isToday(row[0])">{{
$tc("You have one event today.", row[1].length, {
count: row[1].length,
})
}}</span>
<span v-else-if="isTomorrow(row[0])">{{
$tc("You have one event tomorrow.", row[1].length, {
count: row[1].length,
})
}}</span>
<span v-else-if="isInLessThanSevenDays(row[0])">
{{
$tc("You have one event in {days} days.", row[1].length, {
count: row[1].length,
days: calculateDiffDays(row[0]),
})
}}
</span>
</p>
<div>
<event-participation-card
v-for="participation in thisWeek(row)"
@event-deleted="eventDeleted"
:key="participation[1].id"
:participation="participation[1]"
/>
</div>
</div>
<span class="view-all">
<router-link :to="{ name: RouteName.MY_EVENTS }"
>{{ $t("View everything") }} >></router-link
>
</span>
</section>
<hr
role="presentation"
class="home-separator"
v-if="canShowMyUpcomingEvents && canShowFollowedGroupEvents"
/>
<!-- Events from your followed groups -->
<section class="followActivity" v-if="canShowFollowedGroupEvents">
<h2 class="title">
{{ $t("Upcoming events from your groups") }}
</h2>
<p>{{ $t("That you follow or of which you are a member") }}</p>
<multi-card :events="filteredFollowedGroupsEvents" />
<span class="view-all">
<router-link
:to="{
name: RouteName.MY_EVENTS,
query: {
showUpcoming: 'true',
showDrafts: 'false',
showAttending: 'false',
showMyGroups: 'true',
},
}"
>{{ $t("View everything") }} >></router-link
>
</span>
</section>
<hr
role="presentation"
class="home-separator"
v-if="canShowFollowedGroupEvents && canShowCloseEvents"
/>
<!-- Events close to you -->
<section class="events-close" v-if="canShowCloseEvents">
<h2 class="title">
{{ $t("Events nearby") }}
</h2>
<p>
{{
$tc(
"Within {number} kilometers of {place}",
loggedUserSettings.location.range,
{
number: loggedUserSettings.location.range,
place: loggedUserSettings.location.name,
}
)
}}
<router-link
:to="{ name: RouteName.PREFERENCES }"
:title="$t('Change')"
>
<b-icon class="clickable" icon="pencil" size="is-small" />
</router-link>
</p>
<multi-card :events="closeEvents.elements.slice(0, 4)" />
</section>
<hr
role="presentation"
class="home-separator"
v-if="canShowMyUpcomingEvents || canShowCloseEvents"
/>
<section class="events-recent">
<h2 class="title">
{{ $t("Last published events") }}
</h2>
<p>
<i18n tag="span" path="On {instance} and other federated instances">
<b slot="instance">{{ config.name }}</b>
</i18n>
</p>
<div v-if="events.total > 0">
<multi-card :events="events.elements.slice(0, 8)" />
<span class="view-all">
<router-link :to="{ name: RouteName.SEARCH }"
>{{ $t("View everything") }} >></router-link
>
</span>
</div>
<b-message v-else type="is-danger"
>{{ $t("No events found") }}<br />
<div v-if="goingToEvents.size > 0">
<b-icon size="is-small" icon="information-outline" />
<small>{{
$t("The events you created are not shown here.")
}}</small>
</div>
</b-message>
</section>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { EventSortField, ParticipantRole, SortDirection } from "@/types/enums";
import { Paginate } from "@/types/paginate";
import { supportsWebPFormat } from "@/utils/support";
import { IParticipant, Participant } from "../types/participant.model";
import { FETCH_EVENTS } from "../graphql/event";
import EventParticipationCard from "../components/Event/EventParticipationCard.vue";
import MultiCard from "../components/Event/MultiCard.vue";
import { CURRENT_ACTOR_CLIENT } from "../graphql/actor";
import { IPerson, Person } from "../types/actor";
import {
ICurrentUser,
IUser,
IUserSettings,
} from "../types/current-user.model";
import { CURRENT_USER_CLIENT } from "../graphql/user";
import { CLOSE_CONTENT, HOME_USER_QUERIES } from "../graphql/home";
import RouteName from "../router/name";
import { IEvent } from "../types/event.model";
import DateComponent from "../components/Event/DateCalendarIcon.vue";
import { CONFIG } from "../graphql/config";
import { IConfig } from "../types/config.model";
import { IFollowedGroupEvent } from "../types/followedGroupEvent.model";
import Subtitle from "../components/Utils/Subtitle.vue";
@Component({
apollo: {
events: {
query: FETCH_EVENTS,
variables: {
orderBy: EventSortField.INSERTED_AT,
direction: SortDirection.DESC,
},
},
currentActor: {
query: CURRENT_ACTOR_CLIENT,
update: (data) => new Person(data.currentActor),
},
currentUser: CURRENT_USER_CLIENT,
config: CONFIG,
closeContent: {
query: CLOSE_CONTENT,
variables() {
return {
location: this.loggedUser?.settings?.location?.geohash,
radius: this.loggedUser?.settings?.location?.range,
};
},
update(data) {
this.closeEvents = data.searchEvents;
},
skip() {
return (
!this.currentUser?.isLoggedIn ||
!this.loggedUser?.settings?.location?.geohash ||
!this.loggedUser?.settings?.location?.range
);
},
},
userQueries: {
query: HOME_USER_QUERIES,
update(data) {
console.log("loggedUser", data.loggedUser);
this.loggedUser = data.loggedUser;
this.followedGroupEvents = data.loggedUser.followedGroupEvents;
this.currentUserParticipations =
data.loggedUser.participations.elements.map(
(participation: IParticipant) => new Participant(participation)
);
},
variables: {
afterDateTime: new Date().toISOString(),
},
skip() {
return !this.currentUser?.isLoggedIn;
},
},
},
components: {
Subtitle,
DateComponent,
EventParticipationCard,
MultiCard,
},
metaInfo() {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
title: this.instanceName,
titleTemplate: "%s | Mobilizon",
};
},
})
export default class Home extends Vue {
events: Paginate<IEvent> = {
elements: [],
total: 0,
};
locations = [];
city = { name: null };
country = { name: null };
currentUser!: ICurrentUser;
loggedUser: IUser | null = null;
currentActor!: IPerson;
config!: IConfig;
RouteName = RouteName;
currentUserParticipations: IParticipant[] = [];
supportsWebPFormat = supportsWebPFormat;
closeEvents: Paginate<IEvent> = { elements: [], total: 0 };
followedGroupEvents: Paginate<IFollowedGroupEvent> = {
elements: [],
total: 0,
};
// get displayed_name() {
// return this.loggedPerson && this.loggedPerson.name === null
// ? this.loggedPerson.preferredUsername
// : this.loggedPerson.name;
// }
get instanceName(): string | undefined {
if (!this.config) return undefined;
return this.config.name;
}
// eslint-disable-next-line class-methods-use-this
get welcomeBack(): boolean {
return window.localStorage.getItem("welcome-back") === "yes";
}
// eslint-disable-next-line class-methods-use-this
get newRegisteredUser(): boolean {
return window.localStorage.getItem("new-registered-user") === "yes";
}
thisWeek(
row: [string, Map<string, IParticipant>]
): Map<string, IParticipant> {
if (this.isInLessThanSevenDays(row[0])) {
return row[1];
}
return new Map();
}
// eslint-disable-next-line class-methods-use-this
mounted(): void {
if (window.localStorage.getItem("welcome-back")) {
window.localStorage.removeItem("welcome-back");
}
if (window.localStorage.getItem("new-registered-user")) {
window.localStorage.removeItem("new-registered-user");
}
}
// eslint-disable-next-line class-methods-use-this
isToday(date: Date): boolean {
return new Date(date).toDateString() === new Date().toDateString();
}
isTomorrow(date: string): boolean {
return this.isInDays(date, 1);
}
isInDays(date: string, nbDays: number): boolean {
return this.calculateDiffDays(date) === nbDays;
}
isBefore(date: string, nbDays: number): boolean {
return this.calculateDiffDays(date) < nbDays;
}
isAfter(date: string, nbDays: number): boolean {
return this.calculateDiffDays(date) >= nbDays;
}
isInLessThanSevenDays(date: string): boolean {
return this.isBefore(date, 7);
}
// eslint-disable-next-line class-methods-use-this
calculateDiffDays(date: string): number {
return Math.ceil(
(new Date(date).getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24
);
}
get thisWeekGoingToEvents(): IParticipant[] {
const res = this.currentUserParticipations.filter(
({ event, role }) =>
event.beginsOn != null &&
this.isAfter(event.beginsOn.toDateString(), 0) &&
this.isBefore(event.beginsOn.toDateString(), 7) &&
role !== ParticipantRole.REJECTED
);
res.sort(
(a: IParticipant, b: IParticipant) =>
a.event.beginsOn.getTime() - b.event.beginsOn.getTime()
);
return res;
}
get goingToEvents(): Map<string, Map<string, IParticipant>> {
return this.thisWeekGoingToEvents.reduce(
(
acc: Map<string, Map<string, IParticipant>>,
participation: IParticipant
) => {
const day = new Date(participation.event.beginsOn).toDateString();
const participations: Map<string, IParticipant> =
acc.get(day) || new Map();
participations.set(
`${participation.event.uuid}${participation.actor.id}`,
participation
);
acc.set(day, participations);
return acc;
},
new Map()
);
}
eventDeleted(eventid: string): void {
this.currentUserParticipations = this.currentUserParticipations.filter(
(participation) => participation.event.id !== eventid
);
}
viewEvent(event: IEvent): void {
this.$router.push({ name: RouteName.EVENT, params: { uuid: event.uuid } });
}
@Watch("loggedUser")
detectEmptyUserSettings(loggedUser: IUser): void {
console.debug("Try to detect empty user settings", loggedUser);
if (loggedUser?.id && loggedUser?.settings === null) {
console.debug("No user settings, pushing to onboarding assistant");
this.$router.push({
name: RouteName.WELCOME_SCREEN,
params: { step: "1" },
});
}
}
get loggedUserSettings(): IUserSettings | undefined {
return this.loggedUser?.settings;
}
get canShowMyUpcomingEvents(): boolean {
return this.currentActor.id != undefined && this.goingToEvents.size > 0;
}
get canShowCloseEvents(): boolean {
return (
this.loggedUser?.settings?.location != undefined &&
this.closeEvents.total > 0
);
}
get canShowFollowedGroupEvents(): boolean {
return this.filteredFollowedGroupsEvents.length > 0;
}
get filteredFollowedGroupsEvents(): IEvent[] {
return this.followedGroupEvents.elements
.map(({ event }: { event: IEvent }) => event)
.filter(
({ id }) =>
!this.thisWeekGoingToEvents
.map(({ event: { id: event_id } }) => event_id)
.includes(id)
)
.slice(0, 4);
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@import "~bulma/sass/utilities/mixins.sass";
main > div > .container {
background: $white;
padding: 1rem 0.5rem 3rem;
}
.search-autocomplete {
border: 1px solid #dbdbdb;
color: rgba(0, 0, 0, 0.87);
}
.events-recent {
& > h3 {
@include padding-left(0.75rem);
}
.columns {
margin: 1rem auto 0;
}
}
.date-component-container {
display: flex;
align-items: center;
margin: 0.5rem auto 1rem;
h3.subtitle {
@include margin-left(7px);
}
}
span.view-all {
display: block;
margin-top: 1rem;
text-align: right;
a {
text-decoration: underline;
}
}
section.hero {
position: relative;
z-index: 1;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.3;
z-index: -1;
background: url("../../public/img/pics/homepage_background-1024w.png");
background-size: cover;
}
&.webp::before {
background-image: url("../../public/img/pics/homepage_background-1024w.webp");
}
& > .hero-body {
padding: 1rem 1.5rem 3rem;
}
.title {
color: $background-color;
}
.column figure.image img {
max-width: 400px;
}
.instance-description {
margin-bottom: 1rem;
}
}
#recent_events {
padding: 0;
min-height: 20vh;
z-index: 10;
.title {
margin: 20px auto 0;
}
.columns {
margin: 0 auto;
}
}
#picture {
.picture-container {
position: relative;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
& > img {
object-fit: cover;
max-height: 80vh;
display: block;
margin: auto;
width: 100%;
}
}
.container.section {
background: $white;
@include tablet {
margin-top: -4rem;
}
z-index: 10;
.title {
margin: 0 0 10px;
font-size: 30px;
}
.buttons {
justify-content: center;
margin-top: 2rem;
}
}
}
#homepage {
background: $white;
}
.home-separator {
background-color: $orange-2;
}
.clickable {
cursor: pointer;
}
.title {
font-size: 27px;
&:not(:last-child) {
margin-bottom: 0.5rem;
}
}
</style>

View file

@ -9,7 +9,7 @@
<h1 class="title" v-else> <h1 class="title" v-else>
{{ $t("Add a new post") }} {{ $t("Add a new post") }}
</h1> </h1>
<subtitle>{{ $t("General information") }}</subtitle> <subtitle color="#fff">{{ $t("General information") }}</subtitle>
<picture-upload <picture-upload
v-model="pictureFile" v-model="pictureFile"
:textFallback="$t('Headline picture')" :textFallback="$t('Headline picture')"
@ -373,7 +373,7 @@ export default class EditPost extends mixins(GroupMixin, PostMixin) {
form { form {
nav.navbar { nav.navbar {
min-height: 2rem !important; min-height: 2rem !important;
background: lighten($secondary, 10%); background: lighten($primary, 10%);
.container { .container {
min-height: 2rem; min-height: 2rem;
@ -381,7 +381,7 @@ form {
.navbar-menu, .navbar-menu,
.navbar-end { .navbar-end {
display: flex !important; display: flex !important;
background: lighten($secondary, 10%); background: lighten($primary, 10%);
flex-wrap: wrap; flex-wrap: wrap;
} }

View file

@ -0,0 +1,406 @@
<template>
<div>
<form @submit.prevent="publish(false)" v-if="isCurrentActorAGroupModerator">
<div class="container section">
<breadcrumbs-nav v-if="actualGroup" :links="breadcrumbLinks" />
<h1 class="title" v-if="isUpdate === true">
{{ $t("Edit post") }}
</h1>
<h1 class="title" v-else>
{{ $t("Add a new post") }}
</h1>
<subtitle>{{ $t("General information") }}</subtitle>
<picture-upload
v-model="pictureFile"
:textFallback="$t('Headline picture')"
:defaultImage="editablePost.picture"
/>
<b-field
:label="$t('Title')"
label-for="post-title"
:type="errors.title ? 'is-danger' : null"
:message="errors.title"
>
<b-input
size="is-large"
aria-required="true"
required
v-model="editablePost.title"
id="post-title"
dir="auto"
/>
</b-field>
<tag-input v-model="editablePost.tags" />
<div class="field">
<label class="label">{{ $t("Post") }}</label>
<p v-if="errors.body" class="help is-danger">{{ errors.body }}</p>
<editor v-model="editablePost.body" :aria-label="$t('Post body')" />
</div>
<subtitle>{{ $t("Who can view this post") }}</subtitle>
<fieldset>
<legend>
{{
$t(
"When the post is private, you'll need to share the link around."
)
}}
</legend>
<div class="field">
<b-radio
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.PUBLIC"
>{{ $t("Visible everywhere on the web") }}</b-radio
>
</div>
<div class="field">
<b-radio
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.UNLISTED"
>{{ $t("Only accessible through link") }}</b-radio
>
</div>
<div class="field">
<b-radio
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.PRIVATE"
>{{ $t("Only accessible to members of the group") }}</b-radio
>
</div>
</fieldset>
</div>
<nav class="navbar">
<div class="container">
<div class="navbar-menu">
<div class="navbar-end">
<span class="navbar-item">
<b-button type="is-text" @click="$router.go(-1)">{{
$t("Cancel")
}}</b-button>
</span>
<span class="navbar-item" v-if="this.isUpdate">
<b-button
type="is-danger is-outlined"
@click="openDeletePostModal"
>{{ $t("Delete post") }}</b-button
>
</span>
<!-- If an post has been published we can't make it draft anymore -->
<span class="navbar-item" v-if="post.draft === true">
<b-button type="is-primary" outlined @click="publish(true)">{{
$t("Save draft")
}}</b-button>
</span>
<span class="navbar-item">
<b-button type="is-primary" native-type="submit">
<span v-if="isUpdate === false || post.draft === true">{{
$t("Publish")
}}</span>
<span v-else>{{ $t("Update post") }}</span>
</b-button>
</span>
</div>
</div>
</div>
</nav>
</form>
<b-loading
v-else-if="$apollo.loading"
:is-full-page="false"
:active.sync="$apollo.loading"
:can-cancel="false"
></b-loading>
<div class="container section" v-else>
<b-message type="is-danger">
{{ $t("Only group moderators can create, edit and delete posts.") }}
</b-message>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Watch } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import {
buildFileFromIMedia,
buildFileVariable,
readFileAsync,
} from "@/utils/image";
import GroupMixin from "@/mixins/group";
import { PostVisibility } from "@/types/enums";
import { CONFIG } from "../../graphql/config";
import { CREATE_POST, UPDATE_POST } from "../../graphql/post";
import { IPost } from "../../types/post.model";
import Editor from "../../components/Editor.vue";
import { displayName, IActor, usernameWithDomain } from "../../types/actor";
import TagInput from "../../components/Event/TagInput.vue";
import RouteName from "../../router/name";
import Subtitle from "../../components/Utils/Subtitle.vue";
import PictureUpload from "../../components/PictureUpload.vue";
import { PERSON_STATUS_GROUP } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
import PostMixin from "../../mixins/post";
@Component({
apollo: {
config: CONFIG,
group: {
query: FETCH_GROUP,
variables() {
return {
name: this.preferredUsername,
};
},
skip() {
return !this.preferredUsername;
},
},
person: {
query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
id: this.currentActor.id,
group: usernameWithDomain(this.actualGroup),
};
},
skip() {
return !this.currentActor?.id || !this.actualGroup?.preferredUsername;
},
},
},
components: {
Editor,
TagInput,
Subtitle,
PictureUpload,
},
metaInfo() {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
title: this.isUpdate
? (this.$t("Edit post") as string)
: (this.$t("Add a new post") as string),
// all titles will be injected into this template
titleTemplate: "%s | Mobilizon",
};
},
})
export default class EditPost extends mixins(GroupMixin, PostMixin) {
@Prop({ required: false, type: String }) slug: undefined | string;
@Prop({ required: false, type: String }) preferredUsername!: string;
@Prop({ type: Boolean, default: false }) isUpdate!: boolean;
post: IPost = {
title: "",
body: "",
local: true,
draft: true,
visibility: PostVisibility.PUBLIC,
tags: [],
};
PostVisibility = PostVisibility;
pictureFile: File | null = null;
errors: Record<string, unknown> = {};
RouteName = RouteName;
editablePost: IPost = this.post;
usernameWithDomain = usernameWithDomain;
async mounted(): Promise<void> {
this.pictureFile = await buildFileFromIMedia(this.post.picture);
}
@Watch("post")
async updatePostPicture(oldPost: IPost, newPost: IPost): Promise<void> {
if (oldPost.picture !== newPost.picture) {
this.pictureFile = await buildFileFromIMedia(this.post.picture);
}
this.editablePost = { ...this.post };
}
// eslint-disable-next-line consistent-return
async publish(draft: boolean): Promise<void> {
this.errors = {};
if (this.isUpdate) {
const { data } = await this.$apollo.mutate({
mutation: UPDATE_POST,
variables: {
id: this.editablePost.id,
title: this.editablePost.title,
body: this.editablePost.body,
tags: (this.editablePost.tags || []).map(({ title }) => title),
visibility: this.editablePost.visibility,
draft,
...(await this.buildPicture()),
},
});
if (data && data.updatePost) {
this.$router.push({
name: RouteName.POST,
params: { slug: data.updatePost.slug },
});
}
} else {
try {
const { data } = await this.$apollo.mutate({
mutation: CREATE_POST,
variables: {
...this.editablePost,
...(await this.buildPicture()),
tags: (this.editablePost.tags || []).map(({ title }) => title),
attributedToId: this.actualGroup.id,
draft,
},
});
if (data && data.createPost) {
this.$router.push({
name: RouteName.POST,
params: { slug: data.createPost.slug },
});
}
} catch (error: any) {
console.error(error);
this.errors = error.graphQLErrors.reduce(
(acc: { [key: string]: any }, localError: any) => {
acc[localError.field] = EditPost.transformMessage(
localError.message
);
return acc;
},
{}
);
}
}
}
static transformMessage(message: string[] | string): string | undefined {
if (Array.isArray(message) && message.length > 0) {
return message[0];
}
if (typeof message === "string") {
return message;
}
return undefined;
}
async buildPicture(): Promise<Record<string, unknown>> {
let obj: { picture?: any } = {};
if (this.pictureFile) {
const pictureObj = buildFileVariable(this.pictureFile, "picture");
obj = { ...obj, ...pictureObj };
}
try {
if (this.editablePost.picture && this.pictureFile) {
const oldPictureFile = (await buildFileFromIMedia(
this.editablePost.picture
)) as File;
const oldPictureFileContent = await readFileAsync(oldPictureFile);
const newPictureFileContent = await readFileAsync(
this.pictureFile as File
);
if (oldPictureFileContent === newPictureFileContent) {
obj.picture = { mediaId: this.editablePost.picture.id };
}
}
} catch (e: any) {
console.error(e);
}
return obj;
}
get actualGroup(): IActor {
if (!this.group?.id) {
return this.post.attributedTo as IActor;
}
return this.group;
}
get breadcrumbLinks() {
const links = [
{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(this.actualGroup),
},
text: displayName(this.actualGroup),
},
{
name: RouteName.POSTS,
params: {
preferredUsername: usernameWithDomain(this.actualGroup),
},
text: this.$t("Posts"),
},
];
if (this.preferredUsername) {
links.push({
text: this.$t("New post") as string,
name: RouteName.POST_EDIT,
params: { preferredUsername: usernameWithDomain(this.actualGroup) },
});
} else {
links.push({
text: this.$t("Edit post") as string,
name: RouteName.POST_EDIT,
params: { preferredUsername: usernameWithDomain(this.actualGroup) },
});
}
return links;
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.container.section {
background: $white;
}
form {
nav.navbar {
min-height: 2rem !important;
background: lighten($secondary, 10%);
.container {
min-height: 2rem;
.navbar-menu,
.navbar-end {
display: flex !important;
background: lighten($secondary, 10%);
flex-wrap: wrap;
}
.navbar-end {
justify-content: flex-end;
@include margin-left(auto);
}
}
}
h2 {
margin: 15px 0 7.5px;
}
legend {
margin-bottom: 0.75rem;
}
}
.breadcrumb li.is-active > span {
padding: 0 0.75em;
}
</style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="section container"> <div class="section container">
<h1 class="title">{{ $t("Explore") }}</h1> <!-- <h1 class="title">{{ $t("Explore") }}</h1> -->
<section v-if="tag"> <section v-if="tag">
<i18n path="Events tagged with {tag}"> <i18n path="Events tagged with {tag}">
<b-tag slot="tag" type="is-light">{{ $t("#{tag}", { tag }) }}</b-tag> <b-tag slot="tag" type="is-light">{{ $t("#{tag}", { tag }) }}</b-tag>
@ -125,7 +125,7 @@
v-if="!canSearchEvents && !canSearchGroups" v-if="!canSearchEvents && !canSearchGroups"
> >
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<h2 class="title">{{ $t("Featured events") }}</h2> <!-- <h2 class="title">{{ $t("Featured events") }}</h2> -->
<div v-if="events.elements.length > 0"> <div v-if="events.elements.length > 0">
<multi-card class="my-4" :events="events.elements" /> <multi-card class="my-4" :events="events.elements" />
<div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT"> <div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT">
@ -255,9 +255,9 @@ interface ISearchTimeOption {
end?: Date | null; end?: Date | null;
} }
const EVENT_PAGE_LIMIT = 12; const EVENT_PAGE_LIMIT = 99;
const GROUP_PAGE_LIMIT = 12; const GROUP_PAGE_LIMIT = 99;
const DEFAULT_RADIUS = 25; // value to set if radius is null but location set const DEFAULT_RADIUS = 25; // value to set if radius is null but location set

View file

@ -0,0 +1,756 @@
<template>
<div class="section container">
<h1 class="title">{{ $t("Explore") }}</h1>
<section v-if="tag">
<i18n path="Events tagged with {tag}">
<b-tag slot="tag" type="is-light">{{ $t("#{tag}", { tag }) }}</b-tag>
</i18n>
</section>
<section class="hero is-light" v-else>
<div class="hero-body">
<form @submit.prevent="submit()">
<b-field
class="searchQuery"
:label="$t('Key words')"
label-for="search"
>
<b-input
icon="magnify"
type="search"
id="search"
ref="autocompleteSearchInput"
:value="search"
@input="debouncedUpdateSearchQuery"
dir="auto"
:placeholder="
$t('For instance: London, Taekwondo, Architecture…')
"
/>
</b-field>
<full-address-auto-complete
class="searchLocation"
:label="$t('Location')"
v-model="location"
id="location"
ref="aac"
:placeholder="$t('For instance: London')"
@input="locchange"
:hideMap="true"
:hideSelected="true"
/>
<b-field
:label="$t('Radius')"
label-for="radius"
class="searchRadius"
>
<b-select expanded v-model="radius" id="radius">
<option
v-for="(radiusOption, index) in radiusOptions"
:key="index"
:value="radiusOption"
>
{{ radiusString(radiusOption) }}
</option>
</b-select>
</b-field>
<b-field :label="$t('Date')" label-for="date" class="searchDate">
<b-select
expanded
v-model="when"
id="date"
:disabled="activeTab !== 0"
>
<option
v-for="(option, index) in dateOptions"
:key="index"
:value="index"
>
{{ option.label }}
</option>
</b-select>
</b-field>
<b-field
expanded
:label="$t('Type')"
label-for="type"
class="searchType"
>
<b-select
expanded
v-model="type"
id="type"
:disabled="activeTab !== 0"
>
<option :value="null">
{{ $t("Any type") }}
</option>
<option :value="'ONLINE'">
{{ $t("Online") }}
</option>
<option :value="'IN_PERSON'">
{{ $t("In person") }}
</option>
</b-select>
</b-field>
<b-field
v-if="config"
expanded
:label="$t('Category')"
label-for="category"
class="searchCategory"
>
<b-select
expanded
v-model="eventCategory"
id="category"
:disabled="activeTab !== 0"
>
<option :value="null">
{{ $t("Any category") }}
</option>
<option
:value="category.id"
v-for="category in config.eventCategories"
:key="category.id"
>
{{ category.label }}
</option>
</b-select>
</b-field>
</form>
</div>
</section>
<section
class="events-featured"
v-if="!canSearchEvents && !canSearchGroups"
>
<b-loading :active.sync="$apollo.loading"></b-loading>
<h2 class="title">{{ $t("Featured events") }}</h2>
<div v-if="events.elements.length > 0">
<multi-card class="my-4" :events="events.elements" />
<div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="events.total"
v-model="featuredEventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message
v-else-if="events.elements.length === 0 && $apollo.loading === false"
type="is-danger"
>{{ $t("No events found") }}</b-message
>
</section>
<b-tabs v-else v-model="activeTab" type="is-boxed" class="mt-3 searchTabs">
<b-loading :active.sync="$apollo.loading"></b-loading>
<b-tab-item>
<template slot="header">
<b-icon icon="calendar"></b-icon>
<span>
{{ $t("Events") }}
<b-tag rounded>{{ searchEvents.total }}</b-tag>
</span>
</template>
<div v-if="searchEvents.total > 0">
<multi-card class="my-4" :events="searchEvents.elements" />
<div class="pagination" v-if="searchEvents.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="searchEvents.total"
v-model="eventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
<p>{{ $t("No events found") }}</p>
<p v-if="searchIsUrl && !currentUser.id">
{{
$t(
"Only registered users may fetch remote events from their URL."
)
}}
</p>
</b-message>
</b-tab-item>
<b-tab-item v-if="!tag">
<template slot="header">
<b-icon icon="account-multiple"></b-icon>
<span>
{{ $t("Groups") }} <b-tag rounded>{{ searchGroups.total }}</b-tag>
</span>
</template>
<b-message v-if="config && !config.features.groups" type="is-danger">
{{ $t("Groups are not enabled on this instance.") }}
</b-message>
<div v-else-if="searchGroups.total > 0">
<multi-group-card class="my-4" :groups="searchGroups.elements" />
<div class="pagination">
<b-pagination
:total="searchGroups.total"
v-model="groupPage"
:per-page="GROUP_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
{{ $t("No groups found") }}
</b-message>
</b-tab-item>
</b-tabs>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import ngeohash, { GeographicPoint } from "ngeohash";
import {
endOfToday,
addDays,
startOfDay,
endOfDay,
endOfWeek,
addWeeks,
startOfWeek,
endOfMonth,
addMonths,
startOfMonth,
eachWeekendOfInterval,
} from "date-fns";
import { SearchTabs } from "@/types/enums";
import MultiCard from "../components/Event/MultiCard.vue";
import { FETCH_EVENTS } from "../graphql/event";
import { EventType, IEvent } from "../types/event.model";
import RouteName from "../router/name";
import { IAddress, Address } from "../types/address.model";
import FullAddressAutoComplete from "../components/Event/FullAddressAutoComplete.vue";
import { SEARCH_EVENTS_AND_GROUPS } from "../graphql/search";
import { Paginate } from "../types/paginate";
import { IGroup } from "../types/actor";
import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
import { CONFIG } from "../graphql/config";
import { REVERSE_GEOCODE } from "../graphql/address";
import debounce from "lodash/debounce";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
interface ISearchTimeOption {
label: string;
start?: Date | null;
end?: Date | null;
}
const EVENT_PAGE_LIMIT = 12;
const GROUP_PAGE_LIMIT = 12;
const DEFAULT_RADIUS = 25; // value to set if radius is null but location set
const DEFAULT_ZOOM = 11; // zoom on a city
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
@Component({
components: {
MultiCard,
FullAddressAutoComplete,
MultiGroupCard,
},
apollo: {
config: CONFIG,
events: {
query: FETCH_EVENTS,
variables() {
return {
page: this.featuredEventPage,
limit: EVENT_PAGE_LIMIT,
};
},
},
searchElements: {
query: SEARCH_EVENTS_AND_GROUPS,
fetchPolicy: "cache-and-network",
variables() {
return {
term: this.search,
tags: this.tag,
location: this.geohash,
beginsOn: this.start,
endsOn: this.end,
radius: this.radius,
eventPage: this.eventPage,
groupPage: this.groupPage,
limit: EVENT_PAGE_LIMIT,
type: this.type,
};
},
update(data) {
this.searchEvents = data.searchEvents;
this.searchGroups = data.searchGroups;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.$refs.autocompleteSearchInput?.focus();
},
},
currentUser: CURRENT_USER_CLIENT,
},
metaInfo() {
return {
title: this.$t("Explore events") as string,
titleTemplate: "%s | Mobilizon",
};
},
})
export default class Search extends Vue {
@Prop({ type: String, required: false }) tag!: string;
events: Paginate<IEvent> = {
total: 0,
elements: [],
};
searchEvents: Paginate<IEvent> = {
total: 0,
elements: [],
};
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
location: IAddress = new Address();
currentUser!: ICurrentUser;
dateOptions: Record<string, ISearchTimeOption> = {
past: {
label: this.$t("In the past") as string,
start: null,
end: new Date(),
},
today: {
label: this.$t("Today") as string,
start: new Date(),
end: endOfToday(),
},
tomorrow: {
label: this.$t("Tomorrow") as string,
start: startOfDay(addDays(new Date(), 1)),
end: endOfDay(addDays(new Date(), 1)),
},
weekend: {
label: this.$t("This weekend") as string,
start: this.weekend.start,
end: this.weekend.end,
},
week: {
label: this.$t("This week") as string,
start: new Date(),
end: endOfWeek(new Date(), { locale: this.$dateFnsLocale }),
},
next_week: {
label: this.$t("Next week") as string,
start: startOfWeek(addWeeks(new Date(), 1), {
locale: this.$dateFnsLocale,
}),
end: endOfWeek(addWeeks(new Date(), 1), { locale: this.$dateFnsLocale }),
},
month: {
label: this.$t("This month") as string,
start: new Date(),
end: endOfMonth(new Date()),
},
next_month: {
label: this.$t("Next month") as string,
start: startOfMonth(addMonths(new Date(), 1)),
end: endOfMonth(addMonths(new Date(), 1)),
},
any: {
label: this.$t("Any day") as string,
start: undefined,
end: undefined,
},
};
EVENT_PAGE_LIMIT = EVENT_PAGE_LIMIT;
GROUP_PAGE_LIMIT = GROUP_PAGE_LIMIT;
$refs!: {
aac: FullAddressAutoComplete;
autocompleteSearchInput: any;
};
data(): Record<string, unknown> {
return {
debouncedUpdateSearchQuery: debounce(this.updateSearchQuery, 500),
};
}
mounted(): void {
this.prepareLocation(this.$route.query.geohash as string);
}
radiusString = (radius: number | null): string => {
if (radius) {
return this.$tc("{nb} km", radius, { nb: radius }) as string;
}
return this.$t("any distance") as string;
};
radiusOptions: (number | null)[] = [1, 5, 10, 25, 50, 100, 150, null];
submit(): void {
this.$apollo.queries.searchEvents.refetch();
}
updateSearchQuery(searchQuery: string): void {
this.search = searchQuery;
}
get featuredEventPage(): number {
return parseInt(this.$route.query.featuredEventPage as string, 10) || 1;
}
set featuredEventPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, featuredEventPage: page.toString() },
});
}
get eventPage(): number {
return parseInt(this.$route.query.eventPage as string, 10) || 1;
}
set eventPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, eventPage: page.toString() },
});
}
get groupPage(): number {
return parseInt(this.$route.query.groupPage as string, 10) || 1;
}
set groupPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, groupPage: page.toString() },
});
}
get search(): string | undefined {
return this.$route.query.term as string;
}
set search(term: string | undefined) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, term },
});
}
get activeTab(): SearchTabs {
return (
parseInt(this.$route.query.searchType as string, 10) || SearchTabs.EVENTS
);
}
set activeTab(value: SearchTabs) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, searchType: value.toString() },
});
}
get geohash(): string | undefined {
if (this.location?.geom) {
const [lon, lat] = this.location.geom.split(";");
return ngeohash.encode(lat, lon, GEOHASH_DEPTH);
}
return undefined;
}
set geohash(value: string | undefined) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, geohash: value },
});
}
get radius(): number | null {
if (this.$route.query.radius === "any") {
return null;
}
return parseInt(this.$route.query.radius as string, 10) || null;
}
set radius(value: number | null) {
const radius = value === null ? "any" : value.toString();
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, radius },
});
}
get when(): string {
return (this.$route.query.when as string) || "any";
}
set when(value: string) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, when: value },
});
}
get type(): EventType {
return this.$route.query.type as EventType;
}
set type(type: EventType) {
const query = { ...this.$route.query, type };
if (type == null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete query.type;
}
this.$router.replace({
name: RouteName.SEARCH,
query,
});
}
get eventCategory(): string | null {
return (this.$route.query.eventCategory as string) || null;
}
set eventCategory(eventCategory: string | null) {
let query = { ...this.$route.query, eventCategory };
if (query.eventCategory === null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete query.eventCategory;
}
this.$router.replace({
name: RouteName.SEARCH,
query,
});
}
get weekend(): { start: Date; end: Date } {
const now = new Date();
const endOfWeekDate = endOfWeek(now, { locale: this.$dateFnsLocale });
const startOfWeekDate = startOfWeek(now, { locale: this.$dateFnsLocale });
const [start, end] = eachWeekendOfInterval({
start: startOfWeekDate,
end: endOfWeekDate,
});
return { start: startOfDay(start), end: endOfDay(end) };
}
private prepareLocation(value: string | undefined): void {
if (value !== undefined) {
// decode
const latlon = ngeohash.decode(value);
// set location
this.reverseGeoCode(latlon, DEFAULT_ZOOM);
}
}
async reverseGeoCode(e: GeographicPoint, zoom: number): Promise<void> {
const result = await this.$apollo.query({
query: REVERSE_GEOCODE,
variables: {
latitude: e.latitude,
longitude: e.longitude,
zoom,
locale: this.$i18n.locale,
},
});
const addressData = result.data.reverseGeocode.map(
(address: IAddress) => new Address(address)
);
if (addressData.length > 0) {
this.location = addressData[0];
}
}
locchange = (e: IAddress): void => {
if (this.radius === undefined || this.radius === null) {
this.radius = DEFAULT_RADIUS;
}
if (e?.geom) {
const [lon, lat] = e.geom.split(";");
this.geohash = ngeohash.encode(lat, lon, GEOHASH_DEPTH);
} else {
this.geohash = undefined;
}
};
get start(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].start;
}
return undefined;
}
get end(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].end;
}
return undefined;
}
get canSearchGroups(): boolean {
return (
this.stringExists(this.search) ||
(this.stringExists(this.geohash) && this.valueExists(this.radius))
);
}
get canSearchEvents(): boolean {
return (
this.stringExists(this.search) ||
this.stringExists(this.tag) ||
this.stringExists(this.type) ||
(this.stringExists(this.geohash) && this.valueExists(this.radius)) ||
this.valueExists(this.end)
);
}
// helper functions for skip
private valueExists(value: any): boolean {
return value !== undefined && value !== null;
}
private stringExists(value: string | null | undefined): boolean {
return this.valueExists(value) && (value as string).length > 0;
}
get searchIsUrl(): boolean {
let url;
if (!this.search) return false;
try {
url = new URL(this.search);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
}
</script>
<style scoped lang="scss">
@import "~bulma/sass/utilities/mixins.sass";
main > .container {
background: $white;
.hero-body {
padding: 1rem 1.5rem;
}
}
h1.title {
margin-top: 1.5rem;
}
h3.title {
margin-bottom: 1.5rem;
}
.events-featured {
margin: 25px auto;
.columns {
margin: 1rem auto 3rem;
}
}
form {
display: grid;
grid-gap: 0 15px;
grid-template-areas: "query" "location" "radius" "date" "type" "category";
& > * {
margin-bottom: 0 !important;
}
@include desktop {
grid-template-areas: "query . ." "location radius ." "date type category";
}
.searchQuery {
grid-area: query;
@include tablet {
grid-column: span 4;
}
@include desktop {
grid-column-start: 1;
grid-column-end: 5;
}
}
.searchLocation {
grid-area: location;
:v-deep .column {
padding-bottom: 0;
}
@include tablet {
grid-column: span 4;
}
@include desktop {
grid-column-start: 1;
grid-column-end: 4;
}
}
.searchRadius {
grid-area: radius;
@include desktop {
grid-column-start: 4;
grid-column-end: 5;
}
}
.searchDate {
grid-area: date;
@include desktop {
grid-column-start: 1;
grid-column-end: 2;
}
}
.searchType {
grid-area: type;
@include desktop {
grid-column-start: 2;
grid-column-end: 3;
}
}
.searchCategory {
grid-area: category;
@include desktop {
grid-column-start: 3;
grid-column-end: 5;
}
}
}
</style>

View file

@ -0,0 +1,641 @@
<template>
<div class="section container">
<section v-if="tag">
<i18n path="Events tagged with {tag}">
<b-tag slot="tag" type="is-light">{{ $t("#{tag}", { tag }) }}</b-tag>
</i18n>
</section>
<section
class="events-featured"
v-if="!canSearchEvents && !canSearchGroups"
>
<b-loading :active.sync="$apollo.loading"></b-loading>
<h2 class="title">{{ $t("Explore") }}</h2>
<div v-if="events.elements.length > 0">
<multi-card class="my-4" :events="events.elements" />
<div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="events.total"
v-model="featuredEventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message
v-else-if="events.elements.length === 0 && $apollo.loading === false"
type="is-danger"
>{{ $t("No events found") }}</b-message
>
</section>
<b-tabs v-else v-model="activeTab" type="is-boxed" class="mt-3 searchTabs">
<b-loading :active.sync="$apollo.loading"></b-loading>
<b-tab-item>
<template slot="header">
<b-icon icon="calendar"></b-icon>
<span>
{{ $t("Events") }}
<b-tag rounded>{{ searchEvents.total }}</b-tag>
</span>
</template>
<div v-if="searchEvents.total > 0">
<multi-card class="my-4" :events="searchEvents.elements" />
<div class="pagination" v-if="searchEvents.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="searchEvents.total"
v-model="eventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
<p>{{ $t("No events found") }}</p>
<p v-if="searchIsUrl && !currentUser.id">
{{
$t(
"Only registered users may fetch remote events from their URL."
)
}}
</p>
</b-message>
</b-tab-item>
<b-tab-item v-if="!tag">
<template slot="header">
<b-icon icon="account-multiple"></b-icon>
<span>
{{ $t("Groups") }} <b-tag rounded>{{ searchGroups.total }}</b-tag>
</span>
</template>
<b-message v-if="config && !config.features.groups" type="is-danger">
{{ $t("Groups are not enabled on this instance.") }}
</b-message>
<div v-else-if="searchGroups.total > 0">
<multi-group-card class="my-4" :groups="searchGroups.elements" />
<div class="pagination">
<b-pagination
:total="searchGroups.total"
v-model="groupPage"
:per-page="GROUP_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
{{ $t("No groups found") }}
</b-message>
</b-tab-item>
</b-tabs>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import ngeohash, { GeographicPoint } from "ngeohash";
import {
endOfToday,
addDays,
startOfDay,
endOfDay,
endOfWeek,
addWeeks,
startOfWeek,
endOfMonth,
addMonths,
startOfMonth,
eachWeekendOfInterval,
} from "date-fns";
import { SearchTabs } from "@/types/enums";
import MultiCard from "../components/Event/MultiCard.vue";
import { FETCH_EVENTS } from "../graphql/event";
import { EventType, IEvent } from "../types/event.model";
import RouteName from "../router/name";
import { IAddress, Address } from "../types/address.model";
import FullAddressAutoComplete from "../components/Event/FullAddressAutoComplete.vue";
import { SEARCH_EVENTS_AND_GROUPS } from "../graphql/search";
import { Paginate } from "../types/paginate";
import { IGroup } from "../types/actor";
import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
import { CONFIG } from "../graphql/config";
import { REVERSE_GEOCODE } from "../graphql/address";
import debounce from "lodash/debounce";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
interface ISearchTimeOption {
label: string;
start?: Date | null;
end?: Date | null;
}
const EVENT_PAGE_LIMIT = 99;
const GROUP_PAGE_LIMIT = 99;
const DEFAULT_RADIUS = 25; // value to set if radius is null but location set
const DEFAULT_ZOOM = 11; // zoom on a city
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
@Component({
components: {
MultiCard,
FullAddressAutoComplete,
MultiGroupCard,
},
apollo: {
config: CONFIG,
events: {
query: FETCH_EVENTS,
variables() {
return {
page: this.featuredEventPage,
limit: EVENT_PAGE_LIMIT,
};
},
},
searchElements: {
query: SEARCH_EVENTS_AND_GROUPS,
fetchPolicy: "cache-and-network",
variables() {
return {
term: this.search,
tags: this.tag,
location: this.geohash,
beginsOn: this.start,
endsOn: this.end,
radius: this.radius,
eventPage: this.eventPage,
groupPage: this.groupPage,
limit: EVENT_PAGE_LIMIT,
type: this.type,
};
},
update(data) {
this.searchEvents = data.searchEvents;
this.searchGroups = data.searchGroups;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.$refs.autocompleteSearchInput?.focus();
},
},
currentUser: CURRENT_USER_CLIENT,
},
metaInfo() {
return {
title: this.$t("Explore events") as string,
titleTemplate: "%s | Mobilizon",
};
},
})
export default class Search extends Vue {
@Prop({ type: String, required: false }) tag!: string;
events: Paginate<IEvent> = {
total: 0,
elements: [],
};
searchEvents: Paginate<IEvent> = {
total: 0,
elements: [],
};
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
location: IAddress = new Address();
currentUser!: ICurrentUser;
dateOptions: Record<string, ISearchTimeOption> = {
past: {
label: this.$t("In the past") as string,
start: null,
end: new Date(),
},
today: {
label: this.$t("Today") as string,
start: new Date(),
end: endOfToday(),
},
tomorrow: {
label: this.$t("Tomorrow") as string,
start: startOfDay(addDays(new Date(), 1)),
end: endOfDay(addDays(new Date(), 1)),
},
weekend: {
label: this.$t("This weekend") as string,
start: this.weekend.start,
end: this.weekend.end,
},
week: {
label: this.$t("This week") as string,
start: new Date(),
end: endOfWeek(new Date(), { locale: this.$dateFnsLocale }),
},
next_week: {
label: this.$t("Next week") as string,
start: startOfWeek(addWeeks(new Date(), 1), {
locale: this.$dateFnsLocale,
}),
end: endOfWeek(addWeeks(new Date(), 1), { locale: this.$dateFnsLocale }),
},
month: {
label: this.$t("This month") as string,
start: new Date(),
end: endOfMonth(new Date()),
},
next_month: {
label: this.$t("Next month") as string,
start: startOfMonth(addMonths(new Date(), 1)),
end: endOfMonth(addMonths(new Date(), 1)),
},
any: {
label: this.$t("Any day") as string,
start: undefined,
end: undefined,
},
};
EVENT_PAGE_LIMIT = EVENT_PAGE_LIMIT;
GROUP_PAGE_LIMIT = GROUP_PAGE_LIMIT;
$refs!: {
aac: FullAddressAutoComplete;
autocompleteSearchInput: any;
};
data(): Record<string, unknown> {
return {
debouncedUpdateSearchQuery: debounce(this.updateSearchQuery, 500),
};
}
mounted(): void {
this.prepareLocation(this.$route.query.geohash as string);
}
radiusString = (radius: number | null): string => {
if (radius) {
return this.$tc("{nb} km", radius, { nb: radius }) as string;
}
return this.$t("any distance") as string;
};
radiusOptions: (number | null)[] = [1, 5, 10, 25, 50, 100, 150, null];
submit(): void {
this.$apollo.queries.searchEvents.refetch();
}
updateSearchQuery(searchQuery: string): void {
this.search = searchQuery;
}
get featuredEventPage(): number {
return parseInt(this.$route.query.featuredEventPage as string, 10) || 1;
}
set featuredEventPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, featuredEventPage: page.toString() },
});
}
get eventPage(): number {
return parseInt(this.$route.query.eventPage as string, 10) || 1;
}
set eventPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, eventPage: page.toString() },
});
}
get groupPage(): number {
return parseInt(this.$route.query.groupPage as string, 10) || 1;
}
set groupPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, groupPage: page.toString() },
});
}
get search(): string | undefined {
return this.$route.query.term as string;
}
set search(term: string | undefined) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, term },
});
}
get activeTab(): SearchTabs {
return (
parseInt(this.$route.query.searchType as string, 10) || SearchTabs.EVENTS
);
}
set activeTab(value: SearchTabs) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, searchType: value.toString() },
});
}
get geohash(): string | undefined {
if (this.location?.geom) {
const [lon, lat] = this.location.geom.split(";");
return ngeohash.encode(lat, lon, GEOHASH_DEPTH);
}
return undefined;
}
set geohash(value: string | undefined) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, geohash: value },
});
}
get radius(): number | null {
if (this.$route.query.radius === "any") {
return null;
}
return parseInt(this.$route.query.radius as string, 10) || null;
}
set radius(value: number | null) {
const radius = value === null ? "any" : value.toString();
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, radius },
});
}
get when(): string {
return (this.$route.query.when as string) || "any";
}
set when(value: string) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, when: value },
});
}
get type(): EventType {
return this.$route.query.type as EventType;
}
set type(type: EventType) {
const query = { ...this.$route.query, type };
if (type == null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete query.type;
}
this.$router.replace({
name: RouteName.SEARCH,
query,
});
}
get eventCategory(): string | null {
return (this.$route.query.eventCategory as string) || null;
}
set eventCategory(eventCategory: string | null) {
let query = { ...this.$route.query, eventCategory };
if (query.eventCategory === null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete query.eventCategory;
}
this.$router.replace({
name: RouteName.SEARCH,
query,
});
}
get weekend(): { start: Date; end: Date } {
const now = new Date();
const endOfWeekDate = endOfWeek(now, { locale: this.$dateFnsLocale });
const startOfWeekDate = startOfWeek(now, { locale: this.$dateFnsLocale });
const [start, end] = eachWeekendOfInterval({
start: startOfWeekDate,
end: endOfWeekDate,
});
return { start: startOfDay(start), end: endOfDay(end) };
}
private prepareLocation(value: string | undefined): void {
if (value !== undefined) {
// decode
const latlon = ngeohash.decode(value);
// set location
this.reverseGeoCode(latlon, DEFAULT_ZOOM);
}
}
async reverseGeoCode(e: GeographicPoint, zoom: number): Promise<void> {
const result = await this.$apollo.query({
query: REVERSE_GEOCODE,
variables: {
latitude: e.latitude,
longitude: e.longitude,
zoom,
locale: this.$i18n.locale,
},
});
const addressData = result.data.reverseGeocode.map(
(address: IAddress) => new Address(address)
);
if (addressData.length > 0) {
this.location = addressData[0];
}
}
locchange = (e: IAddress): void => {
if (this.radius === undefined || this.radius === null) {
this.radius = DEFAULT_RADIUS;
}
if (e?.geom) {
const [lon, lat] = e.geom.split(";");
this.geohash = ngeohash.encode(lat, lon, GEOHASH_DEPTH);
} else {
this.geohash = undefined;
}
};
get start(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].start;
}
return undefined;
}
get end(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].end;
}
return undefined;
}
get canSearchGroups(): boolean {
return (
this.stringExists(this.search) ||
(this.stringExists(this.geohash) && this.valueExists(this.radius))
);
}
get canSearchEvents(): boolean {
return (
this.stringExists(this.search) ||
this.stringExists(this.tag) ||
this.stringExists(this.type) ||
(this.stringExists(this.geohash) && this.valueExists(this.radius)) ||
this.valueExists(this.end)
);
}
// helper functions for skip
private valueExists(value: any): boolean {
return value !== undefined && value !== null;
}
private stringExists(value: string | null | undefined): boolean {
return this.valueExists(value) && (value as string).length > 0;
}
get searchIsUrl(): boolean {
let url;
if (!this.search) return false;
try {
url = new URL(this.search);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
}
</script>
<style scoped lang="scss">
@import "~bulma/sass/utilities/mixins.sass";
main > .container {
background: $white;
.hero-body {
padding: 1rem 1.5rem;
}
}
h1.title {
margin-top: 1.5rem;
}
h3.title {
margin-bottom: 1.5rem;
}
.events-featured {
margin: 25px auto;
.columns {
margin: 1rem auto 3rem;
}
}
form {
display: grid;
grid-gap: 0 15px;
grid-template-areas: "query" "location" "radius" "date" "type" "category";
& > * {
margin-bottom: 0 !important;
}
@include desktop {
grid-template-areas: "query . ." "location radius ." "date type category";
}
.searchQuery {
grid-area: query;
@include tablet {
grid-column: span 4;
}
@include desktop {
grid-column-start: 1;
grid-column-end: 5;
}
}
.searchLocation {
grid-area: location;
:v-deep .column {
padding-bottom: 0;
}
@include tablet {
grid-column: span 4;
}
@include desktop {
grid-column-start: 1;
grid-column-end: 4;
}
}
.searchRadius {
grid-area: radius;
@include desktop {
grid-column-start: 4;
grid-column-end: 5;
}
}
.searchDate {
grid-area: date;
@include desktop {
grid-column-start: 1;
grid-column-end: 2;
}
}
.searchType {
grid-area: type;
@include desktop {
grid-column-start: 2;
grid-column-end: 3;
}
}
.searchCategory {
grid-area: category;
@include desktop {
grid-column-start: 3;
grid-column-end: 5;
}
}
}
</style>

View file

@ -0,0 +1,641 @@
<template>
<div class="section container">
<section v-if="tag">
<i18n path="Events tagged with {tag}">
<b-tag slot="tag" type="is-light">{{ $t("#{tag}", { tag }) }}</b-tag>
</i18n>
</section>
<section
class="events-featured"
v-if="!canSearchEvents && !canSearchGroups"
>
<b-loading :active.sync="$apollo.loading"></b-loading>
<h2 class="title">{{ $t("Explore") }}</h2>
<div v-if="events.elements.length > 0">
<multi-card class="my-4" :events="events.elements" />
<div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="events.total"
v-model="featuredEventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message
v-else-if="events.elements.length === 0 && $apollo.loading === false"
type="is-danger"
>{{ $t("No events found") }}</b-message
>
</section>
<b-tabs v-else v-model="activeTab" type="is-boxed" class="mt-3 searchTabs">
<b-loading :active.sync="$apollo.loading"></b-loading>
<b-tab-item>
<template slot="header">
<b-icon icon="calendar"></b-icon>
<span>
{{ $t("Events") }}
<b-tag rounded>{{ searchEvents.total }}</b-tag>
</span>
</template>
<div v-if="searchEvents.total > 0">
<multi-card class="my-4" :events="searchEvents.elements" />
<div class="pagination" v-if="searchEvents.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="searchEvents.total"
v-model="eventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
<p>{{ $t("No events found") }}</p>
<p v-if="searchIsUrl && !currentUser.id">
{{
$t(
"Only registered users may fetch remote events from their URL."
)
}}
</p>
</b-message>
</b-tab-item>
<b-tab-item v-if="!tag">
<template slot="header">
<b-icon icon="account-multiple"></b-icon>
<span>
{{ $t("Groups") }} <b-tag rounded>{{ searchGroups.total }}</b-tag>
</span>
</template>
<b-message v-if="config && !config.features.groups" type="is-danger">
{{ $t("Groups are not enabled on this instance.") }}
</b-message>
<div v-else-if="searchGroups.total > 0">
<multi-group-card class="my-4" :groups="searchGroups.elements" />
<div class="pagination">
<b-pagination
:total="searchGroups.total"
v-model="groupPage"
:per-page="GROUP_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
{{ $t("No groups found") }}
</b-message>
</b-tab-item>
</b-tabs>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import ngeohash, { GeographicPoint } from "ngeohash";
import {
endOfToday,
addDays,
startOfDay,
endOfDay,
endOfWeek,
addWeeks,
startOfWeek,
endOfMonth,
addMonths,
startOfMonth,
eachWeekendOfInterval,
} from "date-fns";
import { SearchTabs } from "@/types/enums";
import MultiCard from "../components/Event/MultiCard.vue";
import { FETCH_EVENTS } from "../graphql/event";
import { EventType, IEvent } from "../types/event.model";
import RouteName from "../router/name";
import { IAddress, Address } from "../types/address.model";
import FullAddressAutoComplete from "../components/Event/FullAddressAutoComplete.vue";
import { SEARCH_EVENTS_AND_GROUPS } from "../graphql/search";
import { Paginate } from "../types/paginate";
import { IGroup } from "../types/actor";
import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
import { CONFIG } from "../graphql/config";
import { REVERSE_GEOCODE } from "../graphql/address";
import debounce from "lodash/debounce";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
interface ISearchTimeOption {
label: string;
start?: Date | null;
end?: Date | null;
}
const EVENT_PAGE_LIMIT = 12;
const GROUP_PAGE_LIMIT = 12;
const DEFAULT_RADIUS = 25; // value to set if radius is null but location set
const DEFAULT_ZOOM = 11; // zoom on a city
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
@Component({
components: {
MultiCard,
FullAddressAutoComplete,
MultiGroupCard,
},
apollo: {
config: CONFIG,
events: {
query: FETCH_EVENTS,
variables() {
return {
page: this.featuredEventPage,
limit: EVENT_PAGE_LIMIT,
};
},
},
searchElements: {
query: SEARCH_EVENTS_AND_GROUPS,
fetchPolicy: "cache-and-network",
variables() {
return {
term: this.search,
tags: this.tag,
location: this.geohash,
beginsOn: this.start,
endsOn: this.end,
radius: this.radius,
eventPage: this.eventPage,
groupPage: this.groupPage,
limit: EVENT_PAGE_LIMIT,
type: this.type,
};
},
update(data) {
this.searchEvents = data.searchEvents;
this.searchGroups = data.searchGroups;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.$refs.autocompleteSearchInput?.focus();
},
},
currentUser: CURRENT_USER_CLIENT,
},
metaInfo() {
return {
title: this.$t("Explore events") as string,
titleTemplate: "%s | Mobilizon",
};
},
})
export default class Search extends Vue {
@Prop({ type: String, required: false }) tag!: string;
events: Paginate<IEvent> = {
total: 0,
elements: [],
};
searchEvents: Paginate<IEvent> = {
total: 0,
elements: [],
};
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
location: IAddress = new Address();
currentUser!: ICurrentUser;
dateOptions: Record<string, ISearchTimeOption> = {
past: {
label: this.$t("In the past") as string,
start: null,
end: new Date(),
},
today: {
label: this.$t("Today") as string,
start: new Date(),
end: endOfToday(),
},
tomorrow: {
label: this.$t("Tomorrow") as string,
start: startOfDay(addDays(new Date(), 1)),
end: endOfDay(addDays(new Date(), 1)),
},
weekend: {
label: this.$t("This weekend") as string,
start: this.weekend.start,
end: this.weekend.end,
},
week: {
label: this.$t("This week") as string,
start: new Date(),
end: endOfWeek(new Date(), { locale: this.$dateFnsLocale }),
},
next_week: {
label: this.$t("Next week") as string,
start: startOfWeek(addWeeks(new Date(), 1), {
locale: this.$dateFnsLocale,
}),
end: endOfWeek(addWeeks(new Date(), 1), { locale: this.$dateFnsLocale }),
},
month: {
label: this.$t("This month") as string,
start: new Date(),
end: endOfMonth(new Date()),
},
next_month: {
label: this.$t("Next month") as string,
start: startOfMonth(addMonths(new Date(), 1)),
end: endOfMonth(addMonths(new Date(), 1)),
},
any: {
label: this.$t("Any day") as string,
start: undefined,
end: undefined,
},
};
EVENT_PAGE_LIMIT = EVENT_PAGE_LIMIT;
GROUP_PAGE_LIMIT = GROUP_PAGE_LIMIT;
$refs!: {
aac: FullAddressAutoComplete;
autocompleteSearchInput: any;
};
data(): Record<string, unknown> {
return {
debouncedUpdateSearchQuery: debounce(this.updateSearchQuery, 500),
};
}
mounted(): void {
this.prepareLocation(this.$route.query.geohash as string);
}
radiusString = (radius: number | null): string => {
if (radius) {
return this.$tc("{nb} km", radius, { nb: radius }) as string;
}
return this.$t("any distance") as string;
};
radiusOptions: (number | null)[] = [1, 5, 10, 25, 50, 100, 150, null];
submit(): void {
this.$apollo.queries.searchEvents.refetch();
}
updateSearchQuery(searchQuery: string): void {
this.search = searchQuery;
}
get featuredEventPage(): number {
return parseInt(this.$route.query.featuredEventPage as string, 10) || 1;
}
set featuredEventPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, featuredEventPage: page.toString() },
});
}
get eventPage(): number {
return parseInt(this.$route.query.eventPage as string, 10) || 1;
}
set eventPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, eventPage: page.toString() },
});
}
get groupPage(): number {
return parseInt(this.$route.query.groupPage as string, 10) || 1;
}
set groupPage(page: number) {
this.$router.push({
name: this.$route.name || RouteName.SEARCH,
query: { ...this.$route.query, groupPage: page.toString() },
});
}
get search(): string | undefined {
return this.$route.query.term as string;
}
set search(term: string | undefined) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, term },
});
}
get activeTab(): SearchTabs {
return (
parseInt(this.$route.query.searchType as string, 10) || SearchTabs.EVENTS
);
}
set activeTab(value: SearchTabs) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, searchType: value.toString() },
});
}
get geohash(): string | undefined {
if (this.location?.geom) {
const [lon, lat] = this.location.geom.split(";");
return ngeohash.encode(lat, lon, GEOHASH_DEPTH);
}
return undefined;
}
set geohash(value: string | undefined) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, geohash: value },
});
}
get radius(): number | null {
if (this.$route.query.radius === "any") {
return null;
}
return parseInt(this.$route.query.radius as string, 10) || null;
}
set radius(value: number | null) {
const radius = value === null ? "any" : value.toString();
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, radius },
});
}
get when(): string {
return (this.$route.query.when as string) || "any";
}
set when(value: string) {
this.$router.replace({
name: RouteName.SEARCH,
query: { ...this.$route.query, when: value },
});
}
get type(): EventType {
return this.$route.query.type as EventType;
}
set type(type: EventType) {
const query = { ...this.$route.query, type };
if (type == null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete query.type;
}
this.$router.replace({
name: RouteName.SEARCH,
query,
});
}
get eventCategory(): string | null {
return (this.$route.query.eventCategory as string) || null;
}
set eventCategory(eventCategory: string | null) {
let query = { ...this.$route.query, eventCategory };
if (query.eventCategory === null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete query.eventCategory;
}
this.$router.replace({
name: RouteName.SEARCH,
query,
});
}
get weekend(): { start: Date; end: Date } {
const now = new Date();
const endOfWeekDate = endOfWeek(now, { locale: this.$dateFnsLocale });
const startOfWeekDate = startOfWeek(now, { locale: this.$dateFnsLocale });
const [start, end] = eachWeekendOfInterval({
start: startOfWeekDate,
end: endOfWeekDate,
});
return { start: startOfDay(start), end: endOfDay(end) };
}
private prepareLocation(value: string | undefined): void {
if (value !== undefined) {
// decode
const latlon = ngeohash.decode(value);
// set location
this.reverseGeoCode(latlon, DEFAULT_ZOOM);
}
}
async reverseGeoCode(e: GeographicPoint, zoom: number): Promise<void> {
const result = await this.$apollo.query({
query: REVERSE_GEOCODE,
variables: {
latitude: e.latitude,
longitude: e.longitude,
zoom,
locale: this.$i18n.locale,
},
});
const addressData = result.data.reverseGeocode.map(
(address: IAddress) => new Address(address)
);
if (addressData.length > 0) {
this.location = addressData[0];
}
}
locchange = (e: IAddress): void => {
if (this.radius === undefined || this.radius === null) {
this.radius = DEFAULT_RADIUS;
}
if (e?.geom) {
const [lon, lat] = e.geom.split(";");
this.geohash = ngeohash.encode(lat, lon, GEOHASH_DEPTH);
} else {
this.geohash = undefined;
}
};
get start(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].start;
}
return undefined;
}
get end(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].end;
}
return undefined;
}
get canSearchGroups(): boolean {
return (
this.stringExists(this.search) ||
(this.stringExists(this.geohash) && this.valueExists(this.radius))
);
}
get canSearchEvents(): boolean {
return (
this.stringExists(this.search) ||
this.stringExists(this.tag) ||
this.stringExists(this.type) ||
(this.stringExists(this.geohash) && this.valueExists(this.radius)) ||
this.valueExists(this.end)
);
}
// helper functions for skip
private valueExists(value: any): boolean {
return value !== undefined && value !== null;
}
private stringExists(value: string | null | undefined): boolean {
return this.valueExists(value) && (value as string).length > 0;
}
get searchIsUrl(): boolean {
let url;
if (!this.search) return false;
try {
url = new URL(this.search);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
}
</script>
<style scoped lang="scss">
@import "~bulma/sass/utilities/mixins.sass";
main > .container {
background: $white;
.hero-body {
padding: 1rem 1.5rem;
}
}
h1.title {
margin-top: 1.5rem;
}
h3.title {
margin-bottom: 1.5rem;
}
.events-featured {
margin: 25px auto;
.columns {
margin: 1rem auto 3rem;
}
}
form {
display: grid;
grid-gap: 0 15px;
grid-template-areas: "query" "location" "radius" "date" "type" "category";
& > * {
margin-bottom: 0 !important;
}
@include desktop {
grid-template-areas: "query . ." "location radius ." "date type category";
}
.searchQuery {
grid-area: query;
@include tablet {
grid-column: span 4;
}
@include desktop {
grid-column-start: 1;
grid-column-end: 5;
}
}
.searchLocation {
grid-area: location;
:v-deep .column {
padding-bottom: 0;
}
@include tablet {
grid-column: span 4;
}
@include desktop {
grid-column-start: 1;
grid-column-end: 4;
}
}
.searchRadius {
grid-area: radius;
@include desktop {
grid-column-start: 4;
grid-column-end: 5;
}
}
.searchDate {
grid-area: date;
@include desktop {
grid-column-start: 1;
grid-column-end: 2;
}
}
.searchType {
grid-area: type;
@include desktop {
grid-column-start: 2;
grid-column-end: 3;
}
}
.searchCategory {
grid-area: category;
@include desktop {
grid-column-start: 3;
grid-column-end: 5;
}
}
}
</style>

View file

@ -1663,7 +1663,7 @@ defmodule Mobilizon.Events do
@spec filter_future_events(Ecto.Queryable.t(), boolean) :: Ecto.Query.t() @spec filter_future_events(Ecto.Queryable.t(), boolean) :: Ecto.Query.t()
defp filter_future_events(query, true) do defp filter_future_events(query, true) do
from(q in query, from(q in query,
where: q.begins_on > ^DateTime.utc_now() where: q.ends_on > ^DateTime.utc_now()
) )
end end

File diff suppressed because it is too large Load diff