Merge branch 'upgrade-deps' into 'master'
Upgrade deps See merge request framasoft/mobilizon!731
This commit is contained in:
commit
c39a771fd5
@ -158,17 +158,14 @@ config :geolix,
|
||||
}
|
||||
]
|
||||
|
||||
config :auto_linker,
|
||||
opts: [
|
||||
scheme: true,
|
||||
extra: true,
|
||||
# TODO: Set to :no_scheme when it works properly
|
||||
validate_tld: true,
|
||||
class: false,
|
||||
strip_prefix: false,
|
||||
new_window: true,
|
||||
rel: "noopener noreferrer ugc"
|
||||
]
|
||||
config :mobilizon, Mobilizon.Service.Formatter,
|
||||
class: false,
|
||||
rel: "noopener noreferrer ugc",
|
||||
new_window: true,
|
||||
truncate: false,
|
||||
strip_prefix: false,
|
||||
extra: true,
|
||||
validate_tld: :no_scheme
|
||||
|
||||
config :tesla, adapter: Tesla.Adapter.Hackney
|
||||
|
||||
|
@ -4,4 +4,4 @@ indent_size = 2
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
max_line_length = 80
|
||||
|
@ -38,8 +38,11 @@ module.exports = {
|
||||
"error",
|
||||
{
|
||||
ignoreStrings: true,
|
||||
ignoreHTMLTextContents: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreComments: true,
|
||||
template: 170,
|
||||
code: 100,
|
||||
code: 80,
|
||||
},
|
||||
],
|
||||
"prettier/prettier": "error",
|
||||
@ -48,13 +51,18 @@ module.exports = {
|
||||
"import/prefer-default-export": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
},
|
||||
|
||||
ignorePatterns: ["src/typings/*.d.ts", "vue.config.js"],
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
|
||||
files: [
|
||||
"**/__tests__/*.{j,t}s?(x)",
|
||||
"**/tests/unit/**/*.spec.{j,t}s?(x)",
|
||||
],
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
|
@ -24,7 +24,9 @@ fetch(`http://localhost:4000/api`, {
|
||||
.then((result) => result.json())
|
||||
.then((result) => {
|
||||
// here we're filtering out any type information unrelated to unions or interfaces
|
||||
const filteredData = result.data.__schema.types.filter((type) => type.possibleTypes !== null);
|
||||
const filteredData = result.data.__schema.types.filter(
|
||||
(type) => type.possibleTypes !== null
|
||||
);
|
||||
result.data.__schema.types = filteredData;
|
||||
fs.writeFile("./fragmentTypes.json", JSON.stringify(result.data), (err) => {
|
||||
if (err) {
|
||||
|
@ -63,13 +63,13 @@
|
||||
"@types/vuedraggable": "^2.23.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.1",
|
||||
"@typescript-eslint/parser": "^4.0.1",
|
||||
"@vue/cli-plugin-babel": "~4.5.8",
|
||||
"@vue/cli-plugin-e2e-cypress": "~4.5.8",
|
||||
"@vue/cli-plugin-eslint": "~4.5.8",
|
||||
"@vue/cli-plugin-pwa": "~4.5.8",
|
||||
"@vue/cli-plugin-router": "~4.5.8",
|
||||
"@vue/cli-plugin-typescript": "~4.5.8",
|
||||
"@vue/cli-service": "~4.5.8",
|
||||
"@vue/cli-plugin-babel": "~4.5.9",
|
||||
"@vue/cli-plugin-e2e-cypress": "~4.5.9",
|
||||
"@vue/cli-plugin-eslint": "~4.5.9",
|
||||
"@vue/cli-plugin-pwa": "~4.5.9",
|
||||
"@vue/cli-plugin-router": "~4.5.9",
|
||||
"@vue/cli-plugin-typescript": "~4.5.9",
|
||||
"@vue/cli-service": "~4.5.9",
|
||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
@ -79,11 +79,11 @@
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"prettier": "2.1.2",
|
||||
"prettier-eslint": "^11.0.0",
|
||||
"prettier": "2.2.1",
|
||||
"prettier-eslint": "^12.0.0",
|
||||
"sass": "^1.29.0",
|
||||
"sass-loader": "^10.0.1",
|
||||
"typescript": "~4.0.2",
|
||||
"typescript": "~4.1.2",
|
||||
"vue-cli-plugin-svg": "~0.1.3",
|
||||
"vue-i18n-extract": "^1.0.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
|
18
js/src/@types/dom.d.ts
vendored
Normal file
18
js/src/@types/dom.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
declare global {
|
||||
interface GeolocationCoordinates {
|
||||
readonly accuracy: number;
|
||||
readonly altitude: number | null;
|
||||
readonly altitudeAccuracy: number | null;
|
||||
readonly heading: number | null;
|
||||
readonly latitude: number;
|
||||
readonly longitude: number;
|
||||
readonly speed: number | null;
|
||||
}
|
||||
|
||||
interface GeolocationPosition {
|
||||
readonly coords: GeolocationCoordinates;
|
||||
readonly timestamp: number;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
@ -32,8 +32,16 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import NavBar from "./components/NavBar.vue";
|
||||
import { AUTH_ACCESS_TOKEN, AUTH_USER_EMAIL, AUTH_USER_ID, AUTH_USER_ROLE } from "./constants";
|
||||
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from "./graphql/user";
|
||||
import {
|
||||
AUTH_ACCESS_TOKEN,
|
||||
AUTH_USER_EMAIL,
|
||||
AUTH_USER_ID,
|
||||
AUTH_USER_ROLE,
|
||||
} from "./constants";
|
||||
import {
|
||||
CURRENT_USER_CLIENT,
|
||||
UPDATE_CURRENT_USER_CLIENT,
|
||||
} from "./graphql/user";
|
||||
import Footer from "./components/Footer.vue";
|
||||
import Logo from "./components/Logo.vue";
|
||||
import { initializeCurrentActor } from "./utils/auth";
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { ICurrentUserRole } from "@/types/enums";
|
||||
import { ApolloCache } from "apollo-cache";
|
||||
import { NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||
import { ICurrentUserRole } from "@/types/current-user.model";
|
||||
import { Resolvers } from "apollo-client/core/types";
|
||||
|
||||
export default function buildCurrentUserResolver(cache: ApolloCache<NormalizedCacheObject>) {
|
||||
export default function buildCurrentUserResolver(
|
||||
cache: ApolloCache<NormalizedCacheObject>
|
||||
): Resolvers {
|
||||
cache.writeData({
|
||||
data: {
|
||||
currentUser: {
|
||||
@ -53,7 +56,12 @@ export default function buildCurrentUserResolver(cache: ApolloCache<NormalizedCa
|
||||
preferredUsername,
|
||||
avatar,
|
||||
name,
|
||||
}: { id: string; preferredUsername: string; avatar: string; name: string },
|
||||
}: {
|
||||
id: string;
|
||||
preferredUsername: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
},
|
||||
{ cache: localCache }: { cache: ApolloCache<NormalizedCacheObject> }
|
||||
) => {
|
||||
const data = {
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { IntrospectionFragmentMatcher, NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||
import {
|
||||
IntrospectionFragmentMatcher,
|
||||
NormalizedCacheObject,
|
||||
} from "apollo-cache-inmemory";
|
||||
import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from "@/constants";
|
||||
import { REFRESH_TOKEN } from "@/graphql/auth";
|
||||
import { saveTokenData } from "@/utils/auth";
|
||||
@ -11,7 +14,11 @@ export const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||
{
|
||||
kind: "UNION",
|
||||
name: "SearchResult",
|
||||
possibleTypes: [{ name: "Event" }, { name: "Person" }, { name: "Group" }],
|
||||
possibleTypes: [
|
||||
{ name: "Event" },
|
||||
{ name: "Person" },
|
||||
{ name: "Group" },
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "INTERFACE",
|
||||
|
@ -13,7 +13,12 @@
|
||||
<template slot-scope="props">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<img width="32" :src="props.option.avatar.url" v-if="props.option.avatar" alt="" />
|
||||
<img
|
||||
width="32"
|
||||
:src="props.option.avatar.url"
|
||||
v-if="props.option.avatar"
|
||||
alt=""
|
||||
/>
|
||||
<b-icon v-else icon="account-circle" />
|
||||
</div>
|
||||
<div class="media-content">
|
||||
@ -21,7 +26,9 @@
|
||||
{{ props.option.name }}
|
||||
<br />
|
||||
<small>{{ `@${props.option.preferredUsername}` }}</small>
|
||||
<small v-if="props.option.domain">{{ `@${props.option.domain}` }}</small>
|
||||
<small v-if="props.option.domain">{{
|
||||
`@${props.option.domain}`
|
||||
}}</small>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ `@${props.option.preferredUsername}` }}
|
||||
@ -53,7 +60,9 @@ export default class ActorAutoComplete extends Vue {
|
||||
|
||||
selected: IPerson | null = this.defaultSelected;
|
||||
|
||||
name: string = this.defaultSelected ? this.defaultSelected.preferredUsername : "";
|
||||
name: string = this.defaultSelected
|
||||
? this.defaultSelected.preferredUsername
|
||||
: "";
|
||||
|
||||
page = 1;
|
||||
|
||||
|
@ -12,8 +12,15 @@
|
||||
<p>
|
||||
{{ actor.name || `@${usernameWithDomain(actor)}` }}
|
||||
</p>
|
||||
<p class="has-text-grey" v-if="actor.name">@{{ usernameWithDomain(actor) }}</p>
|
||||
<div v-if="full" class="summary" :class="{ limit: limit }" v-html="actor.summary" />
|
||||
<p class="has-text-grey" v-if="actor.name">
|
||||
@{{ usernameWithDomain(actor) }}
|
||||
</p>
|
||||
<div
|
||||
v-if="full"
|
||||
class="summary"
|
||||
:class="{ limit: limit }"
|
||||
v-html="actor.summary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,10 @@
|
||||
<ul class="identities">
|
||||
<li v-for="identity in identities" :key="identity.id">
|
||||
<router-link
|
||||
:to="{ name: 'UpdateIdentity', params: { identityName: identity.preferredUsername } }"
|
||||
:to="{
|
||||
name: 'UpdateIdentity',
|
||||
params: { identityName: identity.preferredUsername },
|
||||
}"
|
||||
class="media identity"
|
||||
v-bind:class="{ 'is-current-identity': isCurrentIdentity(identity) }"
|
||||
>
|
||||
@ -24,7 +27,10 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<router-link :to="{ name: 'CreateIdentity' }" class="button create-identity is-primary">
|
||||
<router-link
|
||||
:to="{ name: 'CreateIdentity' }"
|
||||
class="button create-identity is-primary"
|
||||
>
|
||||
{{ $t("Create a new identity") }}
|
||||
</router-link>
|
||||
</section>
|
||||
@ -53,7 +59,7 @@ export default class Identities extends Vue {
|
||||
|
||||
errors: string[] = [];
|
||||
|
||||
isCurrentIdentity(identity: IPerson) {
|
||||
isCurrentIdentity(identity: IPerson): boolean {
|
||||
return identity.preferredUsername === this.currentIdentityName;
|
||||
}
|
||||
}
|
||||
|
@ -1,94 +0,0 @@
|
||||
<docs>
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'user1', name: 'someoneIDontLike' }, role: 'REJECTED' }" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'user2', name: 'someoneWhoWillWait' }, role: 'NOT_APPROVED' }" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'user3', name: 'a_participant' }, role: 'PARTICIPANT' }" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'me', name: 'myself' }, role: 'CREATOR' }" />
|
||||
```
|
||||
</docs>
|
||||
<template>
|
||||
<article class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left" v-if="participant.actor.avatar">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="participant.actor.avatar.url" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span ref="title">{{ actorDisplayName }}</span
|
||||
><br />
|
||||
<small class="has-text-grey" v-if="participant.actor.domain"
|
||||
>@{{ participant.actor.preferredUsername }}@{{ participant.actor.domain }}</small
|
||||
>
|
||||
<small class="has-text-grey" v-else>@{{ participant.actor.preferredUsername }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<b-button
|
||||
v-if="[ParticipantRole.NOT_APPROVED, ParticipantRole.REJECTED].includes(participant.role)"
|
||||
@click="accept(participant)"
|
||||
type="is-success"
|
||||
class="card-footer-item"
|
||||
>{{ $t("Approve") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
v-if="participant.role === ParticipantRole.NOT_APPROVED"
|
||||
@click="reject(participant)"
|
||||
type="is-danger"
|
||||
class="card-footer-item"
|
||||
>{{ $t("Reject") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
v-if="participant.role === ParticipantRole.PARTICIPANT"
|
||||
@click="exclude(participant)"
|
||||
type="is-danger"
|
||||
class="card-footer-item"
|
||||
>{{ $t("Exclude") }}</b-button
|
||||
>
|
||||
<span v-if="participant.role === ParticipantRole.CREATOR" class="card-footer-item">{{
|
||||
$t("Creator")
|
||||
}}</span>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IParticipant, ParticipantRole } from "../../types/participant.model";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
|
||||
@Component
|
||||
export default class ParticipantCard extends Vue {
|
||||
@Prop({ required: true }) participant!: IParticipant;
|
||||
|
||||
@Prop({ type: Function }) accept!: Function;
|
||||
|
||||
@Prop({ type: Function }) reject!: Function;
|
||||
|
||||
@Prop({ type: Function }) exclude!: Function;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
get actorDisplayName(): string {
|
||||
const actor = new Person(this.participant.actor as IPerson);
|
||||
return actor.displayName();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.card-footer-item {
|
||||
height: $control-height;
|
||||
}
|
||||
</style>
|
@ -12,8 +12,9 @@
|
||||
</v-popover>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ActorType } from "@/types/enums";
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { IActor, ActorType } from "../../types/actor";
|
||||
import { IActor } from "../../types/actor";
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
|
||||
@Component({
|
||||
|
@ -16,11 +16,21 @@
|
||||
checkable
|
||||
checkbox-position="left"
|
||||
>
|
||||
<b-table-column field="actor.id" label="ID" width="40" numeric v-slot="props">{{
|
||||
props.row.actor.id
|
||||
}}</b-table-column>
|
||||
<b-table-column
|
||||
field="actor.id"
|
||||
label="ID"
|
||||
width="40"
|
||||
numeric
|
||||
v-slot="props"
|
||||
>{{ props.row.actor.id }}</b-table-column
|
||||
>
|
||||
|
||||
<b-table-column field="actor.type" :label="$t('Type')" width="80" v-slot="props">
|
||||
<b-table-column
|
||||
field="actor.type"
|
||||
:label="$t('Type')"
|
||||
width="80"
|
||||
v-slot="props"
|
||||
>
|
||||
<b-icon icon="lan" v-if="RelayMixin.isInstance(props.row.actor)" />
|
||||
<b-icon icon="account-circle" v-else />
|
||||
</b-table-column>
|
||||
@ -33,26 +43,39 @@
|
||||
centered
|
||||
v-slot="props"
|
||||
>
|
||||
<span :class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`">{{
|
||||
props.row.approved ? $t("Accepted") : $t("Pending")
|
||||
}}</span>
|
||||
<span
|
||||
:class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`"
|
||||
>{{ props.row.approved ? $t("Accepted") : $t("Pending") }}</span
|
||||
>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="actor.domain" :label="$t('Domain')" sortable>
|
||||
<template v-slot:default="props">
|
||||
<a @click="toggle(props.row)" v-if="RelayMixin.isInstance(props.row.actor)">{{
|
||||
props.row.actor.domain
|
||||
}}</a>
|
||||
<a
|
||||
@click="toggle(props.row)"
|
||||
v-if="RelayMixin.isInstance(props.row.actor)"
|
||||
>{{ props.row.actor.domain }}</a
|
||||
>
|
||||
<a @click="toggle(props.row)" v-else>{{
|
||||
`${props.row.actor.preferredUsername}@${props.row.actor.domain}`
|
||||
}}</a>
|
||||
</template>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="targetActor.updatedAt" :label="$t('Date')" sortable v-slot="props">
|
||||
<span :title="$options.filters.formatDateTimeString(props.row.updatedAt)">{{
|
||||
formatDistanceToNow(new Date(props.row.updatedAt), { locale: $dateFnsLocale })
|
||||
}}</span></b-table-column
|
||||
<b-table-column
|
||||
field="targetActor.updatedAt"
|
||||
:label="$t('Date')"
|
||||
sortable
|
||||
v-slot="props"
|
||||
>
|
||||
<span
|
||||
:title="$options.filters.formatDateTimeString(props.row.updatedAt)"
|
||||
>{{
|
||||
formatDistanceToNow(new Date(props.row.updatedAt), {
|
||||
locale: $dateFnsLocale,
|
||||
})
|
||||
}}</span
|
||||
></b-table-column
|
||||
>
|
||||
|
||||
<template slot="detail" slot-scope="props">
|
||||
@ -143,7 +166,11 @@ export default class Followers extends Mixins(RelayMixin) {
|
||||
await this.$apollo.queries.relayFollowers.refetch();
|
||||
this.checkedRows = [];
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +185,11 @@ export default class Followers extends Mixins(RelayMixin) {
|
||||
await this.$apollo.queries.relayFollowers.refetch();
|
||||
this.checkedRows = [];
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<form @submit="followRelay">
|
||||
<b-field :label="$t('Add an instance')" custom-class="add-relay" horizontal>
|
||||
<b-field
|
||||
:label="$t('Add an instance')"
|
||||
custom-class="add-relay"
|
||||
horizontal
|
||||
>
|
||||
<b-field grouped expanded size="is-large">
|
||||
<p class="control">
|
||||
<b-input v-model="newRelayAddress" :placeholder="$t('Ex: mobilizon.fr')" />
|
||||
<b-input
|
||||
v-model="newRelayAddress"
|
||||
:placeholder="$t('Ex: mobilizon.fr')"
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<b-button type="is-primary" native-type="submit">{{ $t("Add an instance") }}</b-button>
|
||||
<b-button type="is-primary" native-type="submit">{{
|
||||
$t("Add an instance")
|
||||
}}</b-button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
@ -29,12 +38,25 @@
|
||||
checkable
|
||||
checkbox-position="left"
|
||||
>
|
||||
<b-table-column field="targetActor.id" label="ID" width="40" numeric v-slot="props">{{
|
||||
props.row.targetActor.id
|
||||
}}</b-table-column>
|
||||
<b-table-column
|
||||
field="targetActor.id"
|
||||
label="ID"
|
||||
width="40"
|
||||
numeric
|
||||
v-slot="props"
|
||||
>{{ props.row.targetActor.id }}</b-table-column
|
||||
>
|
||||
|
||||
<b-table-column field="targetActor.type" :label="$t('Type')" width="80" v-slot="props">
|
||||
<b-icon icon="lan" v-if="RelayMixin.isInstance(props.row.targetActor)" />
|
||||
<b-table-column
|
||||
field="targetActor.type"
|
||||
:label="$t('Type')"
|
||||
width="80"
|
||||
v-slot="props"
|
||||
>
|
||||
<b-icon
|
||||
icon="lan"
|
||||
v-if="RelayMixin.isInstance(props.row.targetActor)"
|
||||
/>
|
||||
<b-icon icon="account-circle" v-else />
|
||||
</b-table-column>
|
||||
|
||||
@ -46,26 +68,39 @@
|
||||
centered
|
||||
v-slot="props"
|
||||
>
|
||||
<span :class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`">{{
|
||||
props.row.approved ? $t("Accepted") : $t("Pending")
|
||||
}}</span>
|
||||
<span
|
||||
:class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`"
|
||||
>{{ props.row.approved ? $t("Accepted") : $t("Pending") }}</span
|
||||
>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="targetActor.domain" :label="$t('Domain')" sortable>
|
||||
<template v-slot:default="props">
|
||||
<a @click="toggle(props.row)" v-if="RelayMixin.isInstance(props.row.targetActor)">{{
|
||||
props.row.targetActor.domain
|
||||
}}</a>
|
||||
<a
|
||||
@click="toggle(props.row)"
|
||||
v-if="RelayMixin.isInstance(props.row.targetActor)"
|
||||
>{{ props.row.targetActor.domain }}</a
|
||||
>
|
||||
<a @click="toggle(props.row)" v-else>{{
|
||||
`${props.row.targetActor.preferredUsername}@${props.row.targetActor.domain}`
|
||||
}}</a>
|
||||
</template>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="targetActor.updatedAt" :label="$t('Date')" sortable v-slot="props">
|
||||
<span :title="$options.filters.formatDateTimeString(props.row.updatedAt)">{{
|
||||
formatDistanceToNow(new Date(props.row.updatedAt), { locale: $dateFnsLocale })
|
||||
}}</span></b-table-column
|
||||
<b-table-column
|
||||
field="targetActor.updatedAt"
|
||||
:label="$t('Date')"
|
||||
sortable
|
||||
v-slot="props"
|
||||
>
|
||||
<span
|
||||
:title="$options.filters.formatDateTimeString(props.row.updatedAt)"
|
||||
>{{
|
||||
formatDistanceToNow(new Date(props.row.updatedAt), {
|
||||
locale: $dateFnsLocale,
|
||||
})
|
||||
}}</span
|
||||
></b-table-column
|
||||
>
|
||||
|
||||
<template slot="detail" slot-scope="props">
|
||||
@ -103,7 +138,6 @@ import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { ADD_RELAY, REMOVE_RELAY } from "../../graphql/admin";
|
||||
import { IFollower } from "../../types/actor/follower.model";
|
||||
import { Paginate } from "../../types/paginate";
|
||||
import RelayMixin from "../../mixins/relay";
|
||||
|
||||
@Component({
|
||||
@ -133,13 +167,19 @@ export default class Followings extends Mixins(RelayMixin) {
|
||||
await this.$apollo.queries.relayFollowings.refetch();
|
||||
this.newRelayAddress = "";
|
||||
} catch (err) {
|
||||
Snackbar.open({ message: err.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: err.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async removeRelays(): Promise<void> {
|
||||
await this.checkedRows.forEach((row: IFollower) => {
|
||||
this.removeRelay(`${row.targetActor.preferredUsername}@${row.targetActor.domain}`);
|
||||
this.removeRelay(
|
||||
`${row.targetActor.preferredUsername}@${row.targetActor.domain}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -154,7 +194,11 @@ export default class Followings extends Mixins(RelayMixin) {
|
||||
await this.$apollo.queries.relayFollowings.refetch();
|
||||
this.checkedRows = [];
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,34 @@
|
||||
<template>
|
||||
<li :class="{ reply: comment.inReplyToComment }">
|
||||
<article class="media" :class="{ selected: commentSelected }" :id="commentId">
|
||||
<article
|
||||
class="media"
|
||||
:class="{ selected: commentSelected }"
|
||||
:id="commentId"
|
||||
>
|
||||
<popover-actor-card
|
||||
class="media-left"
|
||||
:actor="comment.actor"
|
||||
:inline="true"
|
||||
v-if="comment.actor"
|
||||
>
|
||||
<figure class="image is-48x48" v-if="!comment.deletedAt && comment.actor.avatar">
|
||||
<figure
|
||||
class="image is-48x48"
|
||||
v-if="!comment.deletedAt && comment.actor.avatar"
|
||||
>
|
||||
<img class="is-rounded" :src="comment.actor.avatar.url" alt="" />
|
||||
</figure>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<b-icon
|
||||
class="media-left"
|
||||
v-else
|
||||
size="is-large"
|
||||
icon="account-circle"
|
||||
/>
|
||||
</popover-actor-card>
|
||||
<div v-else class="media-left">
|
||||
<figure class="image is-48x48" v-if="!comment.deletedAt && comment.actor.avatar">
|
||||
<figure
|
||||
class="image is-48x48"
|
||||
v-if="!comment.deletedAt && comment.actor.avatar"
|
||||
>
|
||||
<img class="is-rounded" :src="comment.actor.avatar.url" alt="" />
|
||||
</figure>
|
||||
<b-icon v-else size="is-large" icon="account-circle" />
|
||||
@ -21,7 +36,9 @@
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<span class="first-line" v-if="!comment.deletedAt">
|
||||
<strong :class="{ organizer: commentFromOrganizer }">{{ comment.actor.name }}</strong>
|
||||
<strong :class="{ organizer: commentFromOrganizer }">{{
|
||||
comment.actor.name
|
||||
}}</strong>
|
||||
<small>@{{ usernameWithDomain(comment.actor) }}</small>
|
||||
<a class="comment-link has-text-grey" :href="commentURL">
|
||||
<small>{{
|
||||
@ -54,10 +71,15 @@
|
||||
<div class="load-replies" v-if="comment.totalReplies">
|
||||
<p v-if="!showReplies" @click="fetchReplies">
|
||||
<b-icon icon="chevron-down" /><span>{{
|
||||
$tc("View a reply", comment.totalReplies, { totalReplies: comment.totalReplies })
|
||||
$tc("View a reply", comment.totalReplies, {
|
||||
totalReplies: comment.totalReplies,
|
||||
})
|
||||
}}</span>
|
||||
</p>
|
||||
<p v-else-if="comment.totalReplies && showReplies" @click="showReplies = false">
|
||||
<p
|
||||
v-else-if="comment.totalReplies && showReplies"
|
||||
@click="showReplies = false"
|
||||
>
|
||||
<b-icon icon="chevron-up" />
|
||||
<span>{{ $t("Hide replies") }}</span>
|
||||
</p>
|
||||
@ -86,14 +108,24 @@
|
||||
</nav>
|
||||
</div>
|
||||
</article>
|
||||
<form class="reply" @submit.prevent="replyToComment" v-if="currentActor.id" v-show="replyTo">
|
||||
<form
|
||||
class="reply"
|
||||
@submit.prevent="replyToComment"
|
||||
v-if="currentActor.id"
|
||||
v-show="replyTo"
|
||||
>
|
||||
<article class="media reply">
|
||||
<figure class="media-left" v-if="currentActor.avatar">
|
||||
<p class="image is-48x48">
|
||||
<img :src="currentActor.avatar.url" alt="" />
|
||||
</p>
|
||||
</figure>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<b-icon
|
||||
class="media-left"
|
||||
v-else
|
||||
size="is-large"
|
||||
icon="account-circle"
|
||||
/>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<span class="first-line">
|
||||
@ -102,7 +134,12 @@
|
||||
</span>
|
||||
<br />
|
||||
<span class="editor-line">
|
||||
<editor class="editor" ref="commentEditor" v-model="newComment.text" mode="comment" />
|
||||
<editor
|
||||
class="editor"
|
||||
ref="commentEditor"
|
||||
v-model="newComment.text"
|
||||
mode="comment"
|
||||
/>
|
||||
<b-button
|
||||
:disabled="newComment.text.trim().length === 0"
|
||||
native-type="submit"
|
||||
@ -118,7 +155,12 @@
|
||||
<div class="left">
|
||||
<div class="vertical-border" @click="showReplies = false" />
|
||||
</div>
|
||||
<transition-group name="comment-replies" v-if="showReplies" class="comment-replies" tag="ul">
|
||||
<transition-group
|
||||
name="comment-replies"
|
||||
v-if="showReplies"
|
||||
class="comment-replies"
|
||||
tag="ul"
|
||||
>
|
||||
<comment
|
||||
class="reply"
|
||||
v-for="reply in comment.replies"
|
||||
@ -137,7 +179,7 @@ import { Component, Prop, Vue, Ref } from "vue-property-decorator";
|
||||
import EditorComponent from "@/components/Editor.vue";
|
||||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { CommentModeration } from "../../types/event-options.model";
|
||||
import { CommentModeration } from "@/types/enums";
|
||||
import { CommentModel, IComment } from "../../types/comment.model";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||
import { IPerson, usernameWithDomain } from "../../types/actor";
|
||||
@ -155,7 +197,8 @@ import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
},
|
||||
},
|
||||
components: {
|
||||
editor: () => import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
|
||||
editor: () =>
|
||||
import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
|
||||
comment: () => import(/* webpackChunkName: "comment" */ "./Comment.vue"),
|
||||
PopoverActorCard,
|
||||
},
|
||||
@ -167,7 +210,9 @@ export default class Comment extends Vue {
|
||||
|
||||
// Hack because Vue only exports it's own interface.
|
||||
// See https://github.com/kaorun343/vue-property-decorator/issues/257
|
||||
@Ref() readonly commentEditor!: EditorComponent & { replyToComment: (comment: IComment) => void };
|
||||
@Ref() readonly commentEditor!: EditorComponent & {
|
||||
replyToComment: (comment: IComment) => void;
|
||||
};
|
||||
|
||||
currentActor!: IPerson;
|
||||
|
||||
@ -231,7 +276,9 @@ export default class Comment extends Vue {
|
||||
if (!eventData) return;
|
||||
const { event } = eventData;
|
||||
const { comments } = event;
|
||||
const parentCommentIndex = comments.findIndex((oldComment) => oldComment.id === parentId);
|
||||
const parentCommentIndex = comments.findIndex(
|
||||
(oldComment) => oldComment.id === parentId
|
||||
);
|
||||
const parentComment = comments[parentCommentIndex];
|
||||
if (!parentComment) return;
|
||||
parentComment.replies = thread;
|
||||
@ -303,7 +350,11 @@ export default class Comment extends Vue {
|
||||
duration: 5000,
|
||||
});
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,11 @@
|
||||
@submit.prevent="createCommentForEvent(newComment)"
|
||||
@keyup.ctrl.enter="createCommentForEvent(newComment)"
|
||||
>
|
||||
<b-notification v-if="isEventOrganiser && !areCommentsClosed" :closable="false">{{
|
||||
$t("Comments are closed for everybody else.")
|
||||
}}</b-notification>
|
||||
<b-notification
|
||||
v-if="isEventOrganiser && !areCommentsClosed"
|
||||
:closable="false"
|
||||
>{{ $t("Comments are closed for everybody else.") }}</b-notification
|
||||
>
|
||||
<article class="media">
|
||||
<figure class="media-left">
|
||||
<identity-picker-wrapper :inline="false" v-model="newComment.actor" />
|
||||
@ -16,11 +18,17 @@
|
||||
<div class="media-content">
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<editor ref="commenteditor" mode="comment" v-model="newComment.text" />
|
||||
<editor
|
||||
ref="commenteditor"
|
||||
mode="comment"
|
||||
v-model="newComment.text"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="send-comment">
|
||||
<b-button native-type="submit" type="is-primary">{{ $t("Post a comment") }}</b-button>
|
||||
<b-button native-type="submit" type="is-primary">{{
|
||||
$t("Post a comment")
|
||||
}}</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@ -29,7 +37,12 @@
|
||||
$t("The organiser has chosen to close comments.")
|
||||
}}</b-notification>
|
||||
<transition name="comment-empty-list" mode="out-in">
|
||||
<transition-group name="comment-list" v-if="comments.length" class="comment-list" tag="ul">
|
||||
<transition-group
|
||||
name="comment-list"
|
||||
v-if="comments.length"
|
||||
class="comment-list"
|
||||
tag="ul"
|
||||
>
|
||||
<comment
|
||||
class="root-comment"
|
||||
:comment="comment"
|
||||
@ -51,7 +64,7 @@
|
||||
import { Prop, Vue, Component, Watch } from "vue-property-decorator";
|
||||
import Comment from "@/components/Comment/Comment.vue";
|
||||
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
|
||||
import { CommentModeration } from "../../types/event-options.model";
|
||||
import { CommentModeration } from "@/types/enums";
|
||||
import { CommentModel, IComment } from "../../types/comment.model";
|
||||
import {
|
||||
CREATE_COMMENT_FROM_EVENT,
|
||||
@ -76,7 +89,9 @@ import { IEvent } from "../../types/event.model";
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return data.event.comments.map((comment: IComment) => new CommentModel(comment));
|
||||
return data.event.comments.map(
|
||||
(comment: IComment) => new CommentModel(comment)
|
||||
);
|
||||
},
|
||||
skip() {
|
||||
return !this.event.uuid;
|
||||
@ -86,7 +101,8 @@ import { IEvent } from "../../types/event.model";
|
||||
components: {
|
||||
Comment,
|
||||
IdentityPickerWrapper,
|
||||
editor: () => import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
|
||||
editor: () =>
|
||||
import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
|
||||
},
|
||||
})
|
||||
export default class CommentTree extends Vue {
|
||||
@ -113,7 +129,9 @@ export default class CommentTree extends Vue {
|
||||
variables: {
|
||||
eventId: this.event.id,
|
||||
text: comment.text,
|
||||
inReplyToCommentId: comment.inReplyToComment ? comment.inReplyToComment.id : null,
|
||||
inReplyToCommentId: comment.inReplyToComment
|
||||
? comment.inReplyToComment.id
|
||||
: null,
|
||||
},
|
||||
update: (store, { data }) => {
|
||||
if (data == null) return;
|
||||
@ -228,7 +246,9 @@ export default class CommentTree extends Vue {
|
||||
});
|
||||
if (!localData) return;
|
||||
const { thread: oldReplyList } = localData;
|
||||
const replies = oldReplyList.filter((reply) => reply.id !== deletedCommentId);
|
||||
const replies = oldReplyList.filter(
|
||||
(reply) => reply.id !== deletedCommentId
|
||||
);
|
||||
store.writeQuery({
|
||||
query: FETCH_THREAD_REPLIES,
|
||||
variables: {
|
||||
@ -249,7 +269,9 @@ export default class CommentTree extends Vue {
|
||||
event.comments = oldComments;
|
||||
} else {
|
||||
// we have deleted a thread itself
|
||||
event.comments = oldComments.filter((reply) => reply.id !== deletedCommentId);
|
||||
event.comments = oldComments.filter(
|
||||
(reply) => reply.id !== deletedCommentId
|
||||
);
|
||||
}
|
||||
store.writeQuery({
|
||||
query: COMMENTS_THREADS,
|
||||
@ -274,14 +296,18 @@ export default class CommentTree extends Vue {
|
||||
.filter((comment) => comment.inReplyToComment == null)
|
||||
.sort((a, b) => {
|
||||
if (a.updatedAt && b.updatedAt) {
|
||||
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
|
||||
return (
|
||||
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
get filteredOrderedComments(): IComment[] {
|
||||
return this.orderedComments.filter((comment) => !comment.deletedAt || comment.totalReplies > 0);
|
||||
return this.orderedComments.filter(
|
||||
(comment) => !comment.deletedAt || comment.totalReplies > 0
|
||||
);
|
||||
}
|
||||
|
||||
get isEventOrganiser(): boolean {
|
||||
|
@ -15,7 +15,10 @@
|
||||
<span v-else class="name comment-link has-text-grey">
|
||||
{{ $t("[deleted]") }}
|
||||
</span>
|
||||
<span class="icons" v-if="!comment.deletedAt && comment.actor.id === currentActor.id">
|
||||
<span
|
||||
class="icons"
|
||||
v-if="!comment.deletedAt && comment.actor.id === currentActor.id"
|
||||
>
|
||||
<b-dropdown aria-role="list">
|
||||
<b-icon slot="trigger" role="button" icon="dots-horizontal" />
|
||||
|
||||
@ -44,8 +47,9 @@
|
||||
<div class="post-infos">
|
||||
<span :title="comment.insertedAt | formatDateTimeString">
|
||||
{{
|
||||
formatDistanceToNow(new Date(comment.updatedAt), { locale: $dateFnsLocale }) ||
|
||||
$t("Right now")
|
||||
formatDistanceToNow(new Date(comment.updatedAt), {
|
||||
locale: $dateFnsLocale,
|
||||
}) || $t("Right now")
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
@ -77,7 +81,9 @@
|
||||
type="is-primary"
|
||||
>{{ $t("Update") }}</b-button
|
||||
>
|
||||
<b-button native-type="button" @click="toggleEditMode">{{ $t("Cancel") }}</b-button>
|
||||
<b-button native-type="button" @click="toggleEditMode">{{
|
||||
$t("Cancel")
|
||||
}}</b-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -95,7 +101,8 @@ import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
components: {
|
||||
editor: () => import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
|
||||
editor: () =>
|
||||
import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
|
||||
},
|
||||
})
|
||||
export default class DiscussionComment extends Vue {
|
||||
|
@ -1,14 +1,23 @@
|
||||
<template>
|
||||
<router-link
|
||||
class="discussion-minimalist-card-wrapper"
|
||||
:to="{ name: RouteName.DISCUSSION, params: { slug: discussion.slug, id: discussion.id } }"
|
||||
:to="{
|
||||
name: RouteName.DISCUSSION,
|
||||
params: { slug: discussion.slug, id: discussion.id },
|
||||
}"
|
||||
>
|
||||
<div class="media-left">
|
||||
<figure
|
||||
class="image is-32x32"
|
||||
v-if="discussion.lastComment.actor && discussion.lastComment.actor.avatar"
|
||||
v-if="
|
||||
discussion.lastComment.actor && discussion.lastComment.actor.avatar
|
||||
"
|
||||
>
|
||||
<img class="is-rounded" :src="discussion.lastComment.actor.avatar.url" alt />
|
||||
<img
|
||||
class="is-rounded"
|
||||
:src="discussion.lastComment.actor.avatar.url"
|
||||
alt
|
||||
/>
|
||||
</figure>
|
||||
<b-icon v-else size="is-medium" icon="account-circle" />
|
||||
</div>
|
||||
@ -17,15 +26,18 @@
|
||||
<p class="discussion-minimalist-title">{{ discussion.title }}</p>
|
||||
<span :title="actualDate | formatDateTimeString">
|
||||
{{
|
||||
formatDistanceToNowStrict(new Date(actualDate), { locale: $dateFnsLocale }) ||
|
||||
$t("Right now")
|
||||
formatDistanceToNowStrict(new Date(actualDate), {
|
||||
locale: $dateFnsLocale,
|
||||
}) || $t("Right now")
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
<div class="has-text-grey" v-if="!discussion.lastComment.deletedAt">
|
||||
{{ htmlTextEllipsis }}
|
||||
</div>
|
||||
<div v-else class="has-text-grey">{{ $t("[This comment has been deleted]") }}</div>
|
||||
<div v-else class="has-text-grey">
|
||||
{{ $t("[This comment has been deleted]") }}
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
@ -54,7 +66,10 @@ export default class DiscussionListItem extends Vue {
|
||||
}
|
||||
|
||||
get actualDate(): string | Date | undefined {
|
||||
if (this.discussion.updatedAt === this.discussion.insertedAt && this.discussion.lastComment) {
|
||||
if (
|
||||
this.discussion.updatedAt === this.discussion.insertedAt &&
|
||||
this.discussion.lastComment
|
||||
) {
|
||||
return this.discussion.lastComment.publishedAt;
|
||||
}
|
||||
return this.discussion.updatedAt;
|
||||
@ -83,7 +98,8 @@ export default class DiscussionListItem extends Vue {
|
||||
|
||||
.discussion-minimalist-title {
|
||||
color: #3c376e;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica,
|
||||
Arial, serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
flex: 1;
|
||||
|
@ -117,11 +117,21 @@
|
||||
<b-icon icon="format-quote-close" />
|
||||
</button>
|
||||
|
||||
<button v-if="!isBasicMode" class="menubar__button" @click="commands.undo" type="button">
|
||||
<button
|
||||
v-if="!isBasicMode"
|
||||
class="menubar__button"
|
||||
@click="commands.undo"
|
||||
type="button"
|
||||
>
|
||||
<b-icon icon="undo" />
|
||||
</button>
|
||||
|
||||
<button v-if="!isBasicMode" class="menubar__button" @click="commands.redo" type="button">
|
||||
<button
|
||||
v-if="!isBasicMode"
|
||||
class="menubar__button"
|
||||
@click="commands.redo"
|
||||
type="button"
|
||||
>
|
||||
<b-icon icon="redo" />
|
||||
</button>
|
||||
</div>
|
||||
@ -181,7 +191,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="suggestion-list__item is-empty">{{ $t("No profiles found") }}</div>
|
||||
<div v-else class="suggestion-list__item is-empty">
|
||||
{{ $t("No profiles found") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -395,15 +407,7 @@ export default class EditorComponent extends Vue {
|
||||
new Image(),
|
||||
new MaxSize({ maxSize: this.maxSize }),
|
||||
],
|
||||
onUpdate: ({
|
||||
getHTML,
|
||||
transaction,
|
||||
getJSON,
|
||||
}: {
|
||||
getHTML: Function;
|
||||
getJSON: Function;
|
||||
transaction: unknown;
|
||||
}) => {
|
||||
onUpdate: ({ getHTML }: { getHTML: Function }) => {
|
||||
this.$emit("input", getHTML());
|
||||
},
|
||||
});
|
||||
@ -438,7 +442,8 @@ export default class EditorComponent extends Vue {
|
||||
|
||||
upHandler(): void {
|
||||
this.navigatedActorIndex =
|
||||
(this.navigatedActorIndex + this.filteredActors.length - 1) % this.filteredActors.length;
|
||||
(this.navigatedActorIndex + this.filteredActors.length - 1) %
|
||||
this.filteredActors.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -446,7 +451,8 @@ export default class EditorComponent extends Vue {
|
||||
* if it's the last item, navigate to the first one
|
||||
*/
|
||||
downHandler(): void {
|
||||
this.navigatedActorIndex = (this.navigatedActorIndex + 1) % this.filteredActors.length;
|
||||
this.navigatedActorIndex =
|
||||
(this.navigatedActorIndex + 1) % this.filteredActors.length;
|
||||
}
|
||||
|
||||
enterHandler(): void {
|
||||
@ -541,7 +547,10 @@ export default class EditorComponent extends Vue {
|
||||
},
|
||||
});
|
||||
if (data.uploadMedia && data.uploadMedia.url) {
|
||||
command({ src: data.uploadMedia.url, "data-media-id": data.uploadMedia.id });
|
||||
command({
|
||||
src: data.uploadMedia.url,
|
||||
"data-media-id": data.uploadMedia.id,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -48,9 +48,14 @@ export default class Image extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }): any {
|
||||
return (attrs: { [key: string]: string }) => (state: EditorState, dispatch: DispatchFn) => {
|
||||
return (attrs: { [key: string]: string }) => (
|
||||
state: EditorState,
|
||||
dispatch: DispatchFn
|
||||
) => {
|
||||
const { selection }: { selection: TextSelection } = state;
|
||||
const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos;
|
||||
const position = selection.$cursor
|
||||
? selection.$cursor.pos
|
||||
: selection.$to.pos;
|
||||
const node = type.create(attrs);
|
||||
const transaction = state.tr.insert(position, node);
|
||||
dispatch(transaction);
|
||||
@ -75,7 +80,8 @@ export default class Image extends Node {
|
||||
}
|
||||
|
||||
const images = Array.from(realEvent.dataTransfer.files).filter(
|
||||
(file: any) => /image/i.test(file.type) && !/svg/i.test(file.type)
|
||||
(file: any) =>
|
||||
/image/i.test(file.type) && !/svg/i.test(file.type)
|
||||
);
|
||||
|
||||
if (images.length === 0) {
|
||||
@ -105,7 +111,10 @@ export default class Image extends Node {
|
||||
src: data.uploadMedia.url,
|
||||
"data-media-id": data.uploadMedia.id,
|
||||
});
|
||||
const transaction = view.state.tr.insert(coordinates.pos, node);
|
||||
const transaction = view.state.tr.insert(
|
||||
coordinates.pos,
|
||||
node
|
||||
);
|
||||
view.dispatch(transaction);
|
||||
});
|
||||
return true;
|
||||
|
@ -1,11 +1,14 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
import { Extension, Plugin } from "tiptap";
|
||||
|
||||
export default class MaxSize extends Extension {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get name() {
|
||||
return "maxSize";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get defaultOptions() {
|
||||
return {
|
||||
maxSize: null,
|
||||
@ -21,7 +24,7 @@ export default class MaxSize extends Extension {
|
||||
const newLength = newState.doc.content.size;
|
||||
|
||||
if (newLength > max && newLength > oldLength) {
|
||||
let newTr = newState.tr;
|
||||
const newTr = newState.tr;
|
||||
newTr.insertText("", max + 1, newLength);
|
||||
|
||||
return newTr;
|
||||
|
@ -21,9 +21,13 @@
|
||||
</b-autocomplete>
|
||||
</b-field>
|
||||
<b-field v-if="isSecureContext()">
|
||||
<b-button type="is-text" v-if="!gettingLocation" icon-right="target" @click="locateMe">{{
|
||||
$t("Use my location")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
type="is-text"
|
||||
v-if="!gettingLocation"
|
||||
icon-right="target"
|
||||
@click="locateMe"
|
||||
>{{ $t("Use my location") }}</b-button
|
||||
>
|
||||
<span v-else>{{ $t("Getting location") }}</span>
|
||||
</b-field>
|
||||
<!--
|
||||
@ -50,7 +54,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { LatLng } from "leaflet";
|
||||
import { debounce } from "lodash";
|
||||
import { debounce, DebouncedFunc } from "lodash";
|
||||
import { Address, IAddress } from "../../types/address.model";
|
||||
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
@ -58,7 +62,8 @@ import { IConfig } from "../../types/config.model";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
"map-leaflet": () => import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
||||
"map-leaflet": () =>
|
||||
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
||||
},
|
||||
apollo: {
|
||||
config: CONFIG,
|
||||
@ -81,7 +86,7 @@ export default class AddressAutoComplete extends Vue {
|
||||
|
||||
private gettingLocation = false;
|
||||
|
||||
private location!: Position;
|
||||
private location!: GeolocationPosition;
|
||||
|
||||
private gettingLocationError: any;
|
||||
|
||||
@ -89,7 +94,7 @@ export default class AddressAutoComplete extends Vue {
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
fetchAsyncData!: Function;
|
||||
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
|
||||
|
||||
// We put this in data because of issues like
|
||||
// https://github.com/vuejs/vue-class-component/issues/263
|
||||
@ -121,7 +126,9 @@ export default class AddressAutoComplete extends Vue {
|
||||
},
|
||||
});
|
||||
|
||||
this.addressData = result.data.searchAddress.map((address: IAddress) => new Address(address));
|
||||
this.addressData = result.data.searchAddress.map(
|
||||
(address: IAddress) => new Address(address)
|
||||
);
|
||||
this.isFetching = false;
|
||||
}
|
||||
|
||||
@ -174,7 +181,9 @@ export default class AddressAutoComplete extends Vue {
|
||||
},
|
||||
});
|
||||
|
||||
this.addressData = result.data.reverseGeocode.map((address: IAddress) => new Address(address));
|
||||
this.addressData = result.data.reverseGeocode.map(
|
||||
(address: IAddress) => new Address(address)
|
||||
);
|
||||
if (this.addressData.length > 0) {
|
||||
const defaultAddress = new Address(this.addressData[0]);
|
||||
this.selected = defaultAddress;
|
||||
@ -197,7 +206,10 @@ export default class AddressAutoComplete extends Vue {
|
||||
this.location = await AddressAutoComplete.getLocation();
|
||||
this.mapDefaultZoom = 12;
|
||||
this.reverseGeoCode(
|
||||
new LatLng(this.location.coords.latitude, this.location.coords.longitude),
|
||||
new LatLng(
|
||||
this.location.coords.latitude,
|
||||
this.location.coords.longitude
|
||||
),
|
||||
12
|
||||
);
|
||||
} catch (e) {
|
||||
@ -207,7 +219,7 @@ export default class AddressAutoComplete extends Vue {
|
||||
this.gettingLocation = false;
|
||||
}
|
||||
|
||||
static async getLocation(): Promise<Position> {
|
||||
static async getLocation(): Promise<GeolocationPosition> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!("geolocation" in navigator)) {
|
||||
reject(new Error("Geolocation is not available."));
|
||||
|
@ -102,14 +102,20 @@ export default class DateTimePicker extends Vue {
|
||||
}
|
||||
|
||||
get minTime(): Date | null {
|
||||
if (this.minDatetime && this.datesAreOnSameDay(this.dateWithTime, this.minDatetime)) {
|
||||
if (
|
||||
this.minDatetime &&
|
||||
this.datesAreOnSameDay(this.dateWithTime, this.minDatetime)
|
||||
) {
|
||||
return this.minDatetime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get maxTime(): Date | null {
|
||||
if (this.maxDatetime && this.datesAreOnSameDay(this.dateWithTime, this.maxDatetime)) {
|
||||
if (
|
||||
this.maxDatetime &&
|
||||
this.datesAreOnSameDay(this.dateWithTime, this.maxDatetime)
|
||||
) {
|
||||
return this.maxDatetime;
|
||||
}
|
||||
return null;
|
||||
|
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<router-link class="card" :to="{ name: 'Event', params: { uuid: event.uuid } }">
|
||||
<router-link
|
||||
class="card"
|
||||
:to="{ name: 'Event', params: { uuid: event.uuid } }"
|
||||
>
|
||||
<div class="card-image">
|
||||
<figure
|
||||
class="image is-16by9"
|
||||
@ -21,14 +24,18 @@
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<date-calendar-icon v-if="!mergedOptions.hideDate" :date="event.beginsOn" />
|
||||
<date-calendar-icon
|
||||
v-if="!mergedOptions.hideDate"
|
||||
:date="event.beginsOn"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="event-title">{{ event.title }}</p>
|
||||
<div class="event-subtitle" v-if="event.physicalAddress">
|
||||
<!-- <p>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</p>-->
|
||||
<span>
|
||||
{{ event.physicalAddress.description }}, {{ event.physicalAddress.locality }}
|
||||
{{ event.physicalAddress.description }},
|
||||
{{ event.physicalAddress.locality }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -77,7 +84,7 @@ import { IEvent, IEventCardOptions } from "@/types/event.model";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import { Actor, Person } from "@/types/actor";
|
||||
import { ParticipantRole } from "../../types/participant.model";
|
||||
import { ParticipantRole } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
|
@ -18,7 +18,9 @@
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<span v-if="!endsOn">{{ beginsOn | formatDateTimeString(showStartTime) }}</span>
|
||||
<span v-if="!endsOn">{{
|
||||
beginsOn | formatDateTimeString(showStartTime)
|
||||
}}</span>
|
||||
<span v-else-if="isSameDay() && showStartTime && showEndTime">
|
||||
{{
|
||||
$t("On {date} from {startTime} to {endTime}", {
|
||||
@ -44,7 +46,9 @@
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="isSameDay()">{{ $t("On {date}", { date: formatDate(beginsOn) }) }}</span>
|
||||
<span v-else-if="isSameDay()">{{
|
||||
$t("On {date}", { date: formatDate(beginsOn) })
|
||||
}}</span>
|
||||
<span v-else-if="endsOn && showStartTime && showEndTime">
|
||||
{{
|
||||
$t("From the {startDate} at {startTime} to the {endDate} at {endTime}", {
|
||||
@ -97,7 +101,9 @@ export default class EventFullDate extends Vue {
|
||||
}
|
||||
|
||||
isSameDay(): boolean {
|
||||
const sameDay = new Date(this.beginsOn).toDateString() === new Date(this.endsOn).toDateString();
|
||||
const sameDay =
|
||||
new Date(this.beginsOn).toDateString() ===
|
||||
new Date(this.endsOn).toDateString();
|
||||
return this.endsOn !== undefined && sameDay;
|
||||
}
|
||||
}
|
||||
|
@ -6,25 +6,38 @@
|
||||
<div class="date-component">
|
||||
<date-calendar-icon :date="participation.event.beginsOn" />
|
||||
</div>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.EVENT,
|
||||
params: { uuid: participation.event.uuid },
|
||||
}"
|
||||
>
|
||||
<h3 class="title">{{ participation.event.title }}</h3>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="participation-actor has-text-grey">
|
||||
<span>
|
||||
<b-icon icon="earth" v-if="participation.event.visibility === EventVisibility.PUBLIC" />
|
||||
<b-icon
|
||||
icon="earth"
|
||||
v-if="participation.event.visibility === EventVisibility.PUBLIC"
|
||||
/>
|
||||
<b-icon
|
||||
icon="link"
|
||||
v-else-if="participation.event.visibility === EventVisibility.UNLISTED"
|
||||
v-else-if="
|
||||
participation.event.visibility === EventVisibility.UNLISTED
|
||||
"
|
||||
/>
|
||||
<b-icon
|
||||
icon="lock"
|
||||
v-else-if="participation.event.visibility === EventVisibility.PRIVATE"
|
||||
v-else-if="
|
||||
participation.event.visibility === EventVisibility.PRIVATE
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
participation.event.physicalAddress && participation.event.physicalAddress.locality
|
||||
participation.event.physicalAddress &&
|
||||
participation.event.physicalAddress.locality
|
||||
"
|
||||
>{{ participation.event.physicalAddress.locality }} -</span
|
||||
>
|
||||
@ -43,7 +56,11 @@
|
||||
path="Going as {name}"
|
||||
tag="span"
|
||||
>
|
||||
<popover-actor-card slot="name" :actor="participation.actor" :inline="true">
|
||||
<popover-actor-card
|
||||
slot="name"
|
||||
:actor="participation.actor"
|
||||
:inline="true"
|
||||
>
|
||||
{{ participation.actor.displayName() }}
|
||||
</popover-actor-card>
|
||||
</i18n>
|
||||
@ -53,12 +70,15 @@
|
||||
<span
|
||||
class="participant-stats"
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
![
|
||||
ParticipantRole.PARTICIPANT,
|
||||
ParticipantRole.NOT_APPROVED,
|
||||
].includes(participation.role)
|
||||
"
|
||||
>
|
||||
<span v-if="participation.event.options.maximumAttendeeCapacity !== 0">
|
||||
<span
|
||||
v-if="participation.event.options.maximumAttendeeCapacity !== 0"
|
||||
>
|
||||
{{
|
||||
$tc(
|
||||
"{available}/{capacity} available places",
|
||||
@ -68,16 +88,21 @@
|
||||
available:
|
||||
participation.event.options.maximumAttendeeCapacity -
|
||||
participation.event.participantStats.participant,
|
||||
capacity: participation.event.options.maximumAttendeeCapacity,
|
||||
capacity:
|
||||
participation.event.options.maximumAttendeeCapacity,
|
||||
}
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{
|
||||
$tc("{count} participants", participation.event.participantStats.participant, {
|
||||
count: participation.event.participantStats.participant,
|
||||
})
|
||||
$tc(
|
||||
"{count} participants",
|
||||
participation.event.participantStats.participant,
|
||||
{
|
||||
count: participation.event.participantStats.participant,
|
||||
}
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span v-if="participation.event.participantStats.notApproved > 0">
|
||||
@ -107,9 +132,10 @@
|
||||
<ul>
|
||||
<li
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
![
|
||||
ParticipantRole.PARTICIPANT,
|
||||
ParticipantRole.NOT_APPROVED,
|
||||
].includes(participation.role)
|
||||
"
|
||||
>
|
||||
<b-button
|
||||
@ -140,19 +166,23 @@
|
||||
</li>
|
||||
<li
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
![
|
||||
ParticipantRole.PARTICIPANT,
|
||||
ParticipantRole.NOT_APPROVED,
|
||||
].includes(participation.role)
|
||||
"
|
||||
@click="openDeleteEventModalWrapper"
|
||||
>
|
||||
<b-button type="is-text" icon-left="delete">{{ $t("Delete") }}</b-button>
|
||||
<b-button type="is-text" icon-left="delete">{{
|
||||
$t("Delete")
|
||||
}}</b-button>
|
||||
</li>
|
||||
<li
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
![
|
||||
ParticipantRole.PARTICIPANT,
|
||||
ParticipantRole.NOT_APPROVED,
|
||||
].includes(participation.role)
|
||||
"
|
||||
>
|
||||
<b-button
|
||||
@ -172,7 +202,10 @@
|
||||
tag="router-link"
|
||||
icon-left="view-compact"
|
||||
type="is-text"
|
||||
:to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }"
|
||||
:to="{
|
||||
name: RouteName.EVENT,
|
||||
params: { uuid: participation.event.uuid },
|
||||
}"
|
||||
>{{ $t("View event page") }}</b-button
|
||||
>
|
||||
</li>
|
||||
@ -187,8 +220,9 @@ 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 { IParticipant, ParticipantRole } from "../../types/participant.model";
|
||||
import { EventVisibility, IEventCardOptions } from "../../types/event.model";
|
||||
import { EventVisibility, ParticipantRole } from "@/types/enums";
|
||||
import { IParticipant } from "../../types/participant.model";
|
||||
import { IEventCardOptions } from "../../types/event.model";
|
||||
import { IPerson } from "../../types/actor";
|
||||
import ActorMixin from "../../mixins/actor";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||
@ -249,8 +283,14 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
|
||||
await this.openDeleteEventModal(this.participation.event);
|
||||
}
|
||||
|
||||
async gotToWithCheck(participation: IParticipant, route: RawLocation): Promise<Route> {
|
||||
if (participation.actor.id !== this.currentActor.id && participation.event.organizerActor) {
|
||||
async gotToWithCheck(
|
||||
participation: IParticipant,
|
||||
route: RawLocation
|
||||
): Promise<Route> {
|
||||
if (
|
||||
participation.actor.id !== this.currentActor.id &&
|
||||
participation.event.organizerActor
|
||||
) {
|
||||
const organizer = participation.event.organizerActor as IPerson;
|
||||
await changeIdentity(this.$apollo.provider.defaultClient, organizer);
|
||||
this.$buefy.notification.open({
|
||||
|
@ -6,7 +6,9 @@
|
||||
<div class="date-component">
|
||||
<date-calendar-icon :date="event.beginsOn" />
|
||||
</div>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }">
|
||||
<router-link
|
||||
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
|
||||
>
|
||||
<h2 class="title">{{ event.title }}</h2>
|
||||
</router-link>
|
||||
</div>
|
||||
@ -15,17 +17,34 @@
|
||||
{{ event.physicalAddress.locality }}
|
||||
</span>
|
||||
<span v-if="event.attributedTo && options.memberofGroup">
|
||||
{{ $t("Created by {name}", { name: usernameWithDomain(event.organizerActor) }) }}
|
||||
{{
|
||||
$t("Created by {name}", {
|
||||
name: usernameWithDomain(event.organizerActor),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="options.memberofGroup">
|
||||
{{ $t("Organized by {name}", { name: usernameWithDomain(event.organizerActor) }) }}
|
||||
{{
|
||||
$t("Organized by {name}", {
|
||||
name: usernameWithDomain(event.organizerActor),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<span class="column is-narrow">
|
||||
<b-icon icon="earth" v-if="event.visibility === EventVisibility.PUBLIC" />
|
||||
<b-icon icon="link" v-if="event.visibility === EventVisibility.UNLISTED" />
|
||||
<b-icon icon="lock" v-if="event.visibility === EventVisibility.PRIVATE" />
|
||||
<b-icon
|
||||
icon="earth"
|
||||
v-if="event.visibility === EventVisibility.PUBLIC"
|
||||
/>
|
||||
<b-icon
|
||||
icon="link"
|
||||
v-if="event.visibility === EventVisibility.UNLISTED"
|
||||
/>
|
||||
<b-icon
|
||||
icon="lock"
|
||||
v-if="event.visibility === EventVisibility.PRIVATE"
|
||||
/>
|
||||
</span>
|
||||
<span class="column is-narrow participant-stats">
|
||||
<span v-if="event.options.maximumAttendeeCapacity !== 0">
|
||||
@ -38,9 +57,13 @@
|
||||
</span>
|
||||
<span v-else>
|
||||
{{
|
||||
$tc("{count} participants", event.participantStats.participant, {
|
||||
count: event.participantStats.participant,
|
||||
})
|
||||
$tc(
|
||||
"{count} participants",
|
||||
event.participantStats.participant,
|
||||
{
|
||||
count: event.participantStats.participant,
|
||||
}
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
@ -51,7 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { EventVisibility, IEventCardOptions, IEvent } from "@/types/event.model";
|
||||
import { IEventCardOptions, IEvent } from "@/types/event.model";
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import { IPerson, usernameWithDomain } from "@/types/actor";
|
||||
@ -59,7 +82,7 @@ import { mixins } from "vue-class-component";
|
||||
import ActorMixin from "@/mixins/actor";
|
||||
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import EventMixin from "@/mixins/event";
|
||||
import { ParticipantRole } from "../../types/participant.model";
|
||||
import { EventVisibility, ParticipantRole } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
const defaultOptions: IEventCardOptions = {
|
||||
|
@ -14,10 +14,12 @@
|
||||
{{
|
||||
$tc(
|
||||
"{available}/{capacity} available places",
|
||||
event.options.maximumAttendeeCapacity - event.participantStats.participant,
|
||||
event.options.maximumAttendeeCapacity -
|
||||
event.participantStats.participant,
|
||||
{
|
||||
available:
|
||||
event.options.maximumAttendeeCapacity - event.participantStats.participant,
|
||||
event.options.maximumAttendeeCapacity -
|
||||
event.participantStats.participant,
|
||||
capacity: event.options.maximumAttendeeCapacity,
|
||||
}
|
||||
)
|
||||
@ -42,9 +44,13 @@
|
||||
"
|
||||
>
|
||||
{{
|
||||
$tc("{count} requests waiting", event.participantStats.notApproved, {
|
||||
count: event.participantStats.notApproved,
|
||||
})
|
||||
$tc(
|
||||
"{count} requests waiting",
|
||||
event.participantStats.notApproved,
|
||||
{
|
||||
count: event.participantStats.notApproved,
|
||||
}
|
||||
)
|
||||
}}
|
||||
</b-button>
|
||||
</span>
|
||||
@ -56,7 +62,7 @@
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IEvent } from "@/types/event.model";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import { ParticipantRole } from "../../types/participant.model";
|
||||
import { ParticipantRole } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
@ -88,7 +94,8 @@ export default class EventMinimalistCard extends Vue {
|
||||
|
||||
.event-minimalist-title {
|
||||
color: #3c376e;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
|
||||
serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
@ -33,9 +33,12 @@
|
||||
<div v-else-if="queryText.length >= 3" class="is-enabled">
|
||||
<span>{{ $t('No results for "{queryText}"') }}</span>
|
||||
<span>{{
|
||||
$t("You can try another search term or drag and drop the marker on the map", {
|
||||
queryText,
|
||||
})
|
||||
$t(
|
||||
"You can try another search term or drag and drop the marker on the map",
|
||||
{
|
||||
queryText,
|
||||
}
|
||||
)
|
||||
}}</span>
|
||||
<!-- <p class="control" @click="openNewAddressModal">-->
|
||||
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
|
||||
@ -102,7 +105,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { LatLng } from "leaflet";
|
||||
import { debounce } from "lodash";
|
||||
import { debounce, DebouncedFunc } from "lodash";
|
||||
import { Address, IAddress } from "../../types/address.model";
|
||||
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
@ -110,7 +113,8 @@ import { IConfig } from "../../types/config.model";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
"map-leaflet": () => import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
||||
"map-leaflet": () =>
|
||||
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
||||
},
|
||||
apollo: {
|
||||
config: CONFIG,
|
||||
@ -133,7 +137,7 @@ export default class FullAddressAutoComplete extends Vue {
|
||||
|
||||
private gettingLocation = false;
|
||||
|
||||
private location!: Position;
|
||||
private location!: GeolocationPosition;
|
||||
|
||||
private gettingLocationError: any;
|
||||
|
||||
@ -141,11 +145,11 @@ export default class FullAddressAutoComplete extends Vue {
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
fetchAsyncData!: Function;
|
||||
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
|
||||
|
||||
// We put this in data because of issues like
|
||||
// https://github.com/vuejs/vue-class-component/issues/263
|
||||
data() {
|
||||
data(): Record<string, unknown> {
|
||||
return {
|
||||
fetchAsyncData: debounce(this.asyncData, 200),
|
||||
};
|
||||
@ -173,7 +177,9 @@ export default class FullAddressAutoComplete extends Vue {
|
||||
},
|
||||
});
|
||||
|
||||
this.addressData = result.data.searchAddress.map((address: IAddress) => new Address(address));
|
||||
this.addressData = result.data.searchAddress.map(
|
||||
(address: IAddress) => new Address(address)
|
||||
);
|
||||
this.isFetching = false;
|
||||
}
|
||||
|
||||
@ -224,7 +230,9 @@ export default class FullAddressAutoComplete extends Vue {
|
||||
},
|
||||
});
|
||||
|
||||
this.addressData = result.data.reverseGeocode.map((address: IAddress) => new Address(address));
|
||||
this.addressData = result.data.reverseGeocode.map(
|
||||
(address: IAddress) => new Address(address)
|
||||
);
|
||||
if (this.addressData.length > 0) {
|
||||
const defaultAddress = new Address(this.addressData[0]);
|
||||
this.selected = defaultAddress;
|
||||
@ -248,7 +256,10 @@ export default class FullAddressAutoComplete extends Vue {
|
||||
this.location = await FullAddressAutoComplete.getLocation();
|
||||
this.mapDefaultZoom = 12;
|
||||
this.reverseGeoCode(
|
||||
new LatLng(this.location.coords.latitude, this.location.coords.longitude),
|
||||
new LatLng(
|
||||
this.location.coords.latitude,
|
||||
this.location.coords.longitude
|
||||
),
|
||||
12
|
||||
);
|
||||
} catch (e) {
|
||||
@ -266,7 +277,7 @@ export default class FullAddressAutoComplete extends Vue {
|
||||
return window.isSecureContext;
|
||||
}
|
||||
|
||||
static async getLocation(): Promise<Position> {
|
||||
static async getLocation(): Promise<GeolocationPosition> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!("geolocation" in navigator)) {
|
||||
reject(new Error("Geolocation is not available."));
|
||||
|
@ -10,9 +10,18 @@
|
||||
>
|
||||
<div class="media">
|
||||
<figure class="image is-48x48" v-if="availableActor.avatar">
|
||||
<img class="media-left is-rounded" :src="availableActor.avatar.url" alt="" />
|
||||
<img
|
||||
class="media-left is-rounded"
|
||||
:src="availableActor.avatar.url"
|
||||
alt=""
|
||||
/>
|
||||
</figure>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<b-icon
|
||||
class="media-left"
|
||||
v-else
|
||||
size="is-large"
|
||||
icon="account-circle"
|
||||
/>
|
||||
<div class="media-content">
|
||||
<h3>{{ availableActor.name }}</h3>
|
||||
<small>{{ `@${availableActor.preferredUsername}` }}</small>
|
||||
@ -23,9 +32,11 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { IMember, IPerson, MemberRole, IActor, Actor } from "@/types/actor";
|
||||
import { IPerson, IActor, Actor } from "@/types/actor";
|
||||
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { MemberRole } from "@/types/enums";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@ -59,16 +70,21 @@ export default class OrganizerPicker extends Vue {
|
||||
get actualMemberships(): IMember[] {
|
||||
if (this.restrictModeratorLevel) {
|
||||
return this.groupMemberships.elements.filter((membership: IMember) =>
|
||||
[MemberRole.ADMINISTRATOR, MemberRole.MODERATOR, MemberRole.CREATOR].includes(
|
||||
membership.role
|
||||
)
|
||||
[
|
||||
MemberRole.ADMINISTRATOR,
|
||||
MemberRole.MODERATOR,
|
||||
MemberRole.CREATOR,
|
||||
].includes(membership.role)
|
||||
);
|
||||
}
|
||||
return this.groupMemberships.elements;
|
||||
}
|
||||
|
||||
get actualAvailableActors(): IActor[] {
|
||||
return [this.identity, ...this.actualMemberships.map((member) => member.parent)];
|
||||
return [
|
||||
this.identity,
|
||||
...this.actualMemberships.map((member) => member.parent),
|
||||
];
|
||||
}
|
||||
|
||||
@Watch("currentActor")
|
||||
|
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div class="organizer-picker">
|
||||
<!-- If we have a current actor (inline) -->
|
||||
<div v-if="inline && currentActor.id" class="inline box" @click="isComponentModalActive = true">
|
||||
<div
|
||||
v-if="inline && currentActor.id"
|
||||
class="inline box"
|
||||
@click="isComponentModalActive = true"
|
||||
>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="currentActor.avatar">
|
||||
@ -15,7 +19,9 @@
|
||||
</div>
|
||||
<div class="media-content" v-if="currentActor.name">
|
||||
<p class="is-4">{{ currentActor.name }}</p>
|
||||
<p class="is-6 has-text-grey">{{ `@${currentActor.preferredUsername}` }}</p>
|
||||
<p class="is-6 has-text-grey">
|
||||
{{ `@${currentActor.preferredUsername}` }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="media-content" v-else>
|
||||
{{ `@${currentActor.preferredUsername}` }}
|
||||
@ -26,7 +32,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- If we have a current actor -->
|
||||
<span v-else-if="currentActor.id" class="block" @click="isComponentModalActive = true">
|
||||
<span
|
||||
v-else-if="currentActor.id"
|
||||
class="block"
|
||||
@click="isComponentModalActive = true"
|
||||
>
|
||||
<img
|
||||
class="image is-48x48"
|
||||
v-if="currentActor.avatar"
|
||||
@ -40,13 +50,19 @@
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="identity.avatar">
|
||||
<img class="image is-rounded" :src="identity.avatar.url" :alt="identity.avatar.alt" />
|
||||
<img
|
||||
class="image is-rounded"
|
||||
:src="identity.avatar.url"
|
||||
:alt="identity.avatar.alt"
|
||||
/>
|
||||
</figure>
|
||||
<b-icon v-else size="is-large" icon="account-circle" />
|
||||
</div>
|
||||
<div class="media-content" v-if="identity.name">
|
||||
<p class="is-4">{{ identity.name }}</p>
|
||||
<p class="is-6 has-text-grey">{{ `@${identity.preferredUsername}` }}</p>
|
||||
<p class="is-6 has-text-grey">
|
||||
{{ `@${identity.preferredUsername}` }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="media-content" v-else>
|
||||
{{ `@${identity.preferredUsername}` }}
|
||||
@ -74,7 +90,11 @@
|
||||
<div class="column">
|
||||
<div v-if="actorMembersForCurrentActor.length > 0">
|
||||
<p>{{ $t("Add a contact") }}</p>
|
||||
<p class="field" v-for="actor in actorMembersForCurrentActor" :key="actor.id">
|
||||
<p
|
||||
class="field"
|
||||
v-for="actor in actorMembersForCurrentActor"
|
||||
:key="actor.id"
|
||||
>
|
||||
<b-checkbox v-model="actualContacts" :native-value="actor.id">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
@ -89,7 +109,9 @@
|
||||
</div>
|
||||
<div class="media-content" v-if="actor.name">
|
||||
<p class="is-4">{{ actor.name }}</p>
|
||||
<p class="is-6 has-text-grey">{{ `@${actor.preferredUsername}` }}</p>
|
||||
<p class="is-6 has-text-grey">
|
||||
{{ `@${actor.preferredUsername}` }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="media-content" v-else>
|
||||
{{ `@${actor.preferredUsername}` }}
|
||||
@ -115,7 +137,8 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { IActor, IGroup, IMember, IPerson } from "../../types/actor";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { IActor, IGroup, IPerson } from "../../types/actor";
|
||||
import OrganizerPicker from "./OrganizerPicker.vue";
|
||||
import { PERSON_MEMBERSHIPS_WITH_MEMBERS } from "../../graphql/actor";
|
||||
import { Paginate } from "../../types/paginate";
|
||||
@ -150,7 +173,8 @@ export default class OrganizerPickerWrapper extends Vue {
|
||||
|
||||
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
|
||||
|
||||
@Prop({ type: Array, required: false, default: () => [] }) contacts!: IActor[];
|
||||
@Prop({ type: Array, required: false, default: () => [] })
|
||||
contacts!: IActor[];
|
||||
|
||||
actualContacts: (string | undefined)[] = this.contacts.map(({ id }) => id);
|
||||
|
||||
@ -171,7 +195,9 @@ export default class OrganizerPickerWrapper extends Vue {
|
||||
pickActor(): void {
|
||||
this.$emit(
|
||||
"update:contacts",
|
||||
this.actorMembersForCurrentActor.filter(({ id }) => this.actualContacts.includes(id))
|
||||
this.actorMembersForCurrentActor.filter(({ id }) =>
|
||||
this.actualContacts.includes(id)
|
||||
)
|
||||
);
|
||||
this.$emit("input", this.currentActor);
|
||||
this.isComponentModalActive = false;
|
||||
@ -182,7 +208,9 @@ export default class OrganizerPickerWrapper extends Vue {
|
||||
({ parent: { id } }) => id === this.currentActor.id
|
||||
);
|
||||
if (currentMembership) {
|
||||
return currentMembership.parent.members.elements.map(({ actor }) => actor);
|
||||
return currentMembership.parent.members.elements.map(
|
||||
({ actor }: { actor: IActor }) => actor
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
@ -47,8 +47,16 @@ A button to set your participation
|
||||
>
|
||||
</b-dropdown>
|
||||
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.NOT_APPROVED">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" class="dropdown-disabled">
|
||||
<div
|
||||
v-else-if="
|
||||
participation && participation.role === ParticipantRole.NOT_APPROVED
|
||||
"
|
||||
>
|
||||
<b-dropdown
|
||||
aria-role="list"
|
||||
position="is-bottom-left"
|
||||
class="dropdown-disabled"
|
||||
>
|
||||
<button class="button is-success is-large" type="button" slot="trigger">
|
||||
<b-icon icon="timer-sand-empty" />
|
||||
<template>
|
||||
@ -74,9 +82,17 @@ A button to set your participation
|
||||
<small>{{ $t("Waiting for organization team approval.") }}</small>
|
||||
</div>
|
||||
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.REJECTED">
|
||||
<div
|
||||
v-else-if="
|
||||
participation && participation.role === ParticipantRole.REJECTED
|
||||
"
|
||||
>
|
||||
<span>
|
||||
{{ $t("Unfortunately, your participation request was rejected by the organizers.") }}
|
||||
{{
|
||||
$t(
|
||||
"Unfortunately, your participation request was rejected by the organizers."
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -92,7 +108,11 @@ A button to set your participation
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<b-dropdown-item :value="true" aria-role="listitem" @click="joinEvent(currentActor)">
|
||||
<b-dropdown-item
|
||||
:value="true"
|
||||
aria-role="listitem"
|
||||
@click="joinEvent(currentActor)"
|
||||
>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32" v-if="currentActor.avatar">
|
||||
@ -103,7 +123,8 @@ A button to set your participation
|
||||
<span>
|
||||
{{
|
||||
$t("as {identity}", {
|
||||
identity: currentActor.name || `@${currentActor.preferredUsername}`,
|
||||
identity:
|
||||
currentActor.name || `@${currentActor.preferredUsername}`,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
@ -121,7 +142,10 @@ A button to set your participation
|
||||
</b-dropdown>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.EVENT_PARTICIPATE_LOGGED_OUT, params: { uuid: event.uuid } }"
|
||||
:to="{
|
||||
name: RouteName.EVENT_PARTICIPATE_LOGGED_OUT,
|
||||
params: { uuid: event.uuid },
|
||||
}"
|
||||
v-else-if="!participation && hasAnonymousParticipationMethods"
|
||||
type="is-primary"
|
||||
size="is-large"
|
||||
@ -130,7 +154,10 @@ A button to set your participation
|
||||
>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT, params: { uuid: event.uuid } }"
|
||||
:to="{
|
||||
name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT,
|
||||
params: { uuid: event.uuid },
|
||||
}"
|
||||
v-else-if="!currentActor.id"
|
||||
type="is-primary"
|
||||
size="is-large"
|
||||
@ -142,8 +169,9 @@ A button to set your participation
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IParticipant, ParticipantRole } from "../../types/participant.model";
|
||||
import { EventJoinOptions, IEvent } from "../../types/event.model";
|
||||
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
|
||||
import { IParticipant } from "../../types/participant.model";
|
||||
import { IEvent } from "../../types/event.model";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
import { CURRENT_ACTOR_CLIENT, IDENTITIES } from "../../graphql/actor";
|
||||
import { CURRENT_USER_CLIENT } from "../../graphql/user";
|
||||
@ -161,7 +189,9 @@ import RouteName from "../../router/name";
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
update: ({ identities }) =>
|
||||
identities ? identities.map((identity: IPerson) => new Person(identity)) : [],
|
||||
identities
|
||||
? identities.map((identity: IPerson) => new Person(identity))
|
||||
: [],
|
||||
skip() {
|
||||
return this.currentUser.isLoggedIn === false;
|
||||
},
|
||||
|
@ -59,7 +59,12 @@
|
||||
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"
|
||||
><b-icon icon="linkedin" size="is-large" type="is-primary"
|
||||
/></a>
|
||||
<a :href="diasporaShareUrl" class="diaspora" target="_blank" rel="nofollow noopener">
|
||||
<a
|
||||
:href="diasporaShareUrl"
|
||||
class="diaspora"
|
||||
target="_blank"
|
||||
rel="nofollow noopener"
|
||||
>
|
||||
<span data-v-5e15e80a="" class="icon has-text-primary is-large">
|
||||
<DiasporaLogo alt="diaspora-logo" />
|
||||
</span>
|
||||
@ -76,7 +81,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
|
||||
import { IEvent, EventVisibility, EventStatus } from "../../types/event.model";
|
||||
import { EventStatus, EventVisibility } from "@/types/enums";
|
||||
import { IEvent } from "../../types/event.model";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import DiasporaLogo from "../../assets/diaspora-icon.svg?inline";
|
||||
|
||||
@ -88,7 +95,8 @@ import DiasporaLogo from "../../assets/diaspora-icon.svg?inline";
|
||||
export default class ShareEventModal extends Vue {
|
||||
@Prop({ type: Object, required: true }) event!: IEvent;
|
||||
|
||||
@Prop({ type: Boolean, required: false, default: true }) eventCapacityOK!: boolean;
|
||||
@Prop({ type: Boolean, required: false, default: true })
|
||||
eventCapacityOK!: boolean;
|
||||
|
||||
@Ref("eventURLInput") readonly eventURLInput!: any;
|
||||
|
||||
@ -99,13 +107,15 @@ export default class ShareEventModal extends Vue {
|
||||
showCopiedTooltip = false;
|
||||
|
||||
get twitterShareUrl(): string {
|
||||
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.event.url)}&text=${
|
||||
this.event.title
|
||||
}`;
|
||||
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(
|
||||
this.event.url
|
||||
)}&text=${this.event.title}`;
|
||||
}
|
||||
|
||||
get facebookShareUrl(): string {
|
||||
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(this.event.url)}`;
|
||||
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
|
||||
this.event.url
|
||||
)}`;
|
||||
}
|
||||
|
||||
get linkedInShareUrl(): string {
|
||||
@ -124,7 +134,7 @@ export default class ShareEventModal extends Vue {
|
||||
)}&url=${encodeURIComponent(this.event.url)}`;
|
||||
}
|
||||
|
||||
copyURL() {
|
||||
copyURL(): void {
|
||||
this.eventURLInput.$refs.input.select();
|
||||
document.execCommand("copy");
|
||||
this.showCopiedTooltip = true;
|
||||
|
@ -4,7 +4,9 @@
|
||||
{{ $t("Add some tags") }}
|
||||
<b-tooltip
|
||||
type="is-dark"
|
||||
:label="$t('You can add tags by hitting the Enter key or by adding a comma')"
|
||||
:label="
|
||||
$t('You can add tags by hitting the Enter key or by adding a comma')
|
||||
"
|
||||
>
|
||||
<b-icon size="is-small" icon="help-circle-outline"></b-icon>
|
||||
</b-tooltip>
|
||||
@ -40,7 +42,6 @@ import { ITag } from "../../types/tag.model";
|
||||
if (typeof tag !== "string") {
|
||||
return tag;
|
||||
}
|
||||
// @ts-ignore
|
||||
return { title: tag, slug: tag } as ITag;
|
||||
});
|
||||
this.$emit("input", tagEntities);
|
||||
@ -57,14 +58,14 @@ export default class TagInput extends Vue {
|
||||
|
||||
filteredTags: ITag[] = [];
|
||||
|
||||
getFilteredTags(text: string) {
|
||||
getFilteredTags(text: string): void {
|
||||
this.filteredTags = differenceBy(this.data, this.value, "id").filter(
|
||||
(option) => get(option, this.path).toString().toLowerCase().indexOf(text.toLowerCase()) >= 0
|
||||
(option) =>
|
||||
get(option, this.path)
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(text.toLowerCase()) >= 0
|
||||
);
|
||||
}
|
||||
|
||||
static isTag(x: any): x is ITag {
|
||||
return x.slug !== undefined;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -3,13 +3,20 @@
|
||||
<img :src="`/img/pics/footer_${random}.jpg`" alt="" />
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.ABOUT }">{{ $t("About") }}</router-link>
|
||||
<router-link :to="{ name: RouteName.ABOUT }">{{
|
||||
$t("About")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.TERMS }">{{ $t("Terms") }}</router-link>
|
||||
<router-link :to="{ name: RouteName.TERMS }">{{
|
||||
$t("Terms")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<a hreflang="en" href="https://framagit.org/framasoft/mobilizon/blob/master/LICENSE">
|
||||
<a
|
||||
hreflang="en"
|
||||
href="https://framagit.org/framasoft/mobilizon/blob/master/LICENSE"
|
||||
>
|
||||
{{ $t("License") }}
|
||||
</a>
|
||||
</li>
|
||||
@ -19,7 +26,9 @@
|
||||
tag="span"
|
||||
path="Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}."
|
||||
>
|
||||
<a slot="mobilizon" href="https://joinmobilizon.org">{{ $t("Mobilizon") }}</a>
|
||||
<a slot="mobilizon" href="https://joinmobilizon.org">{{
|
||||
$t("Mobilizon")
|
||||
}}</a>
|
||||
<span slot="date">{{ new Date().getFullYear() }}</span>
|
||||
<a href="https://joinmobilizon.org/hall-of-fame" slot="contributors">{{
|
||||
$t("more than 1360 contributors")
|
||||
|
@ -17,7 +17,9 @@
|
||||
>
|
||||
<h3>{{ group.name }}</h3>
|
||||
<p class="is-6 has-text-grey">
|
||||
<span v-if="group.domain">{{ `@${group.preferredUsername}@${group.domain}` }}</span>
|
||||
<span v-if="group.domain">{{
|
||||
`@${group.preferredUsername}@${group.domain}`
|
||||
}}</span>
|
||||
<span v-else>{{ `@${group.preferredUsername}` }}</span>
|
||||
</p>
|
||||
</router-link>
|
||||
|
@ -13,7 +13,9 @@
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.GROUP,
|
||||
params: { preferredUsername: usernameWithDomain(member.parent) },
|
||||
params: {
|
||||
preferredUsername: usernameWithDomain(member.parent),
|
||||
},
|
||||
}"
|
||||
>
|
||||
<h3>{{ member.parent.name }}</h3>
|
||||
@ -23,12 +25,16 @@
|
||||
}}</span>
|
||||
<span v-else>{{ `@${member.parent.preferredUsername}` }}</span>
|
||||
<b-taglist>
|
||||
<b-tag type="is-info" v-if="member.role === MemberRole.ADMINISTRATOR">{{
|
||||
$t("Administrator")
|
||||
}}</b-tag>
|
||||
<b-tag type="is-info" v-else-if="member.role === MemberRole.MODERATOR">{{
|
||||
$t("Moderator")
|
||||
}}</b-tag>
|
||||
<b-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>
|
||||
@ -54,7 +60,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IMember, MemberRole, usernameWithDomain } from "@/types/actor";
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { MemberRole } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component
|
||||
|
@ -8,7 +8,9 @@
|
||||
<a
|
||||
class="list-item"
|
||||
v-for="groupMembership in actualMemberships"
|
||||
:class="{ 'is-active': groupMembership.parent.id === currentGroup.id }"
|
||||
:class="{
|
||||
'is-active': groupMembership.parent.id === currentGroup.id,
|
||||
}"
|
||||
@click="changeCurrentGroup(groupMembership.parent)"
|
||||
:key="groupMembership.id"
|
||||
>
|
||||
@ -19,14 +21,25 @@
|
||||
:src="groupMembership.parent.avatar.url"
|
||||
alt=""
|
||||
/>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<b-icon
|
||||
class="media-left"
|
||||
v-else
|
||||
size="is-large"
|
||||
icon="account-circle"
|
||||
/>
|
||||
<div class="media-content">
|
||||
<h3>@{{ groupMembership.parent.name }}</h3>
|
||||
<small>{{ `@${groupMembership.parent.preferredUsername}` }}</small>
|
||||
<small>{{
|
||||
`@${groupMembership.parent.preferredUsername}`
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="list-item" @click="changeCurrentGroup(new Group())" v-if="currentGroup.id">
|
||||
<a
|
||||
class="list-item"
|
||||
@click="changeCurrentGroup(new Group())"
|
||||
v-if="currentGroup.id"
|
||||
>
|
||||
<h3>{{ $t("Unset group") }}</h3>
|
||||
</a>
|
||||
</div>
|
||||
@ -36,9 +49,11 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IGroup, IMember, IPerson, Group, MemberRole } from "@/types/actor";
|
||||
import { IGroup, IPerson, Group } from "@/types/actor";
|
||||
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { MemberRole } from "@/types/enums";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@ -77,9 +92,11 @@ export default class GroupPicker extends Vue {
|
||||
get actualMemberships(): IMember[] {
|
||||
if (this.restrictModeratorLevel) {
|
||||
return this.groupMemberships.elements.filter((membership: IMember) =>
|
||||
[MemberRole.ADMINISTRATOR, MemberRole.MODERATOR, MemberRole.CREATOR].includes(
|
||||
membership.role
|
||||
)
|
||||
[
|
||||
MemberRole.ADMINISTRATOR,
|
||||
MemberRole.MODERATOR,
|
||||
MemberRole.CREATOR,
|
||||
].includes(membership.role)
|
||||
);
|
||||
}
|
||||
return this.groupMemberships.elements;
|
||||
|
@ -10,7 +10,11 @@
|
||||
{{ $t("The event will show the group as organizer.") }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="inline && currentGroup.id" class="inline box" @click="isComponentModalActive = true">
|
||||
<div
|
||||
v-if="inline && currentGroup.id"
|
||||
class="inline box"
|
||||
@click="isComponentModalActive = true"
|
||||
>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="currentGroup.avatar">
|
||||
@ -24,7 +28,9 @@
|
||||
</div>
|
||||
<div class="media-content" v-if="currentGroup.name">
|
||||
<p class="is-4">{{ currentGroup.name }}</p>
|
||||
<p class="is-6 has-text-grey">{{ `@${currentGroup.preferredUsername}` }}</p>
|
||||
<p class="is-6 has-text-grey">
|
||||
{{ `@${currentGroup.preferredUsername}` }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="media-content" v-else>
|
||||
{{ `@${currentGroup.preferredUsername}` }}
|
||||
@ -34,7 +40,11 @@
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else-if="currentGroup.id" class="block" @click="isComponentModalActive = true">
|
||||
<span
|
||||
v-else-if="currentGroup.id"
|
||||
class="block"
|
||||
@click="isComponentModalActive = true"
|
||||
>
|
||||
<img
|
||||
class="image is-48x48"
|
||||
v-if="currentGroup.avatar"
|
||||
@ -44,7 +54,9 @@
|
||||
<b-icon v-else size="is-large" icon="account-circle" />
|
||||
</span>
|
||||
<div v-if="groupMemberships.total === 0" class="box">
|
||||
<p class="is-4">{{ $t("This identity is not a member of any group.") }}</p>
|
||||
<p class="is-4">
|
||||
{{ $t("This identity is not a member of any group.") }}
|
||||
</p>
|
||||
<p class="is-6 is-size-6 has-text-grey">
|
||||
{{ $t("You need to create the group before you create an event.") }}
|
||||
</p>
|
||||
@ -61,7 +73,8 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { IGroup, IMember, IPerson } from "../../types/actor";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { IGroup, IPerson } from "../../types/actor";
|
||||
import GroupPicker from "./GroupPicker.vue";
|
||||
import { PERSON_MEMBERSHIPS } from "../../graphql/actor";
|
||||
import { Paginate } from "../../types/paginate";
|
||||
|
@ -26,7 +26,8 @@ export default class GroupSection extends Vue {
|
||||
|
||||
@Prop({ required: true, type: String }) icon!: string;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: true }) privateSection!: boolean;
|
||||
@Prop({ required: false, type: Boolean, default: true })
|
||||
privateSection!: boolean;
|
||||
|
||||
@Prop({ required: true, type: Object }) route!: Route;
|
||||
}
|
||||
@ -76,7 +77,8 @@ div.group-section-title {
|
||||
::v-deep span {
|
||||
display: inline;
|
||||
padding: 3px 8px;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
|
||||
serif;
|
||||
font-weight: 500;
|
||||
font-size: 30px;
|
||||
flex: 1;
|
||||
|
@ -2,7 +2,10 @@
|
||||
<div class="media">
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<i18n tag="p" path="You have been invited by {invitedBy} to the following group:">
|
||||
<i18n
|
||||
tag="p"
|
||||
path="You have been invited by {invitedBy} to the following group:"
|
||||
>
|
||||
<b slot="invitedBy">{{ member.invitedBy.name }}</b>
|
||||
</i18n>
|
||||
</div>
|
||||
@ -20,15 +23,21 @@
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.GROUP,
|
||||
params: { preferredUsername: usernameWithDomain(member.parent) },
|
||||
params: {
|
||||
preferredUsername: usernameWithDomain(member.parent),
|
||||
},
|
||||
}"
|
||||
>
|
||||
<h3>{{ member.parent.name }}</h3>
|
||||
<p class="is-6 has-text-grey">
|
||||
<span v-if="member.parent.domain">
|
||||
{{ `@${member.parent.preferredUsername}@${member.parent.domain}` }}
|
||||
{{
|
||||
`@${member.parent.preferredUsername}@${member.parent.domain}`
|
||||
}}
|
||||
</span>
|
||||
<span v-else>{{ `@${member.parent.preferredUsername}` }}</span>
|
||||
<span v-else>{{
|
||||
`@${member.parent.preferredUsername}`
|
||||
}}</span>
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
@ -54,7 +63,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IMember, usernameWithDomain } from "@/types/actor";
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component
|
||||
|
@ -11,10 +11,10 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ACCEPT_INVITATION, REJECT_INVITATION } from "@/graphql/member";
|
||||
import { IMember } from "@/types/actor";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import InvitationCard from "@/components/Group/InvitationCard.vue";
|
||||
import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -26,13 +26,15 @@ export default class Invitations extends Vue {
|
||||
|
||||
async acceptInvitation(id: string): Promise<void> {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate<{ acceptInvitation: IMember }>({
|
||||
mutation: ACCEPT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
|
||||
});
|
||||
const { data } = await this.$apollo.mutate<{ acceptInvitation: IMember }>(
|
||||
{
|
||||
mutation: ACCEPT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
|
||||
}
|
||||
);
|
||||
if (data) {
|
||||
this.$emit("accept-invitation", data.acceptInvitation);
|
||||
}
|
||||
@ -46,13 +48,15 @@ export default class Invitations extends Vue {
|
||||
|
||||
async rejectInvitation(id: string): Promise<void> {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate<{ rejectInvitation: IMember }>({
|
||||
mutation: REJECT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
|
||||
});
|
||||
const { data } = await this.$apollo.mutate<{ rejectInvitation: IMember }>(
|
||||
{
|
||||
mutation: REJECT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
|
||||
}
|
||||
);
|
||||
if (data) {
|
||||
this.$emit("reject-invitation", data.rejectInvitation);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ export default class JoinGroupWithAccount extends Vue {
|
||||
}`;
|
||||
}
|
||||
|
||||
sentence = this.$t("We will redirect you to your instance in order to interact with this group");
|
||||
sentence = this.$t(
|
||||
"We will redirect you to your instance in order to interact with this group"
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
@ -8,7 +8,11 @@
|
||||
@click="clickMap"
|
||||
@update:zoom="updateZoom"
|
||||
>
|
||||
<l-tile-layer :url="config.maps.tiles.endpoint" :attribution="attribution"> </l-tile-layer>
|
||||
<l-tile-layer
|
||||
:url="config.maps.tiles.endpoint"
|
||||
:attribution="attribution"
|
||||
>
|
||||
</l-tile-layer>
|
||||
<v-locatecontrol :options="{ icon: 'mdi mdi-map-marker' }" />
|
||||
<l-marker
|
||||
:lat-lng="[lat, lon]"
|
||||
@ -17,7 +21,9 @@
|
||||
:draggable="!readOnly"
|
||||
>
|
||||
<l-popup v-if="popupMultiLine">
|
||||
<span v-for="line in popupMultiLine" :key="line">{{ line }}<br /></span>
|
||||
<span v-for="line in popupMultiLine" :key="line"
|
||||
>{{ line }}<br
|
||||
/></span>
|
||||
</l-popup>
|
||||
</l-marker>
|
||||
</l-map>
|
||||
@ -51,12 +57,15 @@ export default class Map extends Vue {
|
||||
|
||||
@Prop({ type: String, required: true }) coords!: string;
|
||||
|
||||
@Prop({ type: Object, required: false }) marker!: { text: string | string[]; icon: string };
|
||||
@Prop({ type: Object, required: false }) marker!: {
|
||||
text: string | string[];
|
||||
icon: string;
|
||||
};
|
||||
|
||||
@Prop({ type: Object, required: false }) options!: object;
|
||||
@Prop({ type: Object, required: false }) options!: Record<string, unknown>;
|
||||
|
||||
@Prop({ type: Function, required: false })
|
||||
updateDraggableMarkerCallback!: Function;
|
||||
updateDraggableMarkerCallback!: (latlng: LatLng, zoom: number) => void;
|
||||
|
||||
defaultOptions: {
|
||||
zoom: number;
|
||||
@ -86,45 +95,48 @@ export default class Map extends Vue {
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
openPopup(event: LeafletEvent) {
|
||||
openPopup(event: LeafletEvent): void {
|
||||
this.$nextTick(() => {
|
||||
event.target.openPopup();
|
||||
});
|
||||
}
|
||||
|
||||
get mergedOptions(): object {
|
||||
get mergedOptions(): Record<string, unknown> {
|
||||
return { ...this.defaultOptions, ...this.options };
|
||||
}
|
||||
|
||||
get lat() {
|
||||
get lat(): number {
|
||||
return this.$props.coords.split(";")[1];
|
||||
}
|
||||
|
||||
get lon() {
|
||||
get lon(): number {
|
||||
return this.$props.coords.split(";")[0];
|
||||
}
|
||||
|
||||
get popupMultiLine() {
|
||||
get popupMultiLine(): Array<string> {
|
||||
if (Array.isArray(this.marker.text)) {
|
||||
return this.marker.text;
|
||||
}
|
||||
return [this.marker.text];
|
||||
}
|
||||
|
||||
clickMap(event: LeafletMouseEvent) {
|
||||
clickMap(event: LeafletMouseEvent): void {
|
||||
this.updateDraggableMarkerPosition(event.latlng);
|
||||
}
|
||||
|
||||
updateDraggableMarkerPosition(e: LatLng) {
|
||||
updateDraggableMarkerPosition(e: LatLng): void {
|
||||
this.updateDraggableMarkerCallback(e, this.zoom);
|
||||
}
|
||||
|
||||
updateZoom(zoom: number) {
|
||||
updateZoom(zoom: number): void {
|
||||
this.zoom = zoom;
|
||||
}
|
||||
|
||||
get attribution() {
|
||||
return this.config.maps.tiles.attribution || this.$t("© The OpenStreetMap Contributors");
|
||||
get attribution(): string {
|
||||
return (
|
||||
this.config.maps.tiles.attribution ||
|
||||
(this.$t("© The OpenStreetMap Contributors") as string)
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -17,13 +17,16 @@ import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component({
|
||||
beforeDestroy() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.parentContainer.removeLayer(this);
|
||||
},
|
||||
})
|
||||
export default class Vue2LeafletLocateControl extends Vue {
|
||||
@Prop({ type: Object, default: () => ({}) }) options!: object;
|
||||
@Prop({ type: Object, default: () => ({}) }) options!: Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
@Prop({ type: Boolean, default: true }) visible = true;
|
||||
|
||||
@ -33,7 +36,7 @@ export default class Vue2LeafletLocateControl extends Vue {
|
||||
|
||||
parentContainer: any;
|
||||
|
||||
mounted() {
|
||||
mounted(): void {
|
||||
this.mapObject = L.control.locate(this.options);
|
||||
DomEvent.on(this.mapObject, this.$listeners as any);
|
||||
propsBinder(this, this.mapObject, this.$props);
|
||||
@ -42,7 +45,7 @@ export default class Vue2LeafletLocateControl extends Vue {
|
||||
this.mapObject.addTo(this.parentContainer.mapObject, !this.visible);
|
||||
}
|
||||
|
||||
public locate() {
|
||||
public locate(): void {
|
||||
this.mapObject.start();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,15 @@
|
||||
<template>
|
||||
<b-navbar type="is-secondary" wrapper-class="container" :active.sync="mobileNavbarActive">
|
||||
<b-navbar
|
||||
type="is-secondary"
|
||||
wrapper-class="container"
|
||||
:active.sync="mobileNavbarActive"
|
||||
>
|
||||
<template slot="brand">
|
||||
<b-navbar-item tag="router-link" :to="{ name: RouteName.HOME }" :aria-label="$t('Home')">
|
||||
<b-navbar-item
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.HOME }"
|
||||
:aria-label="$t('Home')"
|
||||
>
|
||||
<logo />
|
||||
</b-navbar-item>
|
||||
</template>
|
||||
@ -19,9 +27,12 @@
|
||||
>{{ $t("My groups") }}</b-navbar-item
|
||||
>
|
||||
<b-navbar-item tag="span" v-if="config && config.features.eventCreation">
|
||||
<b-button tag="router-link" :to="{ name: RouteName.CREATE_EVENT }" type="is-primary">{{
|
||||
$t("Create")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.CREATE_EVENT }"
|
||||
type="is-primary"
|
||||
>{{ $t("Create") }}</b-button
|
||||
>
|
||||
</b-navbar-item>
|
||||
</template>
|
||||
<template slot="end">
|
||||
@ -30,9 +41,17 @@
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-dropdown v-if="currentActor.id && currentUser.isLoggedIn" right>
|
||||
<template slot="label" v-if="currentActor" class="navbar-dropdown-profile">
|
||||
<template
|
||||
slot="label"
|
||||
v-if="currentActor"
|
||||
class="navbar-dropdown-profile"
|
||||
>
|
||||
<figure class="image is-32x32" v-if="currentActor.avatar">
|
||||
<img class="is-rounded" alt="avatarUrl" :src="currentActor.avatar.url" />
|
||||
<img
|
||||
class="is-rounded"
|
||||
alt="avatarUrl"
|
||||
:src="currentActor.avatar.url"
|
||||
/>
|
||||
</figure>
|
||||
<b-icon v-else icon="account-circle" />
|
||||
</template>
|
||||
@ -65,9 +84,11 @@
|
||||
<hr class="navbar-divider" />
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-item tag="router-link" :to="{ name: RouteName.UPDATE_IDENTITY }">{{
|
||||
$t("My account")
|
||||
}}</b-navbar-item>
|
||||
<b-navbar-item
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.UPDATE_IDENTITY }"
|
||||
>{{ $t("My account") }}</b-navbar-item
|
||||
>
|
||||
|
||||
<!-- <b-navbar-item tag="router-link" :to="{ name: RouteName.CREATE_GROUP }">-->
|
||||
<!-- {{ $t('Create group') }}-->
|
||||
@ -95,9 +116,11 @@
|
||||
<strong>{{ $t("Sign up") }}</strong>
|
||||
</router-link>
|
||||
|
||||
<router-link class="button is-light" :to="{ name: RouteName.LOGIN }">{{
|
||||
$t("Log in")
|
||||
}}</router-link>
|
||||
<router-link
|
||||
class="button is-light"
|
||||
:to="{ name: RouteName.LOGIN }"
|
||||
>{{ $t("Log in") }}</router-link
|
||||
>
|
||||
</div>
|
||||
</b-navbar-item>
|
||||
</template>
|
||||
@ -109,13 +132,18 @@ import { Component, Vue, Watch } from "vue-property-decorator";
|
||||
import Logo from "@/components/Logo.vue";
|
||||
import { GraphQLError } from "graphql";
|
||||
import { loadLanguageAsync } from "@/utils/i18n";
|
||||
import { ICurrentUserRole } from "@/types/enums";
|
||||
import { CURRENT_USER_CLIENT, USER_SETTINGS } from "../graphql/user";
|
||||
import { changeIdentity, logout } from "../utils/auth";
|
||||
import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_DEFAULT_ACTOR } from "../graphql/actor";
|
||||
import {
|
||||
CURRENT_ACTOR_CLIENT,
|
||||
IDENTITIES,
|
||||
UPDATE_DEFAULT_ACTOR,
|
||||
} from "../graphql/actor";
|
||||
import { IPerson, Person } from "../types/actor";
|
||||
import { CONFIG } from "../graphql/config";
|
||||
import { IConfig } from "../types/config.model";
|
||||
import { ICurrentUser, ICurrentUserRole, IUser } from "../types/current-user.model";
|
||||
import { ICurrentUser, IUser } from "../types/current-user.model";
|
||||
import SearchField from "./SearchField.vue";
|
||||
import RouteName from "../router/name";
|
||||
|
||||
@ -130,7 +158,9 @@ import RouteName from "../router/name";
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
update: ({ identities }) =>
|
||||
identities ? identities.map((identity: IPerson) => new Person(identity)) : [],
|
||||
identities
|
||||
? identities.map((identity: IPerson) => new Person(identity))
|
||||
: [],
|
||||
skip() {
|
||||
return this.currentUser.isLoggedIn === false;
|
||||
},
|
||||
@ -201,7 +231,8 @@ export default class NavBar extends Vue {
|
||||
async handleErrors(errors: GraphQLError[]): Promise<void> {
|
||||
if (
|
||||
errors.length > 0 &&
|
||||
errors[0].message === "You need to be logged-in to view your list of identities"
|
||||
errors[0].message ===
|
||||
"You need to be logged-in to view your list of identities"
|
||||
) {
|
||||
await this.logout();
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<section class="section container">
|
||||
<h1 class="title" v-if="loading">{{ $t("Your participation request is being validated") }}</h1>
|
||||
<h1 class="title" v-if="loading">
|
||||
{{ $t("Your participation request is being validated") }}
|
||||
</h1>
|
||||
<div v-else>
|
||||
<div v-if="failed">
|
||||
<b-message :title="$t('Error while validating participation request')" type="is-danger">
|
||||
<b-message
|
||||
:title="$t('Error while validating participation request')"
|
||||
type="is-danger"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
"Either the participation request has already been validated, either the validation token is incorrect."
|
||||
@ -12,9 +17,16 @@
|
||||
</b-message>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1 class="title">{{ $t("Your participation request has been validated") }}</h1>
|
||||
<p class="content" v-if="participation.event.joinOptions == EventJoinOptions.RESTRICTED">
|
||||
{{ $t("Your participation still has to be approved by the organisers.") }}
|
||||
<h1 class="title">
|
||||
{{ $t("Your participation request has been validated") }}
|
||||
</h1>
|
||||
<p
|
||||
class="content"
|
||||
v-if="participation.event.joinOptions == EventJoinOptions.RESTRICTED"
|
||||
>
|
||||
{{
|
||||
$t("Your participation still has to be approved by the organisers.")
|
||||
}}
|
||||
</p>
|
||||
<div class="columns has-text-centered">
|
||||
<div class="column">
|
||||
@ -38,9 +50,9 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { confirmLocalAnonymousParticipation } from "@/services/AnonymousParticipationStorage";
|
||||
import { EventJoinOptions } from "@/types/enums";
|
||||
import { IParticipant } from "../../types/participant.model";
|
||||
import RouteName from "../../router/name";
|
||||
import { EventJoinOptions } from "../../types/event.model";
|
||||
import { CONFIRM_PARTICIPATION } from "../../graphql/event";
|
||||
|
||||
@Component
|
||||
|
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<redirect-with-account :uri="uri" :pathAfterLogin="`/events/${uuid}`" :sentence="sentence" />
|
||||
<redirect-with-account
|
||||
:uri="uri"
|
||||
:pathAfterLogin="`/events/${uuid}`"
|
||||
:sentence="sentence"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
@ -21,6 +25,8 @@ export default class ParticipationWithAccount extends Vue {
|
||||
}`;
|
||||
}
|
||||
|
||||
sentence = this.$t("We will redirect you to your instance in order to interact with this event");
|
||||
sentence = this.$t(
|
||||
"We will redirect you to your instance in order to interact with this event"
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
@ -33,7 +33,13 @@
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p v-else>{{ $t("If you want, you may send a message to the event organizer here.") }}</p>
|
||||
<p v-else>
|
||||
{{
|
||||
$t(
|
||||
"If you want, you may send a message to the event organizer here."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<b-field :label="$t('Message')">
|
||||
<b-input
|
||||
type="textarea"
|
||||
@ -54,18 +60,29 @@
|
||||
</p>
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
<b-button :disabled="sendingForm" type="is-primary" native-type="submit">{{
|
||||
$t("Send email")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
:disabled="sendingForm"
|
||||
type="is-primary"
|
||||
native-type="submit"
|
||||
>{{ $t("Send email") }}</b-button
|
||||
>
|
||||
<div class="has-text-centered">
|
||||
<b-button native-type="button" tag="a" type="is-text" @click="$router.go(-1)">{{
|
||||
$t("Back to previous page")
|
||||
}}</b-button>
|
||||
<b-button
|
||||
native-type="button"
|
||||
tag="a"
|
||||
type="is-text"
|
||||
@click="$router.go(-1)"
|
||||
>{{ $t("Back to previous page") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
<div v-else>
|
||||
<h1 class="title">{{ $t("Request for participation confirmation sent") }}</h1>
|
||||
<p class="content">{{ $t("Check your inbox (and your junk mail folder).") }}</p>
|
||||
<h1 class="title">
|
||||
{{ $t("Request for participation confirmation sent") }}
|
||||
</h1>
|
||||
<p class="content">
|
||||
{{ $t("Check your inbox (and your junk mail folder).") }}
|
||||
</p>
|
||||
<p class="content">{{ $t("You may now close this window.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -74,12 +91,13 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { EventModel, IEvent, EventJoinOptions } from "@/types/event.model";
|
||||
import { EventModel, IEvent } from "@/types/event.model";
|
||||
import { FETCH_EVENT, JOIN_EVENT } from "@/graphql/event";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import { CONFIG } from "@/graphql/config";
|
||||
import { addLocalUnconfirmedAnonymousParticipation } from "@/services/AnonymousParticipationStorage";
|
||||
import { IParticipant, ParticipantRole } from "../../types/participant.model";
|
||||
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
|
||||
import { IParticipant } from "../../types/participant.model";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@ -101,7 +119,11 @@ import { IParticipant, ParticipantRole } from "../../types/participant.model";
|
||||
export default class ParticipationWithoutAccount extends Vue {
|
||||
@Prop({ type: String, required: true }) uuid!: string;
|
||||
|
||||
anonymousParticipation: { email: string; message: string; saveParticipation: boolean } = {
|
||||
anonymousParticipation: {
|
||||
email: string;
|
||||
message: string;
|
||||
saveParticipation: boolean;
|
||||
} = {
|
||||
email: "",
|
||||
message: "",
|
||||
saveParticipation: true,
|
||||
@ -133,7 +155,9 @@ export default class ParticipationWithoutAccount extends Vue {
|
||||
},
|
||||
update: (store, { data: updateData }) => {
|
||||
if (updateData == null) {
|
||||
console.error("Cannot update event participant cache, because of data null value.");
|
||||
console.error(
|
||||
"Cannot update event participant cache, because of data null value."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -142,12 +166,16 @@ export default class ParticipationWithoutAccount extends Vue {
|
||||
variables: { uuid: this.event.uuid },
|
||||
});
|
||||
if (cachedData == null) {
|
||||
console.error("Cannot update event participant cache, because of cached null value.");
|
||||
console.error(
|
||||
"Cannot update event participant cache, because of cached null value."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { event } = cachedData;
|
||||
if (event === null) {
|
||||
console.error("Cannot update event participant cache, because of null value.");
|
||||
console.error(
|
||||
"Cannot update event participant cache, because of null value."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2,22 +2,34 @@
|
||||
<section class="section container hero">
|
||||
<div class="hero-body" v-if="event">
|
||||
<div class="container">
|
||||
<subtitle>{{ $t("You wish to participate to the following event") }}</subtitle>
|
||||
<subtitle>{{
|
||||
$t("You wish to participate to the following event")
|
||||
}}</subtitle>
|
||||
<EventListViewCard v-if="event" :event="event" />
|
||||
<div class="columns has-text-centered">
|
||||
<div class="column">
|
||||
<router-link :to="{ name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT }">
|
||||
<router-link
|
||||
:to="{ name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT }"
|
||||
>
|
||||
<figure class="image is-128x128">
|
||||
<img src="../../assets/undraw_profile.svg" alt="Profile illustration" />
|
||||
<img
|
||||
src="../../assets/undraw_profile.svg"
|
||||
alt="Profile illustration"
|
||||
/>
|
||||
</figure>
|
||||
<b-button type="is-primary">{{ $t("I have a Mobilizon account") }}</b-button>
|
||||
<b-button type="is-primary">{{
|
||||
$t("I have a Mobilizon account")
|
||||
}}</b-button>
|
||||
</router-link>
|
||||
<p>
|
||||
<small>
|
||||
{{
|
||||
$t("Either on the {instance} instance or on another instance.", {
|
||||
instance: host,
|
||||
})
|
||||
$t(
|
||||
"Either on the {instance} instance or on another instance.",
|
||||
{
|
||||
instance: host,
|
||||
}
|
||||
)
|
||||
}}
|
||||
</small>
|
||||
<b-tooltip
|
||||
@ -32,25 +44,41 @@
|
||||
</b-tooltip>
|
||||
</p>
|
||||
</div>
|
||||
<vertical-divider :content="$t('Or')" v-if="anonymousParticipationAllowed" />
|
||||
<vertical-divider
|
||||
:content="$t('Or')"
|
||||
v-if="anonymousParticipationAllowed"
|
||||
/>
|
||||
<div
|
||||
class="column"
|
||||
v-if="anonymousParticipationAllowed && hasAnonymousEmailParticipationMethod"
|
||||
v-if="
|
||||
anonymousParticipationAllowed &&
|
||||
hasAnonymousEmailParticipationMethod
|
||||
"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: RouteName.EVENT_PARTICIPATE_WITHOUT_ACCOUNT }"
|
||||
v-if="event.local"
|
||||
>
|
||||
<figure class="image is-128x128">
|
||||
<img src="../../assets/undraw_mail_2.svg" alt="Privacy illustration" />
|
||||
<img
|
||||
src="../../assets/undraw_mail_2.svg"
|
||||
alt="Privacy illustration"
|
||||
/>
|
||||
</figure>
|
||||
<b-button type="is-primary">{{ $t("I don't have a Mobilizon account") }}</b-button>
|
||||
<b-button type="is-primary">{{
|
||||
$t("I don't have a Mobilizon account")
|
||||
}}</b-button>
|
||||
</router-link>
|
||||
<a :href="`${event.url}/participate/without-account`" v-else>
|
||||
<figure class="image is-128x128">
|
||||
<img src="../../assets/undraw_mail_2.svg" alt="Privacy illustration" />
|
||||
<img
|
||||
src="../../assets/undraw_mail_2.svg"
|
||||
alt="Privacy illustration"
|
||||
/>
|
||||
</figure>
|
||||
<b-button type="is-primary">{{ $t("I don't have a Mobilizon account") }}</b-button>
|
||||
<b-button type="is-primary">{{
|
||||
$t("I don't have a Mobilizon account")
|
||||
}}</b-button>
|
||||
</a>
|
||||
<p>
|
||||
<small>{{ $t("Participate using your email address") }}</small>
|
||||
|
@ -69,7 +69,11 @@ export default class PictureUpload extends Vue {
|
||||
|
||||
@Prop({ type: Object, required: false }) defaultImage!: IMedia;
|
||||
|
||||
@Prop({ type: String, required: false, default: "image/gif,image/png,image/jpeg,image/webp" })
|
||||
@Prop({
|
||||
type: String,
|
||||
required: false,
|
||||
default: "image/gif,image/png,image/jpeg,image/webp",
|
||||
})
|
||||
accept!: string;
|
||||
|
||||
@Prop({
|
||||
@ -95,13 +99,11 @@ export default class PictureUpload extends Vue {
|
||||
|
||||
@Watch("pictureFile")
|
||||
onPictureFileChanged(val: File): void {
|
||||
console.log("onPictureFileChanged", val);
|
||||
this.updatePreview(val);
|
||||
}
|
||||
|
||||
@Watch("defaultImage")
|
||||
onDefaultImageChange(defaultImage: IMedia): void {
|
||||
console.log("onDefaultImageChange", defaultImage);
|
||||
this.imageSrc = defaultImage ? defaultImage.url : null;
|
||||
}
|
||||
|
||||
|
@ -14,26 +14,46 @@
|
||||
<div class="media-content">
|
||||
<p class="post-minimalist-title">{{ post.title }}</p>
|
||||
<div class="metadata">
|
||||
<b-tag type="is-warning" size="is-small" v-if="post.draft">{{ $t("Draft") }}</b-tag>
|
||||
<b-tag type="is-warning" size="is-small" v-if="post.draft">{{
|
||||
$t("Draft")
|
||||
}}</b-tag>
|
||||
<small
|
||||
v-if="post.visibility === PostVisibility.PUBLIC && isCurrentActorMember"
|
||||
v-if="
|
||||
post.visibility === PostVisibility.PUBLIC &&
|
||||
isCurrentActorMember
|
||||
"
|
||||
class="has-text-grey"
|
||||
>
|
||||
<b-icon icon="earth" size="is-small" />{{ $t("Public") }}</small
|
||||
>
|
||||
<small v-else-if="post.visibility === PostVisibility.UNLISTED" class="has-text-grey">
|
||||
<b-icon icon="link" size="is-small" />{{ $t("Accessible through link") }}</small
|
||||
<small
|
||||
v-else-if="post.visibility === PostVisibility.UNLISTED"
|
||||
class="has-text-grey"
|
||||
>
|
||||
<b-icon icon="link" size="is-small" />{{
|
||||
$t("Accessible through link")
|
||||
}}</small
|
||||
>
|
||||
<small
|
||||
v-else-if="post.visibility === PostVisibility.PRIVATE"
|
||||
class="has-text-grey"
|
||||
>
|
||||
<small v-else-if="post.visibility === PostVisibility.PRIVATE" class="has-text-grey">
|
||||
<b-icon icon="lock" size="is-small" />{{
|
||||
$t("Accessible only to members", { group: post.attributedTo.name })
|
||||
$t("Accessible only to members", {
|
||||
group: post.attributedTo.name,
|
||||
})
|
||||
}}</small
|
||||
>
|
||||
<small class="has-text-grey">{{
|
||||
$options.filters.formatDateTimeString(new Date(post.insertedAt), false)
|
||||
$options.filters.formatDateTimeString(
|
||||
new Date(post.insertedAt),
|
||||
false
|
||||
)
|
||||
}}</small>
|
||||
<small class="has-text-grey" v-if="isCurrentActorMember">{{
|
||||
$t("Created by {username}", { username: `@${usernameWithDomain(post.author)}` })
|
||||
$t("Created by {username}", {
|
||||
username: `@${usernameWithDomain(post.author)}`,
|
||||
})
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,15 +63,17 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { PostVisibility } from "@/types/enums";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import { IPost, PostVisibility } from "../../types/post.model";
|
||||
import { IPost } from "../../types/post.model";
|
||||
|
||||
@Component
|
||||
export default class PostElementItem extends Vue {
|
||||
@Prop({ required: true, type: Object }) post!: IPost;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) isCurrentActorMember!: boolean;
|
||||
@Prop({ required: false, type: Boolean, default: false })
|
||||
isCurrentActorMember!: boolean;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
@ -74,7 +96,8 @@ export default class PostElementItem extends Vue {
|
||||
|
||||
.post-minimalist-title {
|
||||
color: #3c376e;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
|
||||
serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
|
@ -43,7 +43,8 @@ export default class PostListItem extends Vue {
|
||||
|
||||
.post-minimalist-title {
|
||||
color: #3c376e;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
|
||||
serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
|
@ -22,10 +22,18 @@
|
||||
<div class="content columns">
|
||||
<div class="column is-one-quarter-desktop">
|
||||
<span v-if="report.reporter.type === ActorType.APPLICATION">
|
||||
{{ $t("Reported by someone on {domain}", { domain: report.reporter.domain }) }}
|
||||
{{
|
||||
$t("Reported by someone on {domain}", {
|
||||
domain: report.reporter.domain,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t("Reported by {reporter}", { reporter: report.reporter.preferredUsername }) }}
|
||||
{{
|
||||
$t("Reported by {reporter}", {
|
||||
reporter: report.reporter.preferredUsername,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="column" v-if="report.content" v-html="report.content" />
|
||||
@ -36,7 +44,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IReport } from "@/types/report.model";
|
||||
import { ActorType } from "@/types/actor";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
@Component
|
||||
export default class ReportCard extends Vue {
|
||||
|
@ -4,7 +4,10 @@
|
||||
<p class="modal-card-title">{{ title }}</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body is-flex" :class="{ 'is-titleless': !title }">
|
||||
<section
|
||||
class="modal-card-body is-flex"
|
||||
:class="{ 'is-titleless': !title }"
|
||||
>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<b-icon icon="alert" type="is-warning" size="is-large" />
|
||||
@ -16,7 +19,12 @@
|
||||
<figure class="image is-48x48" v-if="comment.actor.avatar">
|
||||
<img :src="comment.actor.avatar.url" alt="Image" />
|
||||
</figure>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<b-icon
|
||||
class="media-left"
|
||||
v-else
|
||||
size="is-large"
|
||||
icon="account-circle"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
@ -82,7 +90,10 @@ import { IComment } from "../../types/comment.model";
|
||||
},
|
||||
})
|
||||
export default class ReportModal extends Vue {
|
||||
@Prop({ type: Function }) onConfirm!: Function;
|
||||
@Prop({ type: Function }) onConfirm!: (
|
||||
content: string,
|
||||
forward: boolean
|
||||
) => void;
|
||||
|
||||
@Prop({ type: String }) title!: string;
|
||||
|
||||
|
@ -14,7 +14,9 @@
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>{{ resource.title }}</h3>
|
||||
<span class="host" v-if="inline">{{ resource.updatedAt | formatDateTimeString }}</span>
|
||||
<span class="host" v-if="inline">{{
|
||||
resource.updatedAt | formatDateTimeString
|
||||
}}</span>
|
||||
</div>
|
||||
<draggable
|
||||
v-if="!inline"
|
||||
@ -93,21 +95,27 @@ export default class FolderItem extends Mixins(ResourceMixin) {
|
||||
|
||||
async moveResource(resource: IResource): Promise<IResource | undefined> {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate<{ updateResource: IResource }>({
|
||||
mutation: UPDATE_RESOURCE,
|
||||
variables: {
|
||||
id: resource.id,
|
||||
path: `${this.resource.path}/${resource.title}`,
|
||||
parentId: this.resource.id,
|
||||
},
|
||||
});
|
||||
const { data } = await this.$apollo.mutate<{ updateResource: IResource }>(
|
||||
{
|
||||
mutation: UPDATE_RESOURCE,
|
||||
variables: {
|
||||
id: resource.id,
|
||||
path: `${this.resource.path}/${resource.title}`,
|
||||
parentId: this.resource.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!data) {
|
||||
console.error("Error while updating resource");
|
||||
return undefined;
|
||||
}
|
||||
return data.updateResource;
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
</b-dropdown>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class ResourceDropdown extends Vue {}
|
||||
|
@ -2,7 +2,12 @@
|
||||
<div class="resource-wrapper">
|
||||
<a :href="resource.resourceUrl" target="_blank">
|
||||
<div class="preview">
|
||||
<div v-if="resource.type && Object.keys(mapServiceTypeToIcon).includes(resource.type)">
|
||||
<div
|
||||
v-if="
|
||||
resource.type &&
|
||||
Object.keys(mapServiceTypeToIcon).includes(resource.type)
|
||||
"
|
||||
>
|
||||
<b-icon :icon="mapServiceTypeToIcon[resource.type]" size="is-large" />
|
||||
</div>
|
||||
<div
|
||||
@ -21,7 +26,9 @@
|
||||
:src="resource.metadata.faviconUrl"
|
||||
/>
|
||||
<h3>{{ resource.title }}</h3>
|
||||
<span class="host" v-if="inline">{{ resource.updatedAt | formatDateTimeString }}</span>
|
||||
<span class="host" v-if="inline">{{
|
||||
resource.updatedAt | formatDateTimeString
|
||||
}}</span>
|
||||
<span class="host" v-else>{{ urlHostname }}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -2,9 +2,15 @@
|
||||
<div v-if="resource">
|
||||
<article class="panel is-primary">
|
||||
<p class="panel-heading">
|
||||
{{ $t('Move "{resourceName}"', { resourceName: initialResource.title }) }}
|
||||
{{
|
||||
$t('Move "{resourceName}"', { resourceName: initialResource.title })
|
||||
}}
|
||||
</p>
|
||||
<a class="panel-block clickable" @click="resource = resource.parent" v-if="resource.parent">
|
||||
<a
|
||||
class="panel-block clickable"
|
||||
@click="resource = resource.parent"
|
||||
v-if="resource.parent"
|
||||
>
|
||||
<span class="panel-icon">
|
||||
<b-icon icon="chevron-up" size="is-small" />
|
||||
</span>
|
||||
@ -23,12 +29,19 @@
|
||||
<a
|
||||
class="panel-block"
|
||||
v-for="element in resource.children.elements"
|
||||
:class="{ clickable: element.type === 'folder' && element.id !== initialResource.id }"
|
||||
:class="{
|
||||
clickable:
|
||||
element.type === 'folder' && element.id !== initialResource.id,
|
||||
}"
|
||||
:key="element.id"
|
||||
@click="goDown(element)"
|
||||
>
|
||||
<span class="panel-icon">
|
||||
<b-icon icon="folder" size="is-small" v-if="element.type === 'folder'" />
|
||||
<b-icon
|
||||
icon="folder"
|
||||
size="is-small"
|
||||
v-if="element.type === 'folder'"
|
||||
/>
|
||||
<b-icon icon="link" size="is-small" v-else />
|
||||
</span>
|
||||
{{ element.title }}
|
||||
@ -44,10 +57,17 @@
|
||||
{{ $t("No resources in this folder") }}
|
||||
</p>
|
||||
</article>
|
||||
<b-button type="is-primary" @click="updateResource" :disabled="moveDisabled">{{
|
||||
$t("Move resource to {folder}", { folder: resource.title })
|
||||
<b-button
|
||||
type="is-primary"
|
||||
@click="updateResource"
|
||||
:disabled="moveDisabled"
|
||||
>{{
|
||||
$t("Move resource to {folder}", { folder: resource.title })
|
||||
}}</b-button
|
||||
>
|
||||
<b-button type="is-text" @click="$emit('close-move-modal')">{{
|
||||
$t("Cancel")
|
||||
}}</b-button>
|
||||
<b-button type="is-text" @click="$emit('closeMoveModal')">{{ $t("Cancel") }}</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
@ -81,31 +101,34 @@ export default class ResourceSelector extends Vue {
|
||||
|
||||
resource: IResource | undefined = this.initialResource.parent;
|
||||
|
||||
goDown(element: IResource) {
|
||||
goDown(element: IResource): void {
|
||||
if (element.type === "folder" && element.id !== this.initialResource.id) {
|
||||
this.resource = element;
|
||||
}
|
||||
}
|
||||
|
||||
updateResource() {
|
||||
updateResource(): void {
|
||||
this.$emit(
|
||||
"updateResource",
|
||||
"update-resource",
|
||||
{
|
||||
id: this.initialResource.id,
|
||||
title: this.initialResource.title,
|
||||
parent: this.resource && this.resource.path === "/" ? null : this.resource,
|
||||
parent:
|
||||
this.resource && this.resource.path === "/" ? null : this.resource,
|
||||
path: this.initialResource.path,
|
||||
},
|
||||
this.initialResource.parent
|
||||
);
|
||||
}
|
||||
|
||||
get moveDisabled() {
|
||||
get moveDisabled(): boolean | undefined {
|
||||
return (
|
||||
(this.initialResource.parent &&
|
||||
this.resource &&
|
||||
this.initialResource.parent.path === this.resource.path) ||
|
||||
(this.initialResource.parent == undefined && this.resource && this.resource.path === "/")
|
||||
(this.initialResource.parent === undefined &&
|
||||
this.resource &&
|
||||
this.resource.path === "/")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ export default class SearchField extends Vue {
|
||||
|
||||
search = "";
|
||||
|
||||
enter() {
|
||||
async enter(): Promise<void> {
|
||||
this.$emit("navbar-search");
|
||||
this.$router.push({
|
||||
await this.$router.push({
|
||||
name: RouteName.SEARCH,
|
||||
query: { term: this.search },
|
||||
});
|
||||
|
@ -15,16 +15,27 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-checkbox v-model="notificationOnDay" @input="updateSetting({ notificationOnDay })">
|
||||
<b-checkbox
|
||||
v-model="notificationOnDay"
|
||||
@input="updateSetting({ notificationOnDay })"
|
||||
>
|
||||
<strong>{{ $t("Notification on the day of the event") }}</strong>
|
||||
<p>
|
||||
{{
|
||||
$t("We'll use your timezone settings to send a recap of the morning of the event.")
|
||||
$t(
|
||||
"We'll use your timezone settings to send a recap of the morning of the event."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</b-checkbox>
|
||||
</div>
|
||||
<p>{{ $t("To activate more notifications, head over to the notification settings.") }}</p>
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
"To activate more notifications, head over to the notification settings."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@ -50,7 +61,11 @@ export default class NotificationsOnboarding extends mixins(Onboarding) {
|
||||
try {
|
||||
this.doUpdateSetting(variables);
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export default class SettingMenuItem extends Vue {
|
||||
|
||||
@Prop({ required: true, type: Object }) to!: Route;
|
||||
|
||||
get isActive() {
|
||||
get isActive(): boolean {
|
||||
if (!this.to) return false;
|
||||
if (this.to.name === this.$route.name) {
|
||||
if (this.to.params) {
|
||||
|
@ -20,11 +20,12 @@ export default class SettingMenuSection extends Vue {
|
||||
|
||||
@Prop({ required: true, type: Object }) to!: Route;
|
||||
|
||||
get sectionActive() {
|
||||
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 },
|
||||
},
|
||||
|
@ -1,18 +1,27 @@
|
||||
<template>
|
||||
<aside>
|
||||
<ul>
|
||||
<SettingMenuSection :title="$t('Account')" :to="{ name: RouteName.ACCOUNT_SETTINGS }">
|
||||
<SettingMenuSection
|
||||
:title="$t('Account')"
|
||||
:to="{ name: RouteName.ACCOUNT_SETTINGS }"
|
||||
>
|
||||
<SettingMenuItem
|
||||
:title="this.$t('General')"
|
||||
:to="{ name: RouteName.ACCOUNT_SETTINGS_GENERAL }"
|
||||
/>
|
||||
<SettingMenuItem :title="$t('Preferences')" :to="{ name: RouteName.PREFERENCES }" />
|
||||
<SettingMenuItem
|
||||
:title="$t('Preferences')"
|
||||
:to="{ name: RouteName.PREFERENCES }"
|
||||
/>
|
||||
<SettingMenuItem
|
||||
:title="this.$t('Email notifications')"
|
||||
:to="{ name: RouteName.NOTIFICATIONS }"
|
||||
/>
|
||||
</SettingMenuSection>
|
||||
<SettingMenuSection :title="$t('Profiles')" :to="{ name: RouteName.IDENTITIES }">
|
||||
<SettingMenuSection
|
||||
:title="$t('Profiles')"
|
||||
:to="{ name: RouteName.IDENTITIES }"
|
||||
>
|
||||
<SettingMenuItem
|
||||
v-for="profile in identities"
|
||||
:key="profile.preferredUsername"
|
||||
@ -22,7 +31,10 @@
|
||||
params: { identityName: profile.preferredUsername },
|
||||
}"
|
||||
/>
|
||||
<SettingMenuItem :title="$t('New profile')" :to="{ name: RouteName.CREATE_IDENTITY }" />
|
||||
<SettingMenuItem
|
||||
:title="$t('New profile')"
|
||||
:to="{ name: RouteName.CREATE_IDENTITY }"
|
||||
/>
|
||||
</SettingMenuSection>
|
||||
<SettingMenuSection
|
||||
v-if="
|
||||
@ -33,35 +45,54 @@
|
||||
:title="$t('Moderation')"
|
||||
:to="{ name: RouteName.MODERATION }"
|
||||
>
|
||||
<SettingMenuItem :title="$t('Reports')" :to="{ name: RouteName.REPORTS }" />
|
||||
<SettingMenuItem :title="$t('Moderation log')" :to="{ name: RouteName.REPORT_LOGS }" />
|
||||
<SettingMenuItem
|
||||
:title="$t('Reports')"
|
||||
:to="{ name: RouteName.REPORTS }"
|
||||
/>
|
||||
<SettingMenuItem
|
||||
:title="$t('Moderation log')"
|
||||
:to="{ name: RouteName.REPORT_LOGS }"
|
||||
/>
|
||||
<SettingMenuItem :title="$t('Users')" :to="{ name: RouteName.USERS }" />
|
||||
<SettingMenuItem :title="$t('Profiles')" :to="{ name: RouteName.PROFILES }" />
|
||||
<SettingMenuItem :title="$t('Groups')" :to="{ name: RouteName.ADMIN_GROUPS }" />
|
||||
<SettingMenuItem
|
||||
:title="$t('Profiles')"
|
||||
:to="{ name: RouteName.PROFILES }"
|
||||
/>
|
||||
<SettingMenuItem
|
||||
:title="$t('Groups')"
|
||||
:to="{ name: RouteName.ADMIN_GROUPS }"
|
||||
/>
|
||||
</SettingMenuSection>
|
||||
<SettingMenuSection
|
||||
v-if="this.currentUser.role == ICurrentUserRole.ADMINISTRATOR"
|
||||
:title="$t('Admin')"
|
||||
:to="{ name: RouteName.ADMIN }"
|
||||
>
|
||||
<SettingMenuItem :title="$t('Dashboard')" :to="{ name: RouteName.ADMIN_DASHBOARD }" />
|
||||
<SettingMenuItem
|
||||
:title="$t('Dashboard')"
|
||||
:to="{ name: RouteName.ADMIN_DASHBOARD }"
|
||||
/>
|
||||
<SettingMenuItem
|
||||
:title="$t('Instance settings')"
|
||||
:to="{ name: RouteName.ADMIN_SETTINGS }"
|
||||
/>
|
||||
<SettingMenuItem :title="$t('Federation')" :to="{ name: RouteName.RELAYS }" />
|
||||
<SettingMenuItem
|
||||
:title="$t('Federation')"
|
||||
:to="{ name: RouteName.RELAYS }"
|
||||
/>
|
||||
</SettingMenuSection>
|
||||
</ul>
|
||||
</aside>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { ICurrentUserRole } from "@/types/enums";
|
||||
import SettingMenuSection from "./SettingMenuSection.vue";
|
||||
import SettingMenuItem from "./SettingMenuItem.vue";
|
||||
import { IDENTITIES } from "../../graphql/actor";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
import { CURRENT_USER_CLIENT } from "../../graphql/user";
|
||||
import { ICurrentUser, ICurrentUserRole } from "../../types/current-user.model";
|
||||
import { ICurrentUser } from "../../types/current-user.model";
|
||||
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@ -70,7 +101,8 @@ import RouteName from "../../router/name";
|
||||
apollo: {
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
update: (data) => data.identities.map((identity: IPerson) => new Person(identity)),
|
||||
update: (data) =>
|
||||
data.identities.map((identity: IPerson) => new Person(identity)),
|
||||
},
|
||||
currentUser: CURRENT_USER_CLIENT,
|
||||
},
|
||||
|
@ -39,7 +39,10 @@
|
||||
timezone,
|
||||
})
|
||||
}}
|
||||
<b-message type="is-danger" v-if="!$apollo.loading && !supportedTimezone">
|
||||
<b-message
|
||||
type="is-danger"
|
||||
v-if="!$apollo.loading && !supportedTimezone"
|
||||
>
|
||||
{{ $t("Your timezone {timezone} isn't supported.", { timezone }) }}
|
||||
</b-message>
|
||||
</p>
|
||||
|
@ -2,9 +2,10 @@
|
||||
<div class="card" v-if="todo">
|
||||
<div class="card-content">
|
||||
<b-checkbox v-model="status" />
|
||||
<router-link :to="{ name: RouteName.TODO, params: { todoId: todo.id } }">{{
|
||||
todo.title
|
||||
}}</router-link>
|
||||
<router-link
|
||||
:to="{ name: RouteName.TODO, params: { todoId: todo.id } }"
|
||||
>{{ todo.title }}</router-link
|
||||
>
|
||||
<span class="details has-text-grey">
|
||||
<span v-if="todo.dueDate" class="due_date">
|
||||
<b-icon icon="calendar" />
|
||||
@ -13,7 +14,9 @@
|
||||
<span v-if="todo.assignedTo" class="assigned_to">
|
||||
<b-icon icon="account" />
|
||||
{{ `@${todo.assignedTo.preferredUsername}` }}
|
||||
<span v-if="todo.assignedTo.domain">{{ `@${todo.assignedTo.domain}` }}</span>
|
||||
<span v-if="todo.assignedTo.domain">{{
|
||||
`@${todo.assignedTo.domain}`
|
||||
}}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@ -53,7 +56,11 @@ export default class Todo extends Vue {
|
||||
});
|
||||
this.editMode = false;
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { debounce } from "lodash";
|
||||
import { debounce, DebouncedFunc } from "lodash";
|
||||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
import { ITodo } from "../../types/todos";
|
||||
import RouteName from "../../router/name";
|
||||
@ -36,7 +36,9 @@ export default class Todo extends Vue {
|
||||
|
||||
editMode = false;
|
||||
|
||||
debounceUpdateTodo!: Function;
|
||||
debounceUpdateTodo!: DebouncedFunc<
|
||||
(obj: Record<string, unknown>) => Promise<void>
|
||||
>;
|
||||
|
||||
// We put this in data because of issues like
|
||||
// https://github.com/vuejs/vue-class-component/issues/263
|
||||
@ -89,7 +91,11 @@ export default class Todo extends Vue {
|
||||
});
|
||||
this.editMode = false;
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,11 @@
|
||||
<b-icon :icon="oauthProvider.id" />
|
||||
<span>{{ SELECTED_PROVIDERS[oauthProvider.id] }}</span></a
|
||||
>
|
||||
<a class="button is-light" :href="`/auth/${oauthProvider.id}`" v-else-if="isProviderSelected">
|
||||
<a
|
||||
class="button is-light"
|
||||
:href="`/auth/${oauthProvider.id}`"
|
||||
v-else-if="isProviderSelected"
|
||||
>
|
||||
<b-icon icon="lock" />
|
||||
<span>{{ oauthProvider.label }}</span>
|
||||
</a>
|
||||
|
@ -20,7 +20,9 @@
|
||||
</div>
|
||||
<vertical-divider :content="$t('Or')" />
|
||||
<div class="column">
|
||||
<subtitle>{{ $t("I have an account on another Mobilizon instance.") }}</subtitle>
|
||||
<subtitle>{{
|
||||
$t("I have an account on another Mobilizon instance.")
|
||||
}}</subtitle>
|
||||
<p>{{ $t("Other software may also support this.") }}</p>
|
||||
<p>{{ sentence }}</p>
|
||||
<form @submit.prevent="redirectToInstance">
|
||||
@ -34,7 +36,9 @@
|
||||
:placeholder="$t('profile@instance')"
|
||||
></b-input>
|
||||
<p class="control">
|
||||
<button class="button is-primary" type="submit">{{ $t("Go") }}</button>
|
||||
<button class="button is-primary" type="submit">
|
||||
{{ $t("Go") }}
|
||||
</button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
@ -54,7 +58,7 @@
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import VerticalDivider from "@/components/Utils/VerticalDivider.vue";
|
||||
import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||
import { LoginErrorCode } from "@/types/login-error-code.model";
|
||||
import { LoginErrorCode } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
@ -80,14 +84,22 @@ export default class RedirectWithAccount extends Vue {
|
||||
|
||||
async redirectToInstance(): Promise<void> {
|
||||
const [, host] = this.remoteActorAddress.split("@", 2);
|
||||
const remoteInteractionURI = await this.webFingerFetch(host, this.remoteActorAddress);
|
||||
const remoteInteractionURI = await this.webFingerFetch(
|
||||
host,
|
||||
this.remoteActorAddress
|
||||
);
|
||||
window.open(remoteInteractionURI);
|
||||
}
|
||||
|
||||
private async webFingerFetch(hostname: string, identity: string): Promise<string> {
|
||||
private async webFingerFetch(
|
||||
hostname: string,
|
||||
identity: string
|
||||
): Promise<string> {
|
||||
const scheme = process.env.NODE_ENV === "production" ? "https" : "http";
|
||||
const data = await (
|
||||
await fetch(`${scheme}://${hostname}/.well-known/webfinger?resource=acct:${identity}`)
|
||||
await fetch(
|
||||
`${scheme}://${hostname}/.well-known/webfinger?resource=acct:${identity}`
|
||||
)
|
||||
).json();
|
||||
if (data && Array.isArray(data.links)) {
|
||||
const link: { template: string } = data.links.find(
|
||||
|
@ -21,7 +21,8 @@ h2 {
|
||||
display: inline;
|
||||
padding: 3px 8px;
|
||||
color: #3a384c;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
|
||||
serif;
|
||||
font-weight: 400;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
@ -14,7 +14,10 @@ function formatDateString(value: string): string {
|
||||
}
|
||||
|
||||
function formatTimeString(value: string): string {
|
||||
return parseDateTime(value).toLocaleTimeString(undefined, { hour: "numeric", minute: "numeric" });
|
||||
return parseDateTime(value).toLocaleTimeString(undefined, {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function formatDateTimeString(value: string, showTime = true): string {
|
||||
|
@ -1,5 +1,9 @@
|
||||
import nl2br from "@/filters/utils";
|
||||
import { formatDateString, formatTimeString, formatDateTimeString } from "./datetime";
|
||||
import {
|
||||
formatDateString,
|
||||
formatTimeString,
|
||||
formatDateTimeString,
|
||||
} from "./datetime";
|
||||
|
||||
export default {
|
||||
install(vue: any): void {
|
||||
|
@ -65,7 +65,10 @@ export const GET_PERSON = gql`
|
||||
feedTokens {
|
||||
token
|
||||
}
|
||||
organizedEvents(page: $organizedEventsPage, limit: $organizedEventsLimit) {
|
||||
organizedEvents(
|
||||
page: $organizedEventsPage
|
||||
limit: $organizedEventsLimit
|
||||
) {
|
||||
total
|
||||
elements {
|
||||
id
|
||||
@ -442,7 +445,12 @@ export const CREATE_PERSON = gql`
|
||||
`;
|
||||
|
||||
export const UPDATE_PERSON = gql`
|
||||
mutation UpdatePerson($id: ID!, $name: String, $summary: String, $avatar: MediaInput) {
|
||||
mutation UpdatePerson(
|
||||
$id: ID!
|
||||
$name: String
|
||||
$summary: String
|
||||
$avatar: MediaInput
|
||||
) {
|
||||
updatePerson(id: $id, name: $name, summary: $summary, avatar: $avatar) {
|
||||
id
|
||||
preferredUsername
|
||||
@ -469,7 +477,12 @@ export const DELETE_PERSON = gql`
|
||||
* Prefer CREATE_PERSON when creating another identity
|
||||
*/
|
||||
export const REGISTER_PERSON = gql`
|
||||
mutation($preferredUsername: String!, $name: String!, $summary: String!, $email: String!) {
|
||||
mutation(
|
||||
$preferredUsername: String!
|
||||
$name: String!
|
||||
$summary: String!
|
||||
$email: String!
|
||||
) {
|
||||
registerPerson(
|
||||
preferredUsername: $preferredUsername
|
||||
name: $name
|
||||
|
@ -68,8 +68,16 @@ export const COMMENTS_THREADS = gql`
|
||||
`;
|
||||
|
||||
export const CREATE_COMMENT_FROM_EVENT = gql`
|
||||
mutation CreateCommentFromEvent($eventId: ID!, $text: String!, $inReplyToCommentId: ID) {
|
||||
createComment(eventId: $eventId, text: $text, inReplyToCommentId: $inReplyToCommentId) {
|
||||
mutation CreateCommentFromEvent(
|
||||
$eventId: ID!
|
||||
$text: String!
|
||||
$inReplyToCommentId: ID
|
||||
) {
|
||||
createComment(
|
||||
eventId: $eventId
|
||||
text: $text
|
||||
inReplyToCommentId: $inReplyToCommentId
|
||||
) {
|
||||
...CommentRecursive
|
||||
}
|
||||
}
|
||||
|
@ -42,8 +42,20 @@ export const SEARCH_EVENTS = gql`
|
||||
`;
|
||||
|
||||
export const SEARCH_GROUPS = gql`
|
||||
query SearchGroups($term: String, $location: String, $radius: Float, $page: Int, $limit: Int) {
|
||||
searchGroups(term: $term, location: $location, radius: $radius, page: $page, limit: $limit) {
|
||||
query SearchGroups(
|
||||
$term: String
|
||||
$location: String
|
||||
$radius: Float
|
||||
$page: Int
|
||||
$limit: Int
|
||||
) {
|
||||
searchGroups(
|
||||
term: $term
|
||||
location: $location
|
||||
radius: $radius
|
||||
page: $page
|
||||
limit: $limit
|
||||
) {
|
||||
total
|
||||
elements {
|
||||
id
|
||||
|
@ -108,7 +108,12 @@ export const UPDATE_CURRENT_USER_CLIENT = gql`
|
||||
$isLoggedIn: Boolean!
|
||||
$role: UserRole!
|
||||
) {
|
||||
updateCurrentUser(id: $id, email: $email, isLoggedIn: $isLoggedIn, role: $role) @client
|
||||
updateCurrentUser(
|
||||
id: $id
|
||||
email: $email
|
||||
isLoggedIn: $isLoggedIn
|
||||
role: $role
|
||||
) @client
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -4,9 +4,11 @@ import { Component, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class ActorMixin extends Vue {
|
||||
static actorIsOrganizer(actor: IActor, event: IEvent) {
|
||||
static actorIsOrganizer(actor: IActor, event: IEvent): boolean {
|
||||
console.log("actorIsOrganizer actor", actor.id);
|
||||
console.log("actorIsOrganizer event", event);
|
||||
return event.organizerActor && actor.id === event.organizerActor.id;
|
||||
return (
|
||||
event.organizerActor !== undefined && actor.id === event.organizerActor.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { mixins } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
import { IParticipant, ParticipantRole } from "../types/participant.model";
|
||||
import { ParticipantRole } from "@/types/enums";
|
||||
import { IParticipant } from "../types/participant.model";
|
||||
import { IEvent } from "../types/event.model";
|
||||
import {
|
||||
DELETE_EVENT,
|
||||
@ -20,7 +21,9 @@ export default class EventMixin extends mixins(Vue) {
|
||||
anonymousParticipationConfirmed: boolean | null = null
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { data: resultData } = await this.$apollo.mutate<{ leaveEvent: IParticipant }>({
|
||||
const { data: resultData } = await this.$apollo.mutate<{
|
||||
leaveEvent: IParticipant;
|
||||
}>({
|
||||
mutation: LEAVE_EVENT,
|
||||
variables: {
|
||||
eventId: event.id,
|
||||
@ -32,14 +35,18 @@ export default class EventMixin extends mixins(Vue) {
|
||||
let participation;
|
||||
|
||||
if (!token) {
|
||||
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
||||
const participationCachedData = store.readQuery<{
|
||||
person: IPerson;
|
||||
}>({
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables: { eventId: event.id, actorId },
|
||||
});
|
||||
if (participationCachedData == null) return;
|
||||
const { person } = participationCachedData;
|
||||
if (person === null) {
|
||||
console.error("Cannot update participation cache, because of null value.");
|
||||
console.error(
|
||||
"Cannot update participation cache, because of null value."
|
||||
);
|
||||
return;
|
||||
}
|
||||
[participation] = person.participations.elements;
|
||||
@ -62,7 +69,10 @@ export default class EventMixin extends mixins(Vue) {
|
||||
console.error("Cannot update event cache, because of null value.");
|
||||
return;
|
||||
}
|
||||
if (participation && participation.role === ParticipantRole.NOT_APPROVED) {
|
||||
if (
|
||||
participation &&
|
||||
participation.role === ParticipantRole.NOT_APPROVED
|
||||
) {
|
||||
eventCached.participantStats.notApproved -= 1;
|
||||
} else if (anonymousParticipationConfirmed === false) {
|
||||
eventCached.participantStats.notConfirmed -= 1;
|
||||
@ -81,13 +91,19 @@ export default class EventMixin extends mixins(Vue) {
|
||||
this.participationCancelledMessage();
|
||||
}
|
||||
} catch (error) {
|
||||
Snackbar.open({ message: error.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: error.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private participationCancelledMessage() {
|
||||
this.$notifier.success(this.$t("You have cancelled your participation") as string);
|
||||
this.$notifier.success(
|
||||
this.$t("You have cancelled your participation") as string
|
||||
);
|
||||
}
|
||||
|
||||
protected async openDeleteEventModal(event: IEvent): Promise<void> {
|
||||
@ -96,21 +112,29 @@ export default class EventMixin extends mixins(Vue) {
|
||||
}
|
||||
const participantsLength = event.participantStats.participant;
|
||||
const prefix = participantsLength
|
||||
? this.$tc("There are {participants} participants.", event.participantStats.participant, {
|
||||
participants: event.participantStats.participant,
|
||||
})
|
||||
? this.$tc(
|
||||
"There are {participants} participants.",
|
||||
event.participantStats.participant,
|
||||
{
|
||||
participants: event.participantStats.participant,
|
||||
}
|
||||
)
|
||||
: "";
|
||||
|
||||
this.$buefy.dialog.prompt({
|
||||
type: "is-danger",
|
||||
title: this.$t("Delete event") as string,
|
||||
message: `${prefix}
|
||||
${this.$t("Are you sure you want to delete this event? This action cannot be reverted.")}
|
||||
${this.$t(
|
||||
"Are you sure you want to delete this event? This action cannot be reverted."
|
||||
)}
|
||||
<br><br>
|
||||
${this.$t('To confirm, type your event title "{eventTitle}"', {
|
||||
eventTitle: event.title,
|
||||
})}`,
|
||||
confirmText: this.$t("Delete {eventTitle}", { eventTitle: event.title }) as string,
|
||||
confirmText: this.$t("Delete {eventTitle}", {
|
||||
eventTitle: event.title,
|
||||
}) as string,
|
||||
inputAttrs: {
|
||||
placeholder: event.title,
|
||||
pattern: escapeRegExp(event.title),
|
||||
@ -138,13 +162,19 @@ export default class EventMixin extends mixins(Vue) {
|
||||
this.$emit("event-deleted", event.id);
|
||||
|
||||
this.$buefy.notification.open({
|
||||
message: this.$t("Event {eventTitle} deleted", { eventTitle }) as string,
|
||||
message: this.$t("Event {eventTitle} deleted", {
|
||||
eventTitle,
|
||||
}) as string,
|
||||
type: "is-success",
|
||||
position: "is-bottom-right",
|
||||
duration: 5000,
|
||||
});
|
||||
} catch (error) {
|
||||
Snackbar.open({ message: error.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: error.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ import { PERSON_MEMBERSHIPS, CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import { GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED } from "@/graphql/event";
|
||||
import { FETCH_GROUP } from "@/graphql/group";
|
||||
import RouteName from "@/router/name";
|
||||
import { Group, IActor, IGroup, IPerson, MemberRole } from "@/types/actor";
|
||||
import { Group, IActor, IGroup, IPerson } from "@/types/actor";
|
||||
import { MemberRole } from "@/types/enums";
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component({
|
||||
@ -60,7 +61,10 @@ export default class GroupMixin extends Vue {
|
||||
}
|
||||
|
||||
get isCurrentActorAGroupModerator(): boolean {
|
||||
return this.hasCurrentActorThisRole([MemberRole.MODERATOR, MemberRole.ADMINISTRATOR]);
|
||||
return this.hasCurrentActorThisRole([
|
||||
MemberRole.MODERATOR,
|
||||
MemberRole.ADMINISTRATOR,
|
||||
]);
|
||||
}
|
||||
|
||||
hasCurrentActorThisRole(givenRole: string | string[]): boolean {
|
||||
@ -68,7 +72,8 @@ export default class GroupMixin extends Vue {
|
||||
return (
|
||||
this.person &&
|
||||
this.person.memberships.elements.some(
|
||||
({ parent: { id }, role }) => id === this.group.id && roles.includes(role)
|
||||
({ parent: { id }, role }) =>
|
||||
id === this.group.id && roles.includes(role)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -9,10 +9,14 @@ export default class IdentityEditionMixin extends Mixins(Vue) {
|
||||
oldDisplayName: string | null = null;
|
||||
|
||||
autoUpdateUsername(newDisplayName: string | null): void {
|
||||
const oldUsername = IdentityEditionMixin.convertToUsername(this.oldDisplayName);
|
||||
const oldUsername = IdentityEditionMixin.convertToUsername(
|
||||
this.oldDisplayName
|
||||
);
|
||||
|
||||
if (this.identity.preferredUsername === oldUsername) {
|
||||
this.identity.preferredUsername = IdentityEditionMixin.convertToUsername(newDisplayName);
|
||||
this.identity.preferredUsername = IdentityEditionMixin.convertToUsername(
|
||||
newDisplayName
|
||||
);
|
||||
}
|
||||
|
||||
this.oldDisplayName = newDisplayName;
|
||||
|
@ -13,7 +13,9 @@ export default class Onboarding extends Vue {
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
protected async doUpdateSetting(variables: Record<string, unknown>): Promise<void> {
|
||||
protected async doUpdateSetting(
|
||||
variables: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
await this.$apollo.mutate<{ setUserSettings: string }>({
|
||||
mutation: SET_USER_SETTINGS,
|
||||
variables,
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Component, Vue, Ref } from "vue-property-decorator";
|
||||
import { ActorType, IActor } from "@/types/actor";
|
||||
import { IActor } from "@/types/actor";
|
||||
import { IFollower } from "@/types/actor/follower.model";
|
||||
import { RELAY_FOLLOWERS, RELAY_FOLLOWINGS } from "@/graphql/admin";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@ -62,7 +63,10 @@ export default class RelayMixin extends Vue {
|
||||
relayFollowings: {
|
||||
__typename: previousResult.relayFollowings.__typename,
|
||||
total: previousResult.relayFollowings.total,
|
||||
elements: [...previousResult.relayFollowings.elements, ...newFollowings],
|
||||
elements: [
|
||||
...previousResult.relayFollowings.elements,
|
||||
...newFollowings,
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
@ -87,7 +91,10 @@ export default class RelayMixin extends Vue {
|
||||
relayFollowers: {
|
||||
__typename: previousResult.relayFollowers.__typename,
|
||||
total: previousResult.relayFollowers.total,
|
||||
elements: [...previousResult.relayFollowers.elements, ...newFollowers],
|
||||
elements: [
|
||||
...previousResult.relayFollowers.elements,
|
||||
...newFollowers,
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
@ -100,7 +107,8 @@ export default class RelayMixin extends Vue {
|
||||
static isInstance(actor: IActor): boolean {
|
||||
return (
|
||||
actor.type === ActorType.APPLICATION &&
|
||||
(actor.preferredUsername === "relay" || actor.preferredUsername === actor.domain)
|
||||
(actor.preferredUsername === "relay" ||
|
||||
actor.preferredUsername === actor.domain)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,10 @@ declare module "vue/types/vue" {
|
||||
}
|
||||
}
|
||||
|
||||
export function DateFnsPlugin(vue: typeof VueInstance, { locale }: { locale: string }): void {
|
||||
export function DateFnsPlugin(
|
||||
vue: typeof VueInstance,
|
||||
{ locale }: { locale: string }
|
||||
): void {
|
||||
import(`date-fns/locale/${locale}/index.js`).then((localeEntity) => {
|
||||
VueInstance.prototype.$dateFnsLocale = localeEntity;
|
||||
});
|
||||
|
@ -23,7 +23,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
console.log("New content is available; please refresh.");
|
||||
},
|
||||
offline() {
|
||||
console.log("No internet connection found. App is running in offline mode.");
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode."
|
||||
);
|
||||
},
|
||||
error(error) {
|
||||
console.error("Error during service worker registration:", error);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { RouteConfig } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
export enum ActorRouteName {
|
||||
GROUP = "Group",
|
||||
@ -11,20 +12,23 @@ export const actorRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/groups/create",
|
||||
name: ActorRouteName.CREATE_GROUP,
|
||||
component: () => import(/* webpackChunkName: "CreateGroup" */ "@/views/Group/Create.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "CreateGroup" */ "@/views/Group/Create.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername",
|
||||
name: ActorRouteName.GROUP,
|
||||
component: () => import(/* webpackChunkName: "Group" */ "@/views/Group/Group.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Group" */ "@/views/Group/Group.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/groups/me",
|
||||
name: ActorRouteName.MY_GROUPS,
|
||||
component: () => import(/* webpackChunkName: "MyGroups" */ "@/views/Group/MyGroups.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "MyGroups" */ "@/views/Group/MyGroups.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
];
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { RouteConfig } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
export enum DiscussionRouteName {
|
||||
DISCUSSION_LIST = "DISCUSSION_LIST",
|
||||
@ -10,24 +11,30 @@ export const discussionRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/@:preferredUsername/discussions",
|
||||
name: DiscussionRouteName.DISCUSSION_LIST,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "DiscussionsList" */ "@/views/Discussions/DiscussionsList.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "DiscussionsList" */ "@/views/Discussions/DiscussionsList.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/discussions/new",
|
||||
name: DiscussionRouteName.CREATE_DISCUSSION,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "CreateDiscussion" */ "@/views/Discussions/Create.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "CreateDiscussion" */ "@/views/Discussions/Create.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/c/:slug/:comment_id?",
|
||||
name: DiscussionRouteName.DISCUSSION,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "Discussion" */ "@/views/Discussions/Discussion.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Discussion" */ "@/views/Discussions/Discussion.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { beforeRegisterGuard } from "@/router/guards/register-guard";
|
||||
import { RouteConfig } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
export enum ErrorRouteName {
|
||||
ERROR = "Error",
|
||||
@ -9,7 +10,8 @@ export const errorRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/error",
|
||||
name: ErrorRouteName.ERROR,
|
||||
component: () => import(/* webpackChunkName: "Error" */ "../views/Error.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Error" */ "../views/Error.vue"),
|
||||
beforeEnter: beforeRegisterGuard,
|
||||
},
|
||||
];
|
||||
|
@ -1,10 +1,16 @@
|
||||
import { RouteConfig, Route } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
const participations = () =>
|
||||
import(/* webpackChunkName: "participations" */ "@/views/Event/Participants.vue");
|
||||
const editEvent = () => import(/* webpackChunkName: "edit-event" */ "@/views/Event/Edit.vue");
|
||||
const event = () => import(/* webpackChunkName: "event" */ "@/views/Event/Event.vue");
|
||||
const myEvents = () => import(/* webpackChunkName: "my-events" */ "@/views/Event/MyEvents.vue");
|
||||
const participations = (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "participations" */ "@/views/Event/Participants.vue"
|
||||
);
|
||||
const editEvent = (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "edit-event" */ "@/views/Event/Edit.vue");
|
||||
const event = (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "event" */ "@/views/Event/Event.vue");
|
||||
const myEvents = (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "my-events" */ "@/views/Event/MyEvents.vue");
|
||||
|
||||
export enum EventRouteName {
|
||||
EVENT_LIST = "EventList",
|
||||
@ -18,7 +24,6 @@ export enum EventRouteName {
|
||||
EVENT_PARTICIPATE_WITHOUT_ACCOUNT = "EVENT_PARTICIPATE_WITHOUT_ACCOUNT",
|
||||
EVENT_PARTICIPATE_LOGGED_OUT = "EVENT_PARTICIPATE_LOGGED_OUT",
|
||||
EVENT_PARTICIPATE_CONFIRM = "EVENT_PARTICIPATE_CONFIRM",
|
||||
LOCATION = "Location",
|
||||
TAG = "Tag",
|
||||
}
|
||||
|
||||
@ -26,7 +31,8 @@ export const eventRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/events/list/:location?",
|
||||
name: EventRouteName.EVENT_LIST,
|
||||
component: () => import(/* webpackChunkName: "EventList" */ "@/views/Event/EventList.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "EventList" */ "@/views/Event/EventList.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
@ -46,14 +52,19 @@ export const eventRoutes: RouteConfig[] = [
|
||||
name: EventRouteName.EDIT_EVENT,
|
||||
component: editEvent,
|
||||
meta: { requiredAuth: true },
|
||||
props: (route: Route) => ({ ...route.params, ...{ isUpdate: true } }),
|
||||
props: (route: Route): Record<string, unknown> => {
|
||||
return { ...route.params, ...{ isUpdate: true } };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/events/duplicate/:eventId",
|
||||
name: EventRouteName.DUPLICATE_EVENT,
|
||||
component: editEvent,
|
||||
meta: { requiredAuth: true },
|
||||
props: (route: Route) => ({ ...route.params, ...{ isDuplicate: true } }),
|
||||
props: (route: Route): Record<string, unknown> => ({
|
||||
...route.params,
|
||||
...{ isDuplicate: true },
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/events/:eventId/participations",
|
||||
@ -62,12 +73,6 @@ export const eventRoutes: RouteConfig[] = [
|
||||
meta: { requiredAuth: true },
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/location/new",
|
||||
name: EventRouteName.LOCATION,
|
||||
component: () => import(/* webpackChunkName: "Location" */ "@/views/Location.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/events/:uuid",
|
||||
name: EventRouteName.EVENT,
|
||||
@ -78,31 +83,36 @@ export const eventRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/events/:uuid/participate",
|
||||
name: EventRouteName.EVENT_PARTICIPATE_LOGGED_OUT,
|
||||
component: () => import("../components/Participation/UnloggedParticipation.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("../components/Participation/UnloggedParticipation.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/events/:uuid/participate/with-account",
|
||||
name: EventRouteName.EVENT_PARTICIPATE_WITH_ACCOUNT,
|
||||
component: () => import("../components/Participation/ParticipationWithAccount.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("../components/Participation/ParticipationWithAccount.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/events/:uuid/participate/without-account",
|
||||
name: EventRouteName.EVENT_PARTICIPATE_WITHOUT_ACCOUNT,
|
||||
component: () => import("../components/Participation/ParticipationWithoutAccount.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("../components/Participation/ParticipationWithoutAccount.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/participation/email/confirm/:token",
|
||||
name: EventRouteName.EVENT_PARTICIPATE_CONFIRM,
|
||||
component: () => import("../components/Participation/ConfirmParticipation.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("../components/Participation/ConfirmParticipation.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/tag/:tag",
|
||||
name: EventRouteName.TAG,
|
||||
component: () => import(/* webpackChunkName: "Search" */ "@/views/Search.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Search" */ "@/views/Search.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { RouteConfig, Route } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
export enum GroupsRouteName {
|
||||
TODO_LISTS = "TODO_LISTS",
|
||||
@ -18,29 +19,33 @@ export enum GroupsRouteName {
|
||||
GROUP_JOIN = "GROUP_JOIN",
|
||||
}
|
||||
|
||||
const resourceFolder = () => import("@/views/Resources/ResourceFolder.vue");
|
||||
const groupEvents = () =>
|
||||
const resourceFolder = (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Resources/ResourceFolder.vue");
|
||||
const groupEvents = (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "groupEvents" */ "@/views/Event/GroupEvents.vue");
|
||||
|
||||
export const groupsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/@:preferredUsername/todo-lists",
|
||||
name: GroupsRouteName.TODO_LISTS,
|
||||
component: () => import("@/views/Todos/TodoLists.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Todos/TodoLists.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/todo-lists/:id",
|
||||
name: GroupsRouteName.TODO_LIST,
|
||||
component: () => import("@/views/Todos/TodoList.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Todos/TodoList.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/todo/:todoId",
|
||||
name: GroupsRouteName.TODO,
|
||||
component: () => import("@/views/Todos/Todo.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Todos/Todo.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
@ -60,7 +65,8 @@ export const groupsRoutes: RouteConfig[] = [
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/settings",
|
||||
component: () => import("@/views/Group/Settings.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Group/Settings.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
redirect: { name: GroupsRouteName.GROUP_PUBLIC_SETTINGS },
|
||||
@ -69,40 +75,49 @@ export const groupsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "public",
|
||||
name: GroupsRouteName.GROUP_PUBLIC_SETTINGS,
|
||||
component: () => import("../views/Group/GroupSettings.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("../views/Group/GroupSettings.vue"),
|
||||
},
|
||||
{
|
||||
path: "members",
|
||||
name: GroupsRouteName.GROUP_MEMBERS_SETTINGS,
|
||||
component: () => import("../views/Group/GroupMembers.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("../views/Group/GroupMembers.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/p/new",
|
||||
component: () => import("@/views/Posts/Edit.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Posts/Edit.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.POST_CREATE,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/p/:slug/edit",
|
||||
component: () => import("@/views/Posts/Edit.vue"),
|
||||
props: (route: Route) => ({ ...route.params, ...{ isUpdate: true } }),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Posts/Edit.vue"),
|
||||
props: (route: Route): Record<string, unknown> => ({
|
||||
...route.params,
|
||||
...{ isUpdate: true },
|
||||
}),
|
||||
name: GroupsRouteName.POST_EDIT,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/p/:slug",
|
||||
component: () => import("@/views/Posts/Post.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Posts/Post.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.POST,
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/p",
|
||||
component: () => import("@/views/Posts/List.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/views/Posts/List.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.POSTS,
|
||||
meta: { requiredAuth: false },
|
||||
@ -116,7 +131,8 @@ export const groupsRoutes: RouteConfig[] = [
|
||||
},
|
||||
{
|
||||
path: "/@:preferredUsername/join",
|
||||
component: () => import("@/components/Group/JoinGroupWithAccount.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import("@/components/Group/JoinGroupWithAccount.vue"),
|
||||
props: true,
|
||||
name: GroupsRouteName.GROUP_JOIN,
|
||||
meta: { requiredAuth: false },
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NavigationGuard } from "vue-router";
|
||||
import { UserRouteName } from "@/router/user";
|
||||
import { LoginErrorCode } from "@/types/login-error-code.model";
|
||||
import { AUTH_ACCESS_TOKEN } from "@/constants";
|
||||
import { LoginErrorCode } from "@/types/enums";
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const authGuardIfNeeded: NavigationGuard = async (to, from, next) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ErrorCode } from "@/types/enums";
|
||||
import { NavigationGuard } from "vue-router";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
import { ErrorCode } from "../../types/error-code.model";
|
||||
import apolloProvider from "../../vue-apollo";
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
|
@ -49,7 +49,8 @@ const router = new Router({
|
||||
{
|
||||
path: "/search",
|
||||
name: RouteName.SEARCH,
|
||||
component: () => import(/* webpackChunkName: "search" */ "../views/Search.vue"),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "search" */ "../views/Search.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
@ -62,7 +63,8 @@ const router = new Router({
|
||||
{
|
||||
path: "/about",
|
||||
name: RouteName.ABOUT,
|
||||
component: () => import(/* webpackChunkName: "about" */ "@/views/About.vue"),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "about" */ "@/views/About.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
redirect: { name: RouteName.ABOUT_INSTANCE },
|
||||
children: [
|
||||
@ -70,30 +72,40 @@ const router = new Router({
|
||||
path: "instance",
|
||||
name: RouteName.ABOUT_INSTANCE,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "about" */ "@/views/About/AboutInstance.vue"),
|
||||
import(
|
||||
/* webpackChunkName: "about" */ "@/views/About/AboutInstance.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/terms",
|
||||
name: RouteName.TERMS,
|
||||
component: () => import(/* webpackChunkName: "cookies" */ "@/views/About/Terms.vue"),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "cookies" */ "@/views/About/Terms.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/privacy",
|
||||
name: RouteName.PRIVACY,
|
||||
component: () => import(/* webpackChunkName: "cookies" */ "@/views/About/Privacy.vue"),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "cookies" */ "@/views/About/Privacy.vue"
|
||||
),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/rules",
|
||||
name: RouteName.RULES,
|
||||
component: () => import(/* webpackChunkName: "cookies" */ "@/views/About/Rules.vue"),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "cookies" */ "@/views/About/Rules.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/glossary",
|
||||
name: RouteName.GLOSSARY,
|
||||
component: () => import(/* webpackChunkName: "cookies" */ "@/views/About/Glossary.vue"),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "cookies" */ "@/views/About/Glossary.vue"
|
||||
),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
],
|
||||
@ -101,20 +113,25 @@ const router = new Router({
|
||||
{
|
||||
path: "/interact",
|
||||
name: RouteName.INTERACT,
|
||||
component: () => import(/* webpackChunkName: "cookies" */ "@/views/Interact.vue"),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "cookies" */ "@/views/Interact.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/auth/:provider/callback",
|
||||
name: "auth-callback",
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "ProviderValidation" */ "@/views/User/ProviderValidation.vue"),
|
||||
import(
|
||||
/* webpackChunkName: "ProviderValidation" */ "@/views/User/ProviderValidation.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/welcome/:step?",
|
||||
name: RouteName.WELCOME_SCREEN,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "WelcomeScreen" */ "@/views/User/SettingsOnboard.vue"),
|
||||
import(
|
||||
/* webpackChunkName: "WelcomeScreen" */ "@/views/User/SettingsOnboard.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
props: (route) => {
|
||||
const step = Number.parseInt(route.params.step, 10);
|
||||
@ -127,7 +144,8 @@ const router = new Router({
|
||||
{
|
||||
path: "/404",
|
||||
name: RouteName.PAGE_NOT_FOUND,
|
||||
component: () => import(/* webpackChunkName: "search" */ "../views/PageNotFound.vue"),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "search" */ "../views/PageNotFound.vue"),
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { RouteConfig } from "vue-router";
|
||||
import { Route, RouteConfig } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
export enum SettingsRouteName {
|
||||
SETTINGS = "SETTINGS",
|
||||
@ -30,7 +31,8 @@ export enum SettingsRouteName {
|
||||
export const settingsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/settings",
|
||||
component: () => import(/* webpackChunkName: "Settings" */ "@/views/Settings.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Settings" */ "@/views/Settings.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
redirect: { name: SettingsRouteName.ACCOUNT_SETTINGS },
|
||||
@ -45,24 +47,30 @@ export const settingsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "account/general",
|
||||
name: SettingsRouteName.ACCOUNT_SETTINGS_GENERAL,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "AccountSettings" */ "@/views/Settings/AccountSettings.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "AccountSettings" */ "@/views/Settings/AccountSettings.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "preferences",
|
||||
name: SettingsRouteName.PREFERENCES,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "Preferences" */ "@/views/Settings/Preferences.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Preferences" */ "@/views/Settings/Preferences.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "notifications",
|
||||
name: SettingsRouteName.NOTIFICATIONS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "Notifications" */ "@/views/Settings/Notifications.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Notifications" */ "@/views/Settings/Notifications.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
@ -75,61 +83,77 @@ export const settingsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "admin/dashboard",
|
||||
name: SettingsRouteName.ADMIN_DASHBOARD,
|
||||
component: () => import(/* webpackChunkName: "Dashboard" */ "@/views/Admin/Dashboard.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Dashboard" */ "@/views/Admin/Dashboard.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/settings",
|
||||
name: SettingsRouteName.ADMIN_SETTINGS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "AdminSettings" */ "@/views/Admin/Settings.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "AdminSettings" */ "@/views/Admin/Settings.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/users",
|
||||
name: SettingsRouteName.USERS,
|
||||
component: () => import(/* webpackChunkName: "Users" */ "@/views/Admin/Users.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Users" */ "@/views/Admin/Users.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/users/:id",
|
||||
name: SettingsRouteName.ADMIN_USER_PROFILE,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "AdminUserProfile" */ "@/views/Admin/AdminUserProfile.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "AdminUserProfile" */ "@/views/Admin/AdminUserProfile.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/profiles",
|
||||
name: SettingsRouteName.PROFILES,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "AdminProfiles" */ "@/views/Admin/Profiles.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "AdminProfiles" */ "@/views/Admin/Profiles.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/profiles/:id",
|
||||
name: SettingsRouteName.ADMIN_PROFILE,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "AdminProfile" */ "@/views/Admin/AdminProfile.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "AdminProfile" */ "@/views/Admin/AdminProfile.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/groups",
|
||||
name: SettingsRouteName.ADMIN_GROUPS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "GroupProfiles" */ "@/views/Admin/GroupProfiles.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "GroupProfiles" */ "@/views/Admin/GroupProfiles.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "admin/groups/:id",
|
||||
name: SettingsRouteName.ADMIN_GROUP_PROFILE,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "AdminGroupProfile" */ "@/views/Admin/AdminGroupProfile.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "AdminGroupProfile" */ "@/views/Admin/AdminGroupProfile.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
@ -137,21 +161,26 @@ export const settingsRoutes: RouteConfig[] = [
|
||||
path: "admin/relays",
|
||||
name: SettingsRouteName.RELAYS,
|
||||
redirect: { name: SettingsRouteName.RELAY_FOLLOWINGS },
|
||||
component: () => import(/* webpackChunkName: "Follows" */ "@/views/Admin/Follows.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Follows" */ "@/views/Admin/Follows.vue"),
|
||||
meta: { requiredAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: "followings",
|
||||
name: SettingsRouteName.RELAY_FOLLOWINGS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "Followings" */ "@/components/Admin/Followings.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Followings" */ "@/components/Admin/Followings.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "followers",
|
||||
name: SettingsRouteName.RELAY_FOLLOWERS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "Followers" */ "@/components/Admin/Followers.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Followers" */ "@/components/Admin/Followers.vue"
|
||||
),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
],
|
||||
@ -166,23 +195,30 @@ export const settingsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/moderation/reports/:filter?",
|
||||
name: SettingsRouteName.REPORTS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "ReportList" */ "@/views/Moderation/ReportList.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "ReportList" */ "@/views/Moderation/ReportList.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/moderation/report/:reportId",
|
||||
name: SettingsRouteName.REPORT,
|
||||
component: () => import(/* webpackChunkName: "Report" */ "@/views/Moderation/Report.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "Report" */ "@/views/Moderation/Report.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/moderation/logs",
|
||||
name: SettingsRouteName.REPORT_LOGS,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "ModerationLogs" */ "@/views/Moderation/Logs.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "ModerationLogs" */ "@/views/Moderation/Logs.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
@ -195,21 +231,27 @@ export const settingsRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/identity/create",
|
||||
name: SettingsRouteName.CREATE_IDENTITY,
|
||||
component: () =>
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "EditIdentity" */ "@/views/Account/children/EditIdentity.vue"
|
||||
),
|
||||
props: (route) => ({ identityName: route.params.identityName, isUpdate: false }),
|
||||
props: (route: Route): Record<string, unknown> => ({
|
||||
identityName: route.params.identityName,
|
||||
isUpdate: false,
|
||||
}),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/identity/update/:identityName?",
|
||||
name: SettingsRouteName.UPDATE_IDENTITY,
|
||||
component: () =>
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "EditIdentity" */ "@/views/Account/children/EditIdentity.vue"
|
||||
),
|
||||
props: (route) => ({ identityName: route.params.identityName, isUpdate: true }),
|
||||
props: (route: Route): Record<string, unknown> => ({
|
||||
identityName: route.params.identityName,
|
||||
isUpdate: true,
|
||||
}),
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
],
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { beforeRegisterGuard } from "@/router/guards/register-guard";
|
||||
import { RouteConfig } from "vue-router";
|
||||
import { Route, RouteConfig } from "vue-router";
|
||||
import { EsModuleComponent } from "vue/types/options";
|
||||
|
||||
export enum UserRouteName {
|
||||
REGISTER = "Register",
|
||||
@ -16,7 +17,10 @@ export const userRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/register/user",
|
||||
name: UserRouteName.REGISTER,
|
||||
component: () => import(/* webpackChunkName: "RegisterUser" */ "@/views/User/Register.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "RegisterUser" */ "@/views/User/Register.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
beforeEnter: beforeRegisterGuard,
|
||||
@ -24,10 +28,12 @@ export const userRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/register/profile",
|
||||
name: UserRouteName.REGISTER_PROFILE,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "RegisterProfile" */ "@/views/Account/Register.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "RegisterProfile" */ "@/views/Account/Register.vue"
|
||||
),
|
||||
// We can only pass string values through params, therefore
|
||||
props: (route) => ({
|
||||
props: (route: Route): Record<string, unknown> => ({
|
||||
email: route.params.email,
|
||||
userAlreadyActivated: route.params.userAlreadyActivated === "true",
|
||||
}),
|
||||
@ -36,46 +42,56 @@ export const userRoutes: RouteConfig[] = [
|
||||
{
|
||||
path: "/resend-instructions",
|
||||
name: UserRouteName.RESEND_CONFIRMATION,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "ResendConfirmation" */ "@/views/User/ResendConfirmation.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "ResendConfirmation" */ "@/views/User/ResendConfirmation.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/password-reset/send",
|
||||
name: UserRouteName.SEND_PASSWORD_RESET,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "SendPasswordReset" */ "@/views/User/SendPasswordReset.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "SendPasswordReset" */ "@/views/User/SendPasswordReset.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/password-reset/:token",
|
||||
name: UserRouteName.PASSWORD_RESET,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "PasswordReset" */ "@/views/User/PasswordReset.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "PasswordReset" */ "@/views/User/PasswordReset.vue"
|
||||
),
|
||||
meta: { requiresAuth: false },
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/validate/email/:token",
|
||||
name: UserRouteName.EMAIL_VALIDATE,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "EmailValidate" */ "@/views/User/EmailValidate.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(
|
||||
/* webpackChunkName: "EmailValidate" */ "@/views/User/EmailValidate.vue"
|
||||
),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/validate/:token",
|
||||
name: UserRouteName.VALIDATE,
|
||||
component: () => import(/* webpackChunkName: "Validate" */ "@/views/User/Validate.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Validate" */ "@/views/User/Validate.vue"),
|
||||
props: true,
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: UserRouteName.LOGIN,
|
||||
component: () => import(/* webpackChunkName: "Login" */ "@/views/User/Login.vue"),
|
||||
component: (): Promise<EsModuleComponent> =>
|
||||
import(/* webpackChunkName: "Login" */ "@/views/User/Login.vue"),
|
||||
props: true,
|
||||
meta: { requiredAuth: false },
|
||||
},
|
||||
|
@ -16,20 +16,25 @@ class AnonymousParticipationNotFoundError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch existing anonymous participations saved inside this browser
|
||||
*/
|
||||
function getLocalAnonymousParticipations(): Map<string, IAnonymousParticipation> {
|
||||
return jsonToMap(
|
||||
localStorage.getItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY) || mapToJson(new Map())
|
||||
);
|
||||
function jsonToMap(jsonStr: string): Map<string, IAnonymousParticipation> {
|
||||
return new Map(JSON.parse(jsonStr));
|
||||
}
|
||||
|
||||
function mapToJson(map: Map<any, any>): string {
|
||||
return JSON.stringify([...map]);
|
||||
}
|
||||
function jsonToMap(jsonStr: string): Map<string, IAnonymousParticipation> {
|
||||
return new Map(JSON.parse(jsonStr));
|
||||
|
||||
/**
|
||||
* Fetch existing anonymous participations saved inside this browser
|
||||
*/
|
||||
function getLocalAnonymousParticipations(): Map<
|
||||
string,
|
||||
IAnonymousParticipation
|
||||
> {
|
||||
return jsonToMap(
|
||||
localStorage.getItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY) ||
|
||||
mapToJson(new Map())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,6 +44,7 @@ function jsonToMap(jsonStr: string): Map<string, IAnonymousParticipation> {
|
||||
function purgeOldParticipations(
|
||||
participations: Map<string, IAnonymousParticipation>
|
||||
): Map<string, IAnonymousParticipation> {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [hashedUUID, { expiration }] of participations) {
|
||||
if (expiration < new Date()) {
|
||||
participations.delete(hashedUUID);
|
||||
@ -56,9 +62,14 @@ function insertLocalAnonymousParticipation(
|
||||
hashedUUID: string,
|
||||
participation: IAnonymousParticipation
|
||||
) {
|
||||
const participations = purgeOldParticipations(getLocalAnonymousParticipations());
|
||||
const participations = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
);
|
||||
participations.set(hashedUUID, participation);
|
||||
localStorage.setItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY, mapToJson(participations));
|
||||
localStorage.setItem(
|
||||
ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY,
|
||||
mapToJson(participations)
|
||||
);
|
||||
}
|
||||
|
||||
function buildExpiration(event: IEvent): Date {
|
||||
@ -67,59 +78,6 @@ function buildExpiration(event: IEvent): Date {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
async function addLocalUnconfirmedAnonymousParticipation(event: IEvent, cancellationToken: string) {
|
||||
/**
|
||||
* We hash the event UUID so that we can't know which events an anonymous user goes by looking up it's localstorage
|
||||
*/
|
||||
const hashedUUID = await digestMessage(event.uuid);
|
||||
|
||||
/**
|
||||
* We round expiration to first day of next 3 months so that it's difficult to find event from date
|
||||
*/
|
||||
const expiration = buildExpiration(event);
|
||||
insertLocalAnonymousParticipation(hashedUUID, {
|
||||
token: cancellationToken,
|
||||
expiration,
|
||||
confirmed: false,
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmLocalAnonymousParticipation(uuid: string) {
|
||||
const participations = purgeOldParticipations(getLocalAnonymousParticipations());
|
||||
const hashedUUID = await digestMessage(uuid);
|
||||
const participation = participations.get(hashedUUID);
|
||||
if (participation) {
|
||||
participation.confirmed = true;
|
||||
participations.set(hashedUUID, participation);
|
||||
localStorage.setItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY, mapToJson(participations));
|
||||
}
|
||||
}
|
||||
|
||||
async function isParticipatingInThisEvent(eventUUID: string): Promise<boolean> {
|
||||
const participation = await getParticipation(eventUUID);
|
||||
return participation !== undefined && participation.confirmed;
|
||||
}
|
||||
|
||||
async function getParticipation(eventUUID: string): Promise<IAnonymousParticipation> {
|
||||
const hashedUUID = await digestMessage(eventUUID);
|
||||
const participation = purgeOldParticipations(getLocalAnonymousParticipations()).get(hashedUUID);
|
||||
if (participation) {
|
||||
return participation;
|
||||
}
|
||||
throw new AnonymousParticipationNotFoundError("Participation not found");
|
||||
}
|
||||
|
||||
async function getLeaveTokenForParticipation(eventUUID: string): Promise<string> {
|
||||
return (await getParticipation(eventUUID)).token;
|
||||
}
|
||||
|
||||
async function removeAnonymousParticipation(eventUUID: string): Promise<void> {
|
||||
const hashedUUID = await digestMessage(eventUUID);
|
||||
const participations = purgeOldParticipations(getLocalAnonymousParticipations());
|
||||
participations.delete(hashedUUID);
|
||||
localStorage.setItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY, mapToJson(participations));
|
||||
}
|
||||
|
||||
async function digestMessage(message: string): Promise<string> {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(message);
|
||||
@ -128,6 +86,80 @@ async function digestMessage(message: string): Promise<string> {
|
||||
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
async function addLocalUnconfirmedAnonymousParticipation(
|
||||
event: IEvent,
|
||||
cancellationToken: string
|
||||
): Promise<void> {
|
||||
/**
|
||||
* We hash the event UUID so that we can't know which events
|
||||
* an anonymous user goes by looking up it's localstorage
|
||||
*/
|
||||
const hashedUUID = await digestMessage(event.uuid);
|
||||
|
||||
/**
|
||||
* We round expiration to first day of next 3 months so that
|
||||
* it's difficult to find event from date
|
||||
*/
|
||||
const expiration = buildExpiration(event);
|
||||
insertLocalAnonymousParticipation(hashedUUID, {
|
||||
token: cancellationToken,
|
||||
expiration,
|
||||
confirmed: false,
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmLocalAnonymousParticipation(uuid: string): Promise<void> {
|
||||
const participations = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
);
|
||||
const hashedUUID = await digestMessage(uuid);
|
||||
const participation = participations.get(hashedUUID);
|
||||
if (participation) {
|
||||
participation.confirmed = true;
|
||||
participations.set(hashedUUID, participation);
|
||||
localStorage.setItem(
|
||||
ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY,
|
||||
mapToJson(participations)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getParticipation(
|
||||
eventUUID: string
|
||||
): Promise<IAnonymousParticipation> {
|
||||
const hashedUUID = await digestMessage(eventUUID);
|
||||
const participation = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
).get(hashedUUID);
|
||||
if (participation) {
|
||||
return participation;
|
||||
}
|
||||
throw new AnonymousParticipationNotFoundError("Participation not found");
|
||||
}
|
||||
|
||||
async function isParticipatingInThisEvent(eventUUID: string): Promise<boolean> {
|
||||
const participation = await getParticipation(eventUUID);
|
||||
return participation !== undefined && participation.confirmed;
|
||||
}
|
||||
|
||||
async function getLeaveTokenForParticipation(
|
||||
eventUUID: string
|
||||
): Promise<string> {
|
||||
return (await getParticipation(eventUUID)).token;
|
||||
}
|
||||
|
||||
async function removeAnonymousParticipation(eventUUID: string): Promise<void> {
|
||||
const hashedUUID = await digestMessage(eventUUID);
|
||||
const participations = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
);
|
||||
participations.delete(hashedUUID);
|
||||
localStorage.setItem(
|
||||
ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY,
|
||||
mapToJson(participations)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
addLocalUnconfirmedAnonymousParticipation,
|
||||
confirmLocalAnonymousParticipation,
|
||||
|
@ -1,12 +1,5 @@
|
||||
import { IMedia } from "@/types/media.model";
|
||||
|
||||
export enum ActorType {
|
||||
PERSON = "PERSON",
|
||||
APPLICATION = "APPLICATION",
|
||||
GROUP = "GROUP",
|
||||
ORGANISATION = "ORGANISATION",
|
||||
SERVICE = "SERVICE",
|
||||
}
|
||||
import type { IMedia } from "@/types/media.model";
|
||||
import { ActorType } from "../enums";
|
||||
|
||||
export interface IActor {
|
||||
id?: string;
|
||||
@ -59,7 +52,9 @@ export class Actor implements IActor {
|
||||
}
|
||||
|
||||
public displayName(): string {
|
||||
return this.name != null && this.name !== "" ? this.name : this.usernameWithDomain();
|
||||
return this.name != null && this.name !== ""
|
||||
? this.name
|
||||
: this.usernameWithDomain();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IActor } from "@/types/actor/actor.model";
|
||||
import type { IActor } from "@/types/actor/actor.model";
|
||||
|
||||
export interface IFollower {
|
||||
id?: string;
|
||||
|
@ -1,38 +1,15 @@
|
||||
import { Actor, ActorType, IActor } from "./actor.model";
|
||||
import { Paginate } from "../paginate";
|
||||
import { IResource } from "../resource";
|
||||
import { ITodoList } from "../todos";
|
||||
import { IEvent } from "../event.model";
|
||||
import { IDiscussion } from "../discussions";
|
||||
import { IPerson } from "./person.model";
|
||||
import { IPost } from "../post.model";
|
||||
import { IAddress, Address } from "../address.model";
|
||||
|
||||
export enum MemberRole {
|
||||
NOT_APPROVED = "NOT_APPROVED",
|
||||
INVITED = "INVITED",
|
||||
MEMBER = "MEMBER",
|
||||
MODERATOR = "MODERATOR",
|
||||
ADMINISTRATOR = "ADMINISTRATOR",
|
||||
CREATOR = "CREATOR",
|
||||
REJECTED = "REJECTED",
|
||||
}
|
||||
|
||||
export enum Openness {
|
||||
INVITE_ONLY = "INVITE_ONLY",
|
||||
MODERATED = "MODERATED",
|
||||
OPEN = "OPEN",
|
||||
}
|
||||
|
||||
export interface IMember {
|
||||
id?: string;
|
||||
role: MemberRole;
|
||||
parent: IGroup;
|
||||
actor: IActor;
|
||||
invitedBy?: IPerson;
|
||||
insertedAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
import type { IActor } from "./actor.model";
|
||||
import { Actor } from "./actor.model";
|
||||
import type { Paginate } from "../paginate";
|
||||
import type { IResource } from "../resource";
|
||||
import type { IEvent } from "../event.model";
|
||||
import type { IDiscussion } from "../discussions";
|
||||
import type { IPost } from "../post.model";
|
||||
import type { IAddress } from "../address.model";
|
||||
import { Address } from "../address.model";
|
||||
import { ActorType, Openness } from "../enums";
|
||||
import type { IMember } from "./member.model";
|
||||
import type { ITodoList } from "../todolist";
|
||||
|
||||
export interface IGroup extends IActor {
|
||||
members: Paginate<IMember>;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user