mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-21 23:44:30 +00:00
Various accessibility improvements
* Add announcement element with `aria-live` * Add skip to main content element Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
6113836e29
commit
eba3c70c9b
62 changed files with 687 additions and 175 deletions
|
@ -29,6 +29,8 @@
|
|||
"@tiptap/extension-underline": "^2.0.0-beta.7",
|
||||
"@tiptap/starter-kit": "^2.0.0-beta.37",
|
||||
"@tiptap/vue-2": "^2.0.0-beta.21",
|
||||
"@vue-a11y/announcer": "^2.1.0",
|
||||
"@vue-a11y/skip-to": "^2.1.2",
|
||||
"@vue/apollo-option": "4.0.0-alpha.11",
|
||||
"apollo-absinthe-upload-link": "^1.5.0",
|
||||
"blurhash": "^1.1.3",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div id="mobilizon">
|
||||
<VueAnnouncer />
|
||||
<VueSkipTo to="#main" :label="$t('Skip to main content')" />
|
||||
<NavBar />
|
||||
<div v-if="config && config.demoMode">
|
||||
<b-message
|
||||
|
@ -22,9 +24,9 @@
|
|||
</div>
|
||||
<error v-if="error" :error="error" />
|
||||
|
||||
<main v-else>
|
||||
<main id="main" v-else>
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
<router-view ref="routerView" />
|
||||
</transition>
|
||||
</main>
|
||||
<mobilizon-footer />
|
||||
|
@ -32,7 +34,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { Component, Ref, Vue, Watch } from "vue-property-decorator";
|
||||
import NavBar from "./components/NavBar.vue";
|
||||
import {
|
||||
AUTH_ACCESS_TOKEN,
|
||||
|
@ -52,6 +54,7 @@ import { IConfig } from "./types/config.model";
|
|||
import { ICurrentUser } from "./types/current-user.model";
|
||||
import jwt_decode, { JwtPayload } from "jwt-decode";
|
||||
import { refreshAccessToken } from "./apollo/utils";
|
||||
import { Route } from "vue-router";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
|
@ -82,6 +85,8 @@ export default class App extends Vue {
|
|||
|
||||
interval: number | undefined = undefined;
|
||||
|
||||
@Ref("routerView") routerView!: Vue;
|
||||
|
||||
async created(): Promise<void> {
|
||||
if (await this.initializeCurrentUser()) {
|
||||
await initializeCurrentActor(this.$apollo.provider.defaultClient);
|
||||
|
@ -197,6 +202,39 @@ export default class App extends Vue {
|
|||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
|
||||
@Watch("$route", { immediate: true })
|
||||
updateAnnouncement(route: Route): void {
|
||||
const pageTitle = this.extractPageTitleFromRoute(route);
|
||||
if (pageTitle) {
|
||||
this.$announcer.polite(
|
||||
this.$t("Navigated to {pageTitle}", {
|
||||
pageTitle,
|
||||
}) as string
|
||||
);
|
||||
}
|
||||
// Set the focus to the router view
|
||||
// https://marcus.io/blog/accessible-routing-vuejs
|
||||
setTimeout(() => {
|
||||
const focusTarget = this.routerView.$el as HTMLElement;
|
||||
// Make focustarget programmatically focussable
|
||||
focusTarget.setAttribute("tabindex", "-1");
|
||||
|
||||
// Focus element
|
||||
focusTarget.focus();
|
||||
|
||||
// Remove tabindex from focustarget.
|
||||
// Reason: https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk
|
||||
focusTarget.removeAttribute("tabindex");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
extractPageTitleFromRoute(route: Route): string {
|
||||
if (route.meta?.announcer?.message) {
|
||||
return route.meta?.announcer?.message();
|
||||
}
|
||||
return document.title;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -218,4 +256,8 @@ $mdi-font-path: "~@mdi/font/fonts";
|
|||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.vue-skip-to {
|
||||
z-index: 40;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -135,3 +135,23 @@ a.list-item {
|
|||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin focus() {
|
||||
&:focus {
|
||||
border: 2px solid black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
ul.menu-list > li,
|
||||
p {
|
||||
@include focus;
|
||||
}
|
||||
.navbar-item {
|
||||
@include focus;
|
||||
}
|
||||
|
||||
.navbar-dropdown span.navbar-item:hover {
|
||||
background-color: whitesmoke;
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
)
|
||||
}}
|
||||
</p>
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<p class="content">
|
||||
<span>
|
||||
{{
|
||||
|
@ -33,7 +33,7 @@
|
|||
"
|
||||
/>
|
||||
</p>
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<p class="content">
|
||||
{{
|
||||
$t(
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
ref="commentEditor"
|
||||
v-model="newComment.text"
|
||||
mode="comment"
|
||||
:aria-label="$t('Comment body')"
|
||||
/>
|
||||
<b-button
|
||||
:disabled="newComment.text.trim().length === 0"
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
ref="commenteditor"
|
||||
mode="comment"
|
||||
v-model="newComment.text"
|
||||
:aria-label="$t('Comment body')"
|
||||
/>
|
||||
</p>
|
||||
<p class="help is-danger" v-if="emptyCommentError">
|
||||
|
@ -30,9 +31,11 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="field notify-participants" v-if="isEventOrganiser">
|
||||
<b-switch v-model="newComment.isAnnouncement">{{
|
||||
$t("Notify participants")
|
||||
}}</b-switch>
|
||||
<b-switch
|
||||
aria-labelledby="notify-participants-toggle"
|
||||
v-model="newComment.isAnnouncement"
|
||||
>{{ $t("Notify participants") }}</b-switch
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
{{ $t("[This comment has been deleted by it's author]") }}
|
||||
</div>
|
||||
<form v-else class="edition" @submit.prevent="updateComment">
|
||||
<editor v-model="updatedComment" />
|
||||
<editor v-model="updatedComment" :aria-label="$t('Comment body')" />
|
||||
<div class="buttons">
|
||||
<b-button
|
||||
native-type="submit"
|
||||
|
|
|
@ -227,6 +227,8 @@ export default class EditorComponent extends Vue {
|
|||
|
||||
@Prop({ required: false, default: 100_000_000 }) maxSize!: number;
|
||||
|
||||
@Prop({ required: false }) ariaLabel!: string;
|
||||
|
||||
currentActor!: IPerson;
|
||||
|
||||
editor: Editor | null = null;
|
||||
|
@ -256,6 +258,13 @@ export default class EditorComponent extends Vue {
|
|||
|
||||
mounted(): void {
|
||||
this.editor = new Editor({
|
||||
editorProps: {
|
||||
attributes: {
|
||||
"aria-multiline": this.isShortMode.toString(),
|
||||
"aria-label": this.ariaLabel,
|
||||
role: "textbox",
|
||||
},
|
||||
},
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Document,
|
||||
|
|
|
@ -103,9 +103,11 @@
|
|||
:active="copied !== false"
|
||||
always
|
||||
>
|
||||
<b-button @click="copyErrorToClipboard">{{
|
||||
$t("Copy details to clipboard")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
@click="copyErrorToClipboard"
|
||||
@keyup.enter="copyErrorToClipboard"
|
||||
>{{ $t("Copy details to clipboard") }}</b-button
|
||||
>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
v-if="!gettingLocation"
|
||||
icon-right="target"
|
||||
@click="locateMe"
|
||||
@keyup.enter="locateMe"
|
||||
>{{ $t("Use my location") }}</b-button
|
||||
>
|
||||
<span v-else>{{ $t("Getting location") }}</span>
|
||||
|
|
|
@ -7,25 +7,15 @@
|
|||
<div class="address-wrapper">
|
||||
<span v-if="!physicalAddress">{{ $t("No address defined") }}</span>
|
||||
<div class="address" v-if="physicalAddress">
|
||||
<div>
|
||||
<address>
|
||||
<p
|
||||
class="addressDescription"
|
||||
:title="physicalAddress.poiInfos.name"
|
||||
>
|
||||
{{ physicalAddress.poiInfos.name }}
|
||||
</p>
|
||||
<p class="has-text-grey-dark">
|
||||
{{ physicalAddress.poiInfos.alternativeName }}
|
||||
</p>
|
||||
</address>
|
||||
</div>
|
||||
<span
|
||||
<address-info :address="physicalAddress" />
|
||||
<b-button
|
||||
type="is-text"
|
||||
class="map-show-button"
|
||||
@click="showMap = !showMap"
|
||||
v-if="physicalAddress.geom"
|
||||
>{{ $t("Show map") }}</span
|
||||
>
|
||||
{{ $t("Show map") }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</event-metadata-block>
|
||||
|
|
|
@ -30,18 +30,22 @@ A button to set your participation
|
|||
position="is-bottom-left"
|
||||
v-if="participation && participation.role === ParticipantRole.PARTICIPANT"
|
||||
>
|
||||
<button class="button is-success is-large" type="button" slot="trigger">
|
||||
<b-icon icon="check" />
|
||||
<template>
|
||||
<span>{{ $t("I participate") }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
<template #trigger="{ active }">
|
||||
<b-button
|
||||
type="is-success"
|
||||
size="is-large"
|
||||
icon-left="check"
|
||||
:icon-right="active ? 'menu-up' : 'menu-down'"
|
||||
>
|
||||
{{ $t("I participate") }}
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item
|
||||
:value="false"
|
||||
aria-role="listitem"
|
||||
@click="confirmLeave"
|
||||
@keyup.enter="confirmLeave"
|
||||
class="has-text-danger"
|
||||
>{{ $t("Cancel my participation…") }}</b-dropdown-item
|
||||
>
|
||||
|
@ -73,6 +77,7 @@ A button to set your participation
|
|||
:value="false"
|
||||
aria-role="listitem"
|
||||
@click="confirmLeave"
|
||||
@keyup.enter="confirmLeave"
|
||||
class="has-text-danger"
|
||||
>{{ $t("Cancel my participation request…") }}</b-dropdown-item
|
||||
>
|
||||
|
@ -101,17 +106,21 @@ A button to set your participation
|
|||
position="is-bottom-left"
|
||||
v-else-if="!participation && currentActor.id"
|
||||
>
|
||||
<button class="button is-primary is-large" type="button" slot="trigger">
|
||||
<template>
|
||||
<span>{{ $t("Participate") }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
<template #trigger="{ active }">
|
||||
<b-button
|
||||
type="is-primary"
|
||||
size="is-large"
|
||||
:icon-right="active ? 'menu-up' : 'menu-down'"
|
||||
>
|
||||
{{ $t("Participate") }}
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item
|
||||
:value="true"
|
||||
aria-role="listitem"
|
||||
@click="joinEvent(currentActor)"
|
||||
@keyup.enter="joinEvent(currentActor)"
|
||||
>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
|
@ -136,6 +145,7 @@ A button to set your participation
|
|||
:value="false"
|
||||
aria-role="listitem"
|
||||
@click="joinModal"
|
||||
@keyup.enter="joinModal"
|
||||
v-if="identities.length > 1"
|
||||
>{{ $t("with another identity…") }}</b-dropdown-item
|
||||
>
|
||||
|
|
|
@ -25,7 +25,12 @@
|
|||
v-model="locale"
|
||||
:placeholder="$t('Select a language')"
|
||||
>
|
||||
<option v-for="(language, lang) in langs" :value="lang" :key="lang">
|
||||
<option
|
||||
v-for="(language, lang) in langs"
|
||||
:value="lang"
|
||||
:key="lang"
|
||||
:selected="isLangSelected(lang)"
|
||||
>
|
||||
{{ language }}
|
||||
</option>
|
||||
</b-select>
|
||||
|
@ -48,6 +53,9 @@
|
|||
{{ $t("License") }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#navbar">{{ $t("Back to top") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content has-text-centered">
|
||||
<i18n
|
||||
|
@ -101,6 +109,10 @@ export default class Footer extends Vue {
|
|||
this.locale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
isLangSelected(lang: string): boolean {
|
||||
return lang === this.locale;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
@ -145,6 +157,13 @@ footer.footer {
|
|||
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 {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<b-navbar
|
||||
id="navbar"
|
||||
type="is-secondary"
|
||||
wrapper-class="container"
|
||||
:active.sync="mobileNavbarActive"
|
||||
|
@ -58,6 +59,7 @@
|
|||
href="https://mediation.koena.net/framasoft/mobilizon/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
hreflang="fr"
|
||||
>
|
||||
<img
|
||||
src="/img/koena-a11y.svg"
|
||||
|
@ -75,6 +77,10 @@
|
|||
v-if="currentActor.id && currentUser.isLoggedIn"
|
||||
right
|
||||
collapsible
|
||||
ref="user-dropdown"
|
||||
tabindex="0"
|
||||
tag="span"
|
||||
@keyup.enter="toggleMenu"
|
||||
>
|
||||
<template
|
||||
slot="label"
|
||||
|
@ -109,8 +115,11 @@
|
|||
v-else
|
||||
:active="identity.id === currentActor.id"
|
||||
:key="identity.id"
|
||||
tabindex="0"
|
||||
@click="setIdentity(identity)"
|
||||
@keyup.enter="setIdentity(identity)"
|
||||
>
|
||||
<span @click="setIdentity(identity)">
|
||||
<span>
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32" v-if="identity.avatar">
|
||||
<img
|
||||
|
@ -131,7 +140,7 @@
|
|||
</div>
|
||||
</span>
|
||||
|
||||
<hr class="navbar-divider" />
|
||||
<hr class="navbar-divider" role="presentation" />
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-item
|
||||
|
@ -146,8 +155,13 @@
|
|||
>{{ $t("Administration") }}</b-navbar-item
|
||||
>
|
||||
|
||||
<b-navbar-item tag="span">
|
||||
<span @click="logout">{{ $t("Log out") }}</span>
|
||||
<b-navbar-item
|
||||
tag="span"
|
||||
tabindex="0"
|
||||
@click="logout"
|
||||
@keyup.enter="logout"
|
||||
>
|
||||
<span>{{ $t("Log out") }}</span>
|
||||
</b-navbar-item>
|
||||
</b-navbar-dropdown>
|
||||
|
||||
|
|
|
@ -71,7 +71,13 @@ import { IParticipant } from "../../types/participant.model";
|
|||
import RouteName from "../../router/name";
|
||||
import { CONFIRM_PARTICIPATION } from "../../graphql/event";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Confirm participation") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ConfirmParticipation extends Vue {
|
||||
@Prop({ type: String, required: true }) token!: string;
|
||||
|
||||
|
|
|
@ -28,6 +28,11 @@ import { IEvent } from "@/types/event.model";
|
|||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Participation with account") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ParticipationWithAccount extends Vue {
|
||||
@Prop({ type: String, required: true }) uuid!: string;
|
||||
|
|
|
@ -155,6 +155,11 @@ import { ApolloCache, FetchResult } from "@apollo/client/core";
|
|||
},
|
||||
config: CONFIG,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Participation without account") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ParticipationWithoutAccount extends Vue {
|
||||
@Prop({ type: String, required: true }) uuid!: string;
|
||||
|
|
|
@ -130,6 +130,11 @@ import RouteName from "../../router/name";
|
|||
},
|
||||
config: CONFIG,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Unlogged participation") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class UnloggedParticipation extends Vue {
|
||||
@Prop({ type: String, required: true }) uuid!: string;
|
||||
|
|
|
@ -38,7 +38,12 @@
|
|||
</span>
|
||||
</b-upload>
|
||||
</b-field>
|
||||
<b-button type="is-text" v-if="imageSrc" @click="removeOrClearPicture">
|
||||
<b-button
|
||||
type="is-text"
|
||||
v-if="imageSrc"
|
||||
@click="removeOrClearPicture"
|
||||
@keyup.enter="removeOrClearPicture"
|
||||
>
|
||||
{{ $t("Clear") }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,8 @@ import Component from "vue-class-component";
|
|||
import VueScrollTo from "vue-scrollto";
|
||||
import VueMeta from "vue-meta";
|
||||
import VTooltip from "v-tooltip";
|
||||
import VueAnnouncer from "@vue-a11y/announcer";
|
||||
import VueSkipTo from "@vue-a11y/skip-to";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { NotifierPlugin } from "./plugins/notifier";
|
||||
|
@ -20,6 +22,8 @@ Vue.use(filters);
|
|||
Vue.use(VueMeta);
|
||||
Vue.use(VueScrollTo);
|
||||
Vue.use(VTooltip);
|
||||
Vue.use(VueAnnouncer);
|
||||
Vue.use(VueSkipTo);
|
||||
|
||||
// Register the router hooks with their names
|
||||
Component.registerHooks([
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { RouteConfig } from "vue-router";
|
||||
import { ImportedComponent } from "vue/types/options";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
export enum ActorRouteName {
|
||||
GROUP = "Group",
|
||||
|
@ -14,7 +15,10 @@ export const actorRoutes: RouteConfig[] = [
|
|||
name: ActorRouteName.CREATE_GROUP,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "CreateGroup" */ "@/views/Group/Create.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("Create group") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername",
|
||||
|
@ -22,13 +26,16 @@ export const actorRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Group" */ "@/views/Group/Group.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
meta: { requiredAuth: false, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/groups/me",
|
||||
name: ActorRouteName.MY_GROUPS,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "MyGroups" */ "@/views/Group/MyGroups.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("My groups") as string },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { RouteConfig } from "vue-router";
|
||||
import { ImportedComponent } from "vue/types/options";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
export enum DiscussionRouteName {
|
||||
DISCUSSION_LIST = "DISCUSSION_LIST",
|
||||
|
@ -16,7 +17,12 @@ export const discussionRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "DiscussionsList" */ "@/views/Discussions/DiscussionsList.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Discussions list") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/discussions/new",
|
||||
|
@ -26,7 +32,12 @@ export const discussionRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "CreateDiscussion" */ "@/views/Discussions/Create.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Create discussion") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/c/:slug/:comment_id?",
|
||||
|
@ -36,6 +47,6 @@ export const discussionRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "Discussion" */ "@/views/Discussions/Discussion.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { RouteConfig } from "vue-router";
|
||||
import { ImportedComponent } from "vue/types/options";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
export enum ErrorRouteName {
|
||||
ERROR = "Error",
|
||||
|
@ -11,5 +12,8 @@ export const errorRoutes: RouteConfig[] = [
|
|||
name: ErrorRouteName.ERROR,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Error" */ "../views/Error.vue"),
|
||||
meta: {
|
||||
announcer: { message: (): string => i18n.t("Error") as string },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { RouteConfig, Route } from "vue-router";
|
||||
import { ImportedComponent } from "vue/types/options";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
const participations = (): Promise<ImportedComponent> =>
|
||||
import(
|
||||
|
@ -33,25 +34,34 @@ export const eventRoutes: RouteConfig[] = [
|
|||
name: EventRouteName.EVENT_LIST,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "EventList" */ "@/views/Event/EventList.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Event list") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/events/create",
|
||||
name: EventRouteName.CREATE_EVENT,
|
||||
component: editEvent,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("Create event") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/events/me",
|
||||
name: EventRouteName.MY_EVENTS,
|
||||
component: myEvents,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("My events") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/events/edit/:eventId",
|
||||
name: EventRouteName.EDIT_EVENT,
|
||||
component: editEvent,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
props: (route: Route): Record<string, unknown> => {
|
||||
return { ...route.params, ...{ isUpdate: true } };
|
||||
},
|
||||
|
@ -60,7 +70,7 @@ export const eventRoutes: RouteConfig[] = [
|
|||
path: "/events/duplicate/:eventId",
|
||||
name: EventRouteName.DUPLICATE_EVENT,
|
||||
component: editEvent,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announce: { skip: true } },
|
||||
props: (route: Route): Record<string, unknown> => ({
|
||||
...route.params,
|
||||
...{ isDuplicate: true },
|
||||
|
@ -70,7 +80,7 @@ export const eventRoutes: RouteConfig[] = [
|
|||
path: "/events/:eventId/participations",
|
||||
name: EventRouteName.PARTICIPATIONS,
|
||||
component: participations,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
|
@ -78,7 +88,7 @@ export const eventRoutes: RouteConfig[] = [
|
|||
name: EventRouteName.EVENT,
|
||||
component: event,
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
meta: { requiredAuth: false, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/events/:uuid/participate",
|
||||
|
@ -86,12 +96,22 @@ export const eventRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("../components/Participation/UnloggedParticipation.vue"),
|
||||
props: true,
|
||||
meta: {
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Unlogged participation") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/events/:uuid/participate/with-account",
|
||||
name: EventRouteName.EVENT_PARTICIPATE_WITH_ACCOUNT,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import("../components/Participation/ParticipationWithAccount.vue"),
|
||||
meta: {
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Participation with account") as string,
|
||||
},
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
|
@ -99,6 +119,12 @@ export const eventRoutes: RouteConfig[] = [
|
|||
name: EventRouteName.EVENT_PARTICIPATE_WITHOUT_ACCOUNT,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import("../components/Participation/ParticipationWithoutAccount.vue"),
|
||||
meta: {
|
||||
announcer: {
|
||||
message: (): string =>
|
||||
i18n.t("Participation without account") as string,
|
||||
},
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
|
@ -106,6 +132,11 @@ export const eventRoutes: RouteConfig[] = [
|
|||
name: EventRouteName.EVENT_PARTICIPATE_CONFIRM,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import("../components/Participation/ConfirmParticipation.vue"),
|
||||
meta: {
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Confirm participation") as string,
|
||||
},
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
|
@ -114,6 +145,9 @@ export const eventRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Search" */ "@/views/Search.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Tag search") as string },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -33,7 +33,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("@/views/Todos/TodoLists.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/todo-lists/:id",
|
||||
|
@ -41,7 +41,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("@/views/Todos/TodoList.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/todo/:todoId",
|
||||
|
@ -49,21 +49,21 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("@/views/Todos/Todo.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/resources",
|
||||
name: GroupsRouteName.RESOURCE_FOLDER_ROOT,
|
||||
component: resourceFolder,
|
||||
props: { path: "/" },
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/resources/:path+",
|
||||
name: GroupsRouteName.RESOURCE_FOLDER,
|
||||
component: resourceFolder,
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/settings",
|
||||
|
@ -79,6 +79,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
name: GroupsRouteName.GROUP_PUBLIC_SETTINGS,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import("../views/Group/GroupSettings.vue"),
|
||||
meta: { announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "members",
|
||||
|
@ -86,6 +87,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("../views/Group/GroupMembers.vue"),
|
||||
props: true,
|
||||
meta: { announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "followers",
|
||||
|
@ -93,6 +95,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("../views/Group/GroupFollowers.vue"),
|
||||
props: true,
|
||||
meta: { announcer: { skip: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -102,7 +105,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
import("@/views/Posts/Edit.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.POST_CREATE,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/p/:slug/edit",
|
||||
|
@ -113,7 +116,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
...{ isUpdate: true },
|
||||
}),
|
||||
name: GroupsRouteName.POST_EDIT,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/p/:slug",
|
||||
|
@ -121,7 +124,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
import("@/views/Posts/Post.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.POST,
|
||||
meta: { requiredAuth: false },
|
||||
meta: { requiredAuth: false, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/p",
|
||||
|
@ -129,14 +132,14 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
import("@/views/Posts/List.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.POSTS,
|
||||
meta: { requiredAuth: false },
|
||||
meta: { requiredAuth: false, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/events",
|
||||
component: groupEvents,
|
||||
props: true,
|
||||
name: GroupsRouteName.GROUP_EVENTS,
|
||||
meta: { requiredAuth: false },
|
||||
meta: { requiredAuth: false, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/join",
|
||||
|
@ -144,7 +147,7 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
import("@/components/Group/JoinGroupWithAccount.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.GROUP_JOIN,
|
||||
meta: { requiredAuth: false },
|
||||
meta: { requiredAuth: false, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/timeline",
|
||||
|
@ -152,6 +155,6 @@ export const groupsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import("@/views/Group/Timeline.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
];
|
||||
|
|
|
@ -13,6 +13,7 @@ import { groupsRoutes } from "./groups";
|
|||
import { discussionRoutes } from "./discussion";
|
||||
import { userRoutes } from "./user";
|
||||
import RouteName from "./name";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
|
@ -49,13 +50,19 @@ export const routes = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Search" */ "@/views/Search.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Search") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: RouteName.HOME,
|
||||
component: Home,
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Homepage") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
|
@ -72,27 +79,41 @@ export const routes = [
|
|||
import(
|
||||
/* webpackChunkName: "about" */ "@/views/About/AboutInstance.vue"
|
||||
),
|
||||
meta: {
|
||||
announcer: {
|
||||
message: (): string => i18n.t("About instance") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/terms",
|
||||
name: RouteName.TERMS,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "cookies" */ "@/views/About/Terms.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Terms") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/privacy",
|
||||
name: RouteName.PRIVACY,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "cookies" */ "@/views/About/Privacy.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Privacy") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/rules",
|
||||
name: RouteName.RULES,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "cookies" */ "@/views/About/Rules.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Rules") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/glossary",
|
||||
|
@ -101,7 +122,10 @@ export const routes = [
|
|||
import(
|
||||
/* webpackChunkName: "cookies" */ "@/views/About/Glossary.vue"
|
||||
),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Glossary") as string },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -110,7 +134,10 @@ export const routes = [
|
|||
name: RouteName.INTERACT,
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "interact" */ "@/views/Interact.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Interact") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/auth/:provider/callback",
|
||||
|
@ -119,6 +146,11 @@ export const routes = [
|
|||
import(
|
||||
/* webpackChunkName: "ProviderValidation" */ "@/views/User/ProviderValidation.vue"
|
||||
),
|
||||
meta: {
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Redirecting to Mobilizon") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/welcome/:step?",
|
||||
|
@ -127,7 +159,10 @@ export const routes = [
|
|||
import(
|
||||
/* webpackChunkName: "WelcomeScreen" */ "@/views/User/SettingsOnboard.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("First steps") as string },
|
||||
},
|
||||
props: (route: Route): Record<string, unknown> => {
|
||||
const step = Number.parseInt(route.params.step, 10);
|
||||
if (Number.isNaN(step)) {
|
||||
|
@ -143,7 +178,10 @@ export const routes = [
|
|||
import(
|
||||
/* webpackChunkName: "PageNotFound" */ "../views/PageNotFound.vue"
|
||||
),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Page not found") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Route, RouteConfig } from "vue-router";
|
||||
import { ImportedComponent } from "vue/types/options";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
export enum SettingsRouteName {
|
||||
SETTINGS = "SETTINGS",
|
||||
|
@ -34,7 +35,7 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Settings" */ "@/views/Settings.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
redirect: { name: SettingsRouteName.ACCOUNT_SETTINGS },
|
||||
name: SettingsRouteName.SETTINGS,
|
||||
children: [
|
||||
|
@ -42,7 +43,10 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
path: "account",
|
||||
name: SettingsRouteName.ACCOUNT_SETTINGS,
|
||||
redirect: { name: SettingsRouteName.ACCOUNT_SETTINGS_GENERAL },
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { skip: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "account/general",
|
||||
|
@ -52,7 +56,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "AccountSettings" */ "@/views/Settings/AccountSettings.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Account settings") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "preferences",
|
||||
|
@ -62,7 +71,10 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "Preferences" */ "@/views/Settings/Preferences.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("Preferences") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "notifications",
|
||||
|
@ -72,13 +84,18 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "Notifications" */ "@/views/Settings/Notifications.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Notifications") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin",
|
||||
name: SettingsRouteName.ADMIN,
|
||||
redirect: { name: SettingsRouteName.ADMIN_DASHBOARD },
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "admin/dashboard",
|
||||
|
@ -87,7 +104,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
import(
|
||||
/* webpackChunkName: "Dashboard" */ "@/views/Admin/Dashboard.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Admin dashboard") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin/settings",
|
||||
|
@ -97,7 +119,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "AdminSettings" */ "@/views/Admin/Settings.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Admin settings") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin/users",
|
||||
|
@ -105,7 +132,10 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Users" */ "@/views/Admin/Users.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("Users") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin/users/:id",
|
||||
|
@ -115,7 +145,10 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "AdminUserProfile" */ "@/views/Admin/AdminUserProfile.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { skip: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin/profiles",
|
||||
|
@ -125,7 +158,10 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "AdminProfiles" */ "@/views/Admin/Profiles.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("Profiles") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin/profiles/:id",
|
||||
|
@ -135,7 +171,7 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "AdminProfile" */ "@/views/Admin/AdminProfile.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "admin/groups",
|
||||
|
@ -145,7 +181,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "GroupProfiles" */ "@/views/Admin/GroupProfiles.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Group profiles") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "admin/groups/:id",
|
||||
|
@ -155,7 +196,7 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "AdminGroupProfile" */ "@/views/Admin/AdminGroupProfile.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "admin/relays",
|
||||
|
@ -163,7 +204,7 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
redirect: { name: SettingsRouteName.RELAY_FOLLOWINGS },
|
||||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Follows" */ "@/views/Admin/Follows.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
children: [
|
||||
{
|
||||
path: "followings",
|
||||
|
@ -172,7 +213,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
import(
|
||||
/* webpackChunkName: "Followings" */ "@/components/Admin/Followings.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Followings") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "followers",
|
||||
|
@ -181,7 +227,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
import(
|
||||
/* webpackChunkName: "Followers" */ "@/components/Admin/Followers.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Followers") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
props: true,
|
||||
|
@ -190,7 +241,7 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
path: "/moderation",
|
||||
name: SettingsRouteName.MODERATION,
|
||||
redirect: { name: SettingsRouteName.REPORTS },
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/moderation/reports/:filter?",
|
||||
|
@ -200,7 +251,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "ReportList" */ "@/views/Moderation/ReportList.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Reports list") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/moderation/report/:reportId",
|
||||
|
@ -210,7 +266,10 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "Report" */ "@/views/Moderation/Report.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: { message: (): string => i18n.t("Report") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/moderation/logs",
|
||||
|
@ -220,13 +279,18 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "ModerationLogs" */ "@/views/Moderation/Logs.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Moderation logs") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/identity",
|
||||
name: SettingsRouteName.IDENTITIES,
|
||||
redirect: { name: SettingsRouteName.UPDATE_IDENTITY },
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
{
|
||||
path: "/identity/create",
|
||||
|
@ -239,7 +303,12 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
identityName: route.params.identityName,
|
||||
isUpdate: false,
|
||||
}),
|
||||
meta: { requiredAuth: true },
|
||||
meta: {
|
||||
requiredAuth: true,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Create identity") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/identity/update/:identityName?",
|
||||
|
@ -252,7 +321,7 @@ export const settingsRoutes: RouteConfig[] = [
|
|||
identityName: route.params.identityName,
|
||||
isUpdate: true,
|
||||
}),
|
||||
meta: { requiredAuth: true },
|
||||
meta: { requiredAuth: true, announcer: { skip: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { beforeRegisterGuard } from "@/router/guards/register-guard";
|
||||
import { Route, RouteConfig } from "vue-router";
|
||||
import { ImportedComponent } from "vue/types/options";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
export enum UserRouteName {
|
||||
REGISTER = "Register",
|
||||
|
@ -22,7 +23,10 @@ export const userRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "RegisterUser" */ "@/views/User/Register.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Register") as string },
|
||||
},
|
||||
beforeEnter: beforeRegisterGuard,
|
||||
},
|
||||
{
|
||||
|
@ -37,7 +41,10 @@ export const userRoutes: RouteConfig[] = [
|
|||
email: route.params.email,
|
||||
userAlreadyActivated: route.params.userAlreadyActivated === "true",
|
||||
}),
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Register") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/resend-instructions",
|
||||
|
@ -47,7 +54,12 @@ export const userRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "ResendConfirmation" */ "@/views/User/ResendConfirmation.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Resent confirmation email") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/password-reset/send",
|
||||
|
@ -57,7 +69,12 @@ export const userRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "SendPasswordReset" */ "@/views/User/SendPasswordReset.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Send password reset") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/password-reset/:token",
|
||||
|
@ -66,7 +83,10 @@ export const userRoutes: RouteConfig[] = [
|
|||
import(
|
||||
/* webpackChunkName: "PasswordReset" */ "@/views/User/PasswordReset.vue"
|
||||
),
|
||||
meta: { requiresAuth: false },
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Password reset") as string },
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
|
@ -77,7 +97,10 @@ export const userRoutes: RouteConfig[] = [
|
|||
/* webpackChunkName: "EmailValidate" */ "@/views/User/EmailValidate.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Email validate") as string },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/validate/:token",
|
||||
|
@ -85,7 +108,12 @@ export const userRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Validate" */ "@/views/User/Validate.vue"),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
announcer: {
|
||||
message: (): string => i18n.t("Validating account") as string,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
|
@ -93,6 +121,9 @@ export const userRoutes: RouteConfig[] = [
|
|||
component: (): Promise<ImportedComponent> =>
|
||||
import(/* webpackChunkName: "Login" */ "@/views/User/Login.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
meta: {
|
||||
requiredAuth: false,
|
||||
announcer: { message: (): string => i18n.t("Login") as string },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -82,7 +82,7 @@ export interface IEvent {
|
|||
|
||||
onlineAddress?: string;
|
||||
phoneAddress?: string;
|
||||
physicalAddress?: IAddress;
|
||||
physicalAddress: IAddress | null;
|
||||
|
||||
tags: ITag[];
|
||||
options: IEventOptions;
|
||||
|
@ -115,7 +115,7 @@ export class EventModel implements IEvent {
|
|||
|
||||
phoneAddress: string | undefined = "";
|
||||
|
||||
physicalAddress?: IAddress;
|
||||
physicalAddress: IAddress | null = null;
|
||||
|
||||
picture: IMedia | null = null;
|
||||
|
||||
|
@ -192,7 +192,7 @@ export class EventModel implements IEvent {
|
|||
this.phoneAddress = hash.phoneAddress;
|
||||
this.physicalAddress = hash.physicalAddress
|
||||
? new Address(hash.physicalAddress)
|
||||
: undefined;
|
||||
: null;
|
||||
this.participantStats = hash.participantStats;
|
||||
|
||||
this.contacts = hash.contacts;
|
||||
|
|
|
@ -1,28 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="hero intro is-small is-primary">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title">{{ $t("About Mobilizon") }}</h1>
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<b-button
|
||||
icon-left="open-in-new"
|
||||
size="is-large"
|
||||
type="is-secondary"
|
||||
tag="a"
|
||||
href="https://joinmobilizon.org"
|
||||
>{{ $t("Learn more") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="container">
|
||||
<section class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter-desktop">
|
||||
<aside class="menu">
|
||||
|
@ -62,8 +40,29 @@
|
|||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</section>
|
||||
<div class="hero intro is-small is-secondary">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title">{{ $t("Powered by Mobilizon") }}</h1>
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<b-button
|
||||
icon-left="open-in-new"
|
||||
size="is-large"
|
||||
type="is-primary"
|
||||
tag="a"
|
||||
href="https://joinmobilizon.org"
|
||||
>{{ $t("Learn more") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="hero register is-primary is-medium"
|
||||
v-if="!currentUser || !currentUser.id"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<section class="hero is-primary">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h2 class="title">{{ config.name }}</h2>
|
||||
<h1 class="title">{{ config.name }}</h1>
|
||||
<p>{{ config.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@
|
|||
</i18n>
|
||||
</div>
|
||||
<div class="column contact">
|
||||
<h4>{{ $t("Contact") }}</h4>
|
||||
<p class="has-text-weight-bold">{{ $t("Contact") }}</p>
|
||||
<instance-contact-link
|
||||
v-if="config && config.contact"
|
||||
:contact="config.contact"
|
||||
|
@ -32,13 +32,13 @@
|
|||
<p v-else>{{ $t("No information") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<section class="long-description content">
|
||||
<div v-html="config.longDescription" />
|
||||
</section>
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<section class="config">
|
||||
<h3 class="subtitle">{{ $t("Instance configuration") }}</h3>
|
||||
<h2 class="subtitle">{{ $t("Instance configuration") }}</h2>
|
||||
<table class="table is-fullwidth">
|
||||
<tr>
|
||||
<td>{{ $t("Instance languages") }}</td>
|
||||
|
@ -168,7 +168,7 @@ section {
|
|||
}
|
||||
|
||||
&.hero {
|
||||
h2.title {
|
||||
h1.title {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ section {
|
|||
}
|
||||
}
|
||||
.contact {
|
||||
h4 {
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
p {
|
||||
|
|
|
@ -307,6 +307,14 @@ const MEMBERSHIPS_PER_PAGE = 10;
|
|||
ActorCard,
|
||||
EmptyContent,
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { person } = this;
|
||||
return {
|
||||
title: person ? person.name || usernameWithDomain(person) : "",
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class AdminProfile extends Vue {
|
||||
@Prop({ required: true }) id!: string;
|
||||
|
|
|
@ -96,6 +96,14 @@ import { IPerson } from "../../types/actor";
|
|||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { user } = this;
|
||||
return {
|
||||
title: user.email,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class AdminUserProfile extends Vue {
|
||||
@Prop({ required: true }) id!: string;
|
||||
|
|
|
@ -87,6 +87,11 @@ import RouteName from "../../router/name";
|
|||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Follows") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Follows extends Vue {
|
||||
RouteName = RouteName;
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</b-field>
|
||||
|
||||
<b-field :label="$t('Text')">
|
||||
<editor v-model="discussion.text" />
|
||||
<editor v-model="discussion.text" :aria-label="$t('Comment body')" />
|
||||
</b-field>
|
||||
|
||||
<button class="button is-primary" type="submit">
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
>
|
||||
<form @submit.prevent="reply" v-if="!error">
|
||||
<b-field :label="$t('Text')">
|
||||
<editor v-model="newComment" />
|
||||
<editor v-model="newComment" :aria-label="$t('Comment body')" />
|
||||
</b-field>
|
||||
<b-button
|
||||
native-type="submit"
|
||||
|
|
|
@ -84,7 +84,10 @@
|
|||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t("Description") }}</label>
|
||||
<editor v-model="event.description" />
|
||||
<editor
|
||||
v-model="event.description"
|
||||
:aria-label="$t('Event description body')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<b-field :label="$t('Website / URL')" label-for="website-url">
|
||||
|
|
|
@ -245,12 +245,14 @@
|
|||
aria-role="listitem"
|
||||
v-if="canManageEvent || event.draft"
|
||||
@click="openDeleteEventModalWrapper"
|
||||
@keyup.enter="openDeleteEventModalWrapper"
|
||||
>
|
||||
{{ $t("Delete") }}
|
||||
<b-icon icon="delete" />
|
||||
</b-dropdown-item>
|
||||
|
||||
<hr
|
||||
role="presentation"
|
||||
class="dropdown-divider"
|
||||
aria-role="menuitem"
|
||||
v-if="canManageEvent || event.draft"
|
||||
|
@ -259,6 +261,7 @@
|
|||
aria-role="listitem"
|
||||
v-if="!event.draft"
|
||||
@click="triggerShare()"
|
||||
@keyup.enter="triggerShare()"
|
||||
>
|
||||
<span>
|
||||
{{ $t("Share this event") }}
|
||||
|
@ -268,6 +271,7 @@
|
|||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
@click="downloadIcsEvent()"
|
||||
@keyup.enter="downloadIcsEvent()"
|
||||
v-if="!event.draft"
|
||||
>
|
||||
<span>
|
||||
|
@ -279,6 +283,7 @@
|
|||
aria-role="listitem"
|
||||
v-if="ableToReport"
|
||||
@click="isReportModalActive = true"
|
||||
@keyup.enter="isReportModalActive = true"
|
||||
>
|
||||
<span>
|
||||
{{ $t("Report") }}
|
||||
|
@ -379,6 +384,7 @@
|
|||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="isJoinModalActive = false"
|
||||
@keyup.enter="isJoinModalActive = false"
|
||||
>
|
||||
{{ $t("Cancel") }}
|
||||
</button>
|
||||
|
@ -390,6 +396,11 @@
|
|||
? joinEventWithConfirmation(identity)
|
||||
: joinEvent(identity)
|
||||
"
|
||||
@keyup.enter="
|
||||
event.joinOptions === EventJoinOptions.RESTRICTED
|
||||
? joinEventWithConfirmation(identity)
|
||||
: joinEvent(identity)
|
||||
"
|
||||
>
|
||||
{{ $t("Confirm my particpation") }}
|
||||
</button>
|
||||
|
@ -436,6 +447,7 @@
|
|||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="isJoinConfirmationModalActive = false"
|
||||
@keyup.enter="isJoinConfirmationModalActive = false"
|
||||
>{{ $t("Cancel") }}
|
||||
</b-button>
|
||||
<b-button type="is-primary" native-type="submit">
|
||||
|
|
|
@ -62,13 +62,17 @@
|
|||
</template>
|
||||
|
||||
<b-dropdown-item
|
||||
has-link
|
||||
v-for="format in exportFormats"
|
||||
:key="format"
|
||||
@click="exportParticipants(format)"
|
||||
aria-role="listitem"
|
||||
@click="exportParticipants(format)"
|
||||
@keyup.enter="exportParticipants(format)"
|
||||
>
|
||||
<b-icon :icon="formatToIcon(format)"></b-icon>
|
||||
{{ format }}
|
||||
<button class="dropdown-button">
|
||||
<b-icon :icon="formatToIcon(format)"></b-icon>
|
||||
{{ format }}
|
||||
</button>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
|
@ -566,4 +570,21 @@ nav.breadcrumb {
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
button.dropdown-button {
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #0a0a0a;
|
||||
}
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
background: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #4a4a4a;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
padding: 0.375rem 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
</span>
|
||||
</b-dropdown-item>
|
||||
<hr
|
||||
role="presentation"
|
||||
class="dropdown-divider"
|
||||
v-if="isCurrentActorAGroupMember"
|
||||
/>
|
||||
|
@ -224,7 +225,7 @@
|
|||
{{ $t("ICS/WebCal Feed") }}
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
<hr class="dropdown-divider" />
|
||||
<hr role="presentation" class="dropdown-divider" />
|
||||
<b-dropdown-item
|
||||
v-if="ableToReport"
|
||||
aria-role="menuitem"
|
||||
|
|
|
@ -41,7 +41,11 @@
|
|||
<b-input v-model="editableGroup.name" id="group-settings-name" />
|
||||
</b-field>
|
||||
<b-field :label="$t('Group short description')">
|
||||
<editor mode="basic" v-model="editableGroup.summary" :maxSize="500"
|
||||
<editor
|
||||
mode="basic"
|
||||
v-model="editableGroup.summary"
|
||||
:maxSize="500"
|
||||
:aria-label="$t('Group description body')"
|
||||
/></b-field>
|
||||
<b-field :label="$t('Avatar')">
|
||||
<picture-upload
|
||||
|
|
|
@ -39,6 +39,11 @@ import SettingMenuItem from "../../components/Settings/SettingMenuItem.vue";
|
|||
|
||||
@Component({
|
||||
components: { SettingMenuSection, SettingMenuItem },
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Group settings") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Settings extends mixins(GroupMixin) {
|
||||
RouteName = RouteName;
|
||||
|
|
|
@ -241,6 +241,7 @@
|
|||
</span>
|
||||
</section>
|
||||
<hr
|
||||
role="presentation"
|
||||
class="home-separator"
|
||||
v-if="canShowMyUpcomingEvents && canShowLastWeekEvents"
|
||||
/>
|
||||
|
@ -259,6 +260,7 @@
|
|||
</div>
|
||||
</section>
|
||||
<hr
|
||||
role="presentation"
|
||||
class="home-separator"
|
||||
v-if="canShowLastWeekEvents && canShowCloseEvents"
|
||||
/>
|
||||
|
@ -297,6 +299,7 @@
|
|||
</div>
|
||||
</section>
|
||||
<hr
|
||||
role="presentation"
|
||||
class="home-separator"
|
||||
v-if="
|
||||
canShowMyUpcomingEvents || canShowLastWeekEvents || canShowCloseEvents
|
||||
|
|
|
@ -395,6 +395,11 @@ import { Paginate } from "@/types/paginate";
|
|||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Moderation logs") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ReportList extends Vue {
|
||||
actionLogs?: Paginate<IActionLog> = { total: 0, elements: [] };
|
||||
|
|
|
@ -111,6 +111,11 @@ const REPORT_PAGE_LIMIT = 10;
|
|||
pollInterval: 120000, // 2 minutes
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Reports") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ReportList extends Vue {
|
||||
reports?: Paginate<IReport> = { elements: [], total: 0 };
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<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" />
|
||||
<editor v-model="editablePost.body" :aria-label="$t('Post body')" />
|
||||
</div>
|
||||
<subtitle>{{ $t("Who can view this post") }}</subtitle>
|
||||
<fieldset>
|
||||
|
|
|
@ -127,11 +127,13 @@ const POSTS_PAGE_LIMIT = 10;
|
|||
PostElementItem,
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { group } = this;
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
title: this.$t("My groups") as string,
|
||||
titleTemplate: "%s | Mobilizon",
|
||||
title: this.$t("{group} posts", {
|
||||
group: group.name || usernameWithDomain(group),
|
||||
}) as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
{{ $t("New link") }}
|
||||
</b-dropdown-item>
|
||||
<hr
|
||||
role="presentation"
|
||||
class="dropdown-divider"
|
||||
v-if="config.resourceProviders.length"
|
||||
/>
|
||||
|
|
|
@ -18,12 +18,16 @@
|
|||
<div class="setting-title">
|
||||
<h2>{{ $t("Browser notifications") }}</h2>
|
||||
</div>
|
||||
<b-button v-if="subscribed" @click="unsubscribeToWebPush()">{{
|
||||
$t("Unsubscribe to browser push notifications")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
v-if="subscribed"
|
||||
@click="unsubscribeToWebPush()"
|
||||
@keyup.enter="unsubscribeToWebPush()"
|
||||
>{{ $t("Unsubscribe to browser push notifications") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
icon-left="rss"
|
||||
@click="subscribeToWebPush"
|
||||
@keyup.enter="subscribeToWebPush"
|
||||
v-else-if="canShowWebPush && webPushEnabled"
|
||||
>{{ $t("Activate browser push notifications") }}</b-button
|
||||
>
|
||||
|
@ -247,6 +251,9 @@
|
|||
@click="
|
||||
(e) => copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom')
|
||||
"
|
||||
@keyup.enter="
|
||||
(e) => copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom')
|
||||
"
|
||||
:href="tokenToURL(feedToken.token, 'atom')"
|
||||
target="_blank"
|
||||
>{{ $t("RSS/Atom Feed") }}</b-button
|
||||
|
@ -264,6 +271,9 @@
|
|||
@click="
|
||||
(e) => copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics')
|
||||
"
|
||||
@keyup.enter="
|
||||
(e) => copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics')
|
||||
"
|
||||
icon-left="calendar-sync"
|
||||
:href="tokenToURL(feedToken.token, 'ics')"
|
||||
target="_blank"
|
||||
|
@ -274,6 +284,7 @@
|
|||
icon-left="refresh"
|
||||
type="is-text"
|
||||
@click="openRegenerateFeedTokensConfirmation"
|
||||
@keyup.enter="openRegenerateFeedTokensConfirmation"
|
||||
>{{ $t("Regenerate new links") }}</b-button
|
||||
>
|
||||
</div>
|
||||
|
@ -283,6 +294,7 @@
|
|||
icon-left="refresh"
|
||||
type="is-text"
|
||||
@click="generateFeedTokens"
|
||||
@keyup.enter="generateFeedTokens"
|
||||
>{{ $t("Create new links") }}</b-button
|
||||
>
|
||||
</div>
|
||||
|
@ -333,7 +345,7 @@ type NotificationType = { label: string; subtypes: NotificationSubType[] };
|
|||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Notifications") as string,
|
||||
title: this.$t("Notification settings") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<b-message v-else type="is-danger">{{
|
||||
$t("Unable to detect timezone.")
|
||||
}}</b-message>
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<b-field grouped>
|
||||
<b-field
|
||||
:label="$t('City or region')"
|
||||
|
@ -95,6 +95,7 @@
|
|||
<b-button
|
||||
:disabled="address == undefined"
|
||||
@click="resetArea"
|
||||
@keyup.enter="resetArea"
|
||||
class="reset-area"
|
||||
icon-left="close"
|
||||
:aria-label="$t('Reset')"
|
||||
|
|
|
@ -55,6 +55,14 @@ import RouteName from "../../router/name";
|
|||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { todo } = this;
|
||||
return {
|
||||
title: todo.title,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Todo extends Vue {
|
||||
@Prop({ type: String, required: true }) todoId!: string;
|
||||
|
|
|
@ -69,6 +69,14 @@ import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
|
|||
},
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { todoList } = this;
|
||||
return {
|
||||
title: todoList.title,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class TodoList extends Vue {
|
||||
@Prop({ type: String, required: true }) id!: string;
|
||||
|
|
|
@ -82,6 +82,16 @@ import RouteName from "../../router/name";
|
|||
components: {
|
||||
CompactTodo,
|
||||
},
|
||||
metaInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { group } = this;
|
||||
return {
|
||||
title: this.$t("{group}'s todolists", {
|
||||
group: group.name || usernameWithDomain(group),
|
||||
}) as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class TodoLists extends Vue {
|
||||
@Prop({ type: String, required: true }) preferredUsername!: string;
|
||||
|
|
|
@ -24,7 +24,13 @@ import { VALIDATE_EMAIL } from "../../graphql/user";
|
|||
import RouteName from "../../router/name";
|
||||
import { ICurrentUser } from "../../types/current-user.model";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Validating email") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Validate extends Vue {
|
||||
@Prop({ type: String, required: true }) token!: string;
|
||||
|
||||
|
|
|
@ -50,7 +50,13 @@ import { saveUserData } from "../../utils/auth";
|
|||
import { ILogin } from "../../types/login.model";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Password reset") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class PasswordReset extends Vue {
|
||||
@Prop({ type: String, required: true }) token!: string;
|
||||
|
||||
|
|
|
@ -9,7 +9,13 @@ import RouteName from "../../router/name";
|
|||
import { saveUserData, changeIdentity } from "../../utils/auth";
|
||||
import { IUser } from "../../types/current-user.model";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Redirecting to Mobilizon") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ProviderValidate extends Vue {
|
||||
async mounted(): Promise<void> {
|
||||
const accessToken = this.getValueFromMeta("auth-access-token");
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<router-link class="out" :to="{ name: RouteName.ABOUT }">{{
|
||||
$t("Learn more")
|
||||
}}</router-link>
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<div class="content">
|
||||
<subtitle>{{
|
||||
$t("About {instance}", { instance: config.name })
|
||||
|
@ -170,7 +170,7 @@
|
|||
>
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
<hr role="presentation" />
|
||||
<div
|
||||
class="control"
|
||||
v-if="config && config.auth.oauthProviders.length > 0"
|
||||
|
|
|
@ -56,7 +56,13 @@ import {
|
|||
import { RESEND_CONFIRMATION_EMAIL } from "../../graphql/auth";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Resend confirmation email") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class ResendConfirmation extends Vue {
|
||||
@Prop({ type: String, required: false, default: "" }) email!: string;
|
||||
|
||||
|
|
|
@ -71,7 +71,13 @@ import {
|
|||
import { SEND_RESET_PASSWORD } from "../../graphql/auth";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Reset password") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class SendPasswordReset extends Vue {
|
||||
@Prop({ type: String, required: false, default: "" }) email!: string;
|
||||
|
||||
|
|
|
@ -66,6 +66,11 @@ import { IConfig } from "../../types/config.model";
|
|||
apollo: {
|
||||
config: TIMEZONES,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("First steps") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class SettingsOnboard extends Vue {
|
||||
@Prop({ required: false, default: 1, type: Number }) step!: number;
|
||||
|
|
|
@ -29,7 +29,13 @@ import RouteName from "../../router/name";
|
|||
import { saveUserData, saveTokenData, changeIdentity } from "../../utils/auth";
|
||||
import { ILogin } from "../../types/login.model";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Validating account") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Validate extends Vue {
|
||||
@Prop({ type: String, required: true }) token!: string;
|
||||
|
||||
|
|
10
js/yarn.lock
10
js/yarn.lock
|
@ -2254,6 +2254,16 @@
|
|||
"@typescript-eslint/types" "4.33.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@vue-a11y/announcer@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue-a11y/announcer/-/announcer-2.1.0.tgz#ed725e90b91870c76285840e0aaa637cfafbf27f"
|
||||
integrity sha512-7V1osiJQxZPBA+2duF2nZugwgbIba/yvKLsHWvGIBJGY0VZhR4vYyFH3VFyFH2Yi46tHEVBN+X+a0uQaJMhCsQ==
|
||||
|
||||
"@vue-a11y/skip-to@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue-a11y/skip-to/-/skip-to-2.1.2.tgz#a50f5b97605f5054ca7a7e222bdc721405e38f38"
|
||||
integrity sha512-oKx0YE/nfWSBI48RKHoRsUmpY0//rDPBZTGk1LxhkkUsXPwzuMlJBFrWGIswdd+3DiIE5OyiDaM45ZldYjtDIA==
|
||||
|
||||
"@vue/apollo-option@4.0.0-alpha.11":
|
||||
version "4.0.0-alpha.11"
|
||||
resolved "https://registry.yarnpkg.com/@vue/apollo-option/-/apollo-option-4.0.0-alpha.11.tgz#b4ecac2d1ac40271cb7f20683fb8e4c85974329a"
|
||||
|
|
Loading…
Reference in a new issue