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