Add admin interface to manage instances subscriptions
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -26,7 +26,8 @@
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span class="title" ref="title">{{ actorDisplayName }}</span><br>
|
||||
<small class="has-text-grey">@{{ participant.actor.preferredUsername }}</small>
|
||||
<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>
|
||||
@@ -41,7 +42,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IActor, IPerson, Person } from '@/types/actor';
|
||||
import { Person } from '@/types/actor';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
|
||||
@Component
|
||||
|
||||
141
js/src/components/Admin/Followers.vue
Normal file
141
js/src/components/Admin/Followers.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-table
|
||||
v-show="relayFollowers.elements.length > 0"
|
||||
:data="relayFollowers.elements"
|
||||
:loading="$apollo.queries.relayFollowers.loading"
|
||||
ref="table"
|
||||
:checked-rows.sync="checkedRows"
|
||||
:is-row-checkable="(row) => row.id !== 3"
|
||||
detailed
|
||||
:show-detail-icon="false"
|
||||
paginated
|
||||
backend-pagination
|
||||
:total="relayFollowers.total"
|
||||
:per-page="perPage"
|
||||
@page-change="onPageChange"
|
||||
checkable
|
||||
checkbox-position="left">
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="actor.id" label="ID" width="40" numeric>
|
||||
{{ props.row.actor.id }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="actor.type" :label="$t('Type')" width="80">
|
||||
<b-icon icon="lan" v-if="isInstance(props.row.actor)" />
|
||||
<b-icon icon="account-circle" v-else />
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="approved" :label="$t('Status')" width="100" sortable centered>
|
||||
<span :class="`tag ${props.row.approved ? 'is-success' : 'is-danger' }`">
|
||||
{{ props.row.approved ? $t('Accepted') : $t('Pending') }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="actor.domain" :label="$t('Domain')" sortable>
|
||||
<template>
|
||||
<a @click="toggle(props.row)" v-if="isInstance(props.row.actor)">
|
||||
{{ props.row.actor.domain }}
|
||||
</a>
|
||||
<a @click="toggle(props.row)" v-else>
|
||||
{{ `${props.row.actor.preferredUsername}@${props.row.actor.domain}` }}
|
||||
</a>
|
||||
</template>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="actor.updatedAt" :label="$t('Date')" sortable>
|
||||
{{ props.row.updatedAt | formatDateTimeString }}
|
||||
</b-table-column>
|
||||
</template>
|
||||
|
||||
<template slot="detail" slot-scope="props">
|
||||
<article>
|
||||
<div class="content">
|
||||
<strong>{{ props.row.actor.domain }}</strong>
|
||||
<small>@{{ props.row.actor.preferredUsername }}</small>
|
||||
<small>31m</small>
|
||||
<br>
|
||||
<p v-html="props.row.actor.summary" />
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<template slot="bottom-left" v-if="checkedRows.length > 0">
|
||||
<div class="buttons">
|
||||
<b-button @click="acceptRelays" type="is-success" v-if="checkedRowsHaveAtLeastOneToApprove">
|
||||
{{ $tc('No instance to approve|Approve instance|Approve {number} instances', checkedRows.length, { number: checkedRows.length }) }}
|
||||
</b-button>
|
||||
<b-button @click="rejectRelays" type="is-danger">
|
||||
{{ $tc('No instance to reject|Reject instance|Reject {number} instances', checkedRows.length, { number: checkedRows.length }) }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
</b-table>
|
||||
<b-message type="is-danger" v-if="relayFollowers.elements.length === 0">
|
||||
{{ $t("No instance follows your instance yet.") }}
|
||||
</b-message>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Mixins } from 'vue-property-decorator';
|
||||
import { ACCEPT_RELAY, REJECT_RELAY, RELAY_FOLLOWERS } from '@/graphql/admin';
|
||||
import { Paginate } from '@/types/paginate';
|
||||
import { IFollower } from '@/types/actor/follower.model';
|
||||
import RelayMixin from '@/mixins/relay';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
relayFollowers: {
|
||||
query: RELAY_FOLLOWERS,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('Followers') as string,
|
||||
titleTemplate: '%s | Mobilizon',
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Followers extends Mixins(RelayMixin) {
|
||||
relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
|
||||
async acceptRelays() {
|
||||
await this.checkedRows.forEach((row: IFollower) => {
|
||||
this.acceptRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
|
||||
});
|
||||
}
|
||||
|
||||
async rejectRelays() {
|
||||
await this.checkedRows.forEach((row: IFollower) => {
|
||||
this.rejectRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
|
||||
});
|
||||
}
|
||||
|
||||
async acceptRelay(address: String) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: ACCEPT_RELAY,
|
||||
variables: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
await this.$apollo.queries.relayFollowers.refetch();
|
||||
this.checkedRows = [];
|
||||
}
|
||||
|
||||
async rejectRelay(address: String) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: REJECT_RELAY,
|
||||
variables: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
await this.$apollo.queries.relayFollowers.refetch();
|
||||
this.checkedRows = [];
|
||||
}
|
||||
|
||||
get checkedRowsHaveAtLeastOneToApprove(): boolean {
|
||||
return this.checkedRows.some(checkedRow => !checkedRow.approved);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
142
js/src/components/Admin/Followings.vue
Normal file
142
js/src/components/Admin/Followings.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div>
|
||||
<form @submit="followRelay">
|
||||
<b-field :label="$t('Add an instance')" custom-class="add-relay" horizontal>
|
||||
<b-field grouped expanded size="is-large">
|
||||
<p class="control">
|
||||
<b-input v-model="newRelayAddress" :placeholder="$t('Ex: test.mobilizon.org')" />
|
||||
</p>
|
||||
<p class="control">
|
||||
<b-button type="is-primary" native-type="submit">{{ $t('Add an instance') }}</b-button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
</form>
|
||||
<b-table
|
||||
v-show="relayFollowings.elements.length > 0"
|
||||
:data="relayFollowings.elements"
|
||||
:loading="$apollo.queries.relayFollowings.loading"
|
||||
ref="table"
|
||||
:checked-rows.sync="checkedRows"
|
||||
:is-row-checkable="(row) => row.id !== 3"
|
||||
detailed
|
||||
:show-detail-icon="false"
|
||||
paginated
|
||||
backend-pagination
|
||||
:total="relayFollowings.total"
|
||||
:per-page="perPage"
|
||||
@page-change="onPageChange"
|
||||
checkable
|
||||
checkbox-position="left">
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="targetActor.id" label="ID" width="40" numeric>
|
||||
{{ props.row.targetActor.id }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="targetActor.type" :label="$t('Type')" width="80">
|
||||
<b-icon icon="lan" v-if="isInstance(props.row.targetActor)" />
|
||||
<b-icon icon="account-circle" v-else />
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="approved" :label="$t('Status')" width="100" sortable centered>
|
||||
<span :class="`tag ${props.row.approved ? 'is-success' : 'is-danger' }`">
|
||||
{{ props.row.approved ? $t('Accepted') : $t('Pending') }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="targetActor.domain" :label="$t('Domain')" sortable>
|
||||
<template>
|
||||
<a @click="toggle(props.row)" v-if="isInstance(props.row.targetActor)">
|
||||
{{ props.row.targetActor.domain }}
|
||||
</a>
|
||||
<a @click="toggle(props.row)" v-else>
|
||||
{{ `${props.row.targetActor.preferredUsername}@${props.row.targetActor.domain}` }}
|
||||
</a>
|
||||
</template>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="targetActor.updatedAt" :label="$t('Date')" sortable>
|
||||
{{ props.row.updatedAt | formatDateTimeString }}
|
||||
</b-table-column>
|
||||
</template>
|
||||
|
||||
<template slot="detail" slot-scope="props">
|
||||
<article>
|
||||
<div class="content">
|
||||
<strong>{{ props.row.targetActor.domain }}</strong>
|
||||
<small>@{{ props.row.targetActor.preferredUsername }}</small>
|
||||
<small>31m</small>
|
||||
<br>
|
||||
<p v-html="props.row.targetActor.summary" />
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<template slot="bottom-left" v-if="checkedRows.length > 0">
|
||||
<b-button @click="removeRelays" type="is-danger">
|
||||
{{ $tc('No instance to remove|Remove instance|Remove {number} instances', checkedRows.length, { number: checkedRows.length }) }}
|
||||
</b-button>
|
||||
</template>
|
||||
</b-table>
|
||||
<b-message type="is-danger" v-if="relayFollowings.elements.length === 0">
|
||||
{{ $t("You don't follow any instances yet.") }}
|
||||
</b-message>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Mixins } from 'vue-property-decorator';
|
||||
import { ADD_RELAY, RELAY_FOLLOWINGS, REMOVE_RELAY } from '@/graphql/admin';
|
||||
import { IFollower } from '@/types/actor/follower.model';
|
||||
import { Paginate } from '@/types/paginate';
|
||||
import RelayMixin from '@/mixins/relay';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
relayFollowings: {
|
||||
query: RELAY_FOLLOWINGS,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('Followings') as string,
|
||||
titleTemplate: '%s | Mobilizon',
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Followings extends Mixins(RelayMixin) {
|
||||
|
||||
relayFollowings: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
newRelayAddress: String = '';
|
||||
|
||||
async followRelay(e) {
|
||||
e.preventDefault();
|
||||
await this.$apollo.mutate({
|
||||
mutation: ADD_RELAY,
|
||||
variables: {
|
||||
address: this.newRelayAddress,
|
||||
},
|
||||
// TODO: Handle cache update properly without refreshing
|
||||
});
|
||||
await this.$apollo.queries.relayFollowings.refetch();
|
||||
this.newRelayAddress = '';
|
||||
}
|
||||
|
||||
async removeRelays() {
|
||||
await this.checkedRows.forEach((row: IFollower) => {
|
||||
this.removeRelay(`${row.targetActor.preferredUsername}@${row.targetActor.domain}`);
|
||||
});
|
||||
}
|
||||
|
||||
async removeRelay(address: String) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: REMOVE_RELAY,
|
||||
variables: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
await this.$apollo.queries.relayFollowings.refetch();
|
||||
this.checkedRows = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -11,7 +11,8 @@
|
||||
<div class="content">
|
||||
<span class="first-line" v-if="!comment.deletedAt">
|
||||
<strong>{{ comment.actor.name }}</strong>
|
||||
<small>@{{ comment.actor.preferredUsername }}</small>
|
||||
<small v-if="comment.actor.domain">@{{ comment.actor.preferredUsername }}@{{ comment.actor.domain }}</small>
|
||||
<small v-else>@{{ comment.actor.preferredUsername }}</small>
|
||||
<a class="comment-link has-text-grey" :href="commentId">
|
||||
<small>{{ timeago(new Date(comment.updatedAt)) }}</small>
|
||||
</a>
|
||||
@@ -202,7 +203,7 @@ export default class Comment extends Vue {
|
||||
|
||||
timeago(dateTime): String {
|
||||
if (this.timeAgoInstance != null) {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
return this.timeAgoInstance.format(dateTime);
|
||||
}
|
||||
return '';
|
||||
@@ -213,7 +214,7 @@ export default class Comment extends Vue {
|
||||
}
|
||||
|
||||
get commentFromOrganizer(): boolean {
|
||||
return this.event.organizerActor !== undefined && this.comment.actor.id === this.event.organizerActor.id;
|
||||
return this.event.organizerActor !== undefined && this.comment.actor && this.comment.actor.id === this.event.organizerActor.id;
|
||||
}
|
||||
|
||||
get commentId(): String {
|
||||
@@ -230,6 +231,7 @@ export default class Comment extends Vue {
|
||||
title: this.$t('Report this comment'),
|
||||
comment: this.comment,
|
||||
onConfirm: this.reportComment,
|
||||
outsideDomain: this.comment.actor.domain,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -244,6 +246,7 @@ export default class Comment extends Vue {
|
||||
reportedId: this.comment.actor.id,
|
||||
commentsIds: [this.comment.id],
|
||||
content,
|
||||
forward,
|
||||
},
|
||||
});
|
||||
this.$buefy.notification.open({
|
||||
|
||||
@@ -221,7 +221,7 @@ export default class CommentTree extends Vue {
|
||||
data: { thread: replies },
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
const parentCommentIndex = oldComments.findIndex(oldComment => oldComment.id === comment.originComment.id);
|
||||
const parentComment = oldComments[parentCommentIndex];
|
||||
parentComment.replies = replies;
|
||||
|
||||
@@ -409,9 +409,9 @@ export default class EditorComponent extends Vue {
|
||||
}
|
||||
|
||||
replyToComment(comment: IComment) {
|
||||
console.log('called replyToComment', comment);
|
||||
const actorModel = new Actor(comment.actor);
|
||||
if (!this.editor) return;
|
||||
console.log(this.editor.commands);
|
||||
this.editor.commands.mention({ id: actorModel.id, label: actorModel.usernameWithDomain().substring(1) });
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export default class AddressAutoComplete extends Vue {
|
||||
addressData: IAddress[] = [];
|
||||
selected: IAddress = new Address();
|
||||
isFetching: boolean = false;
|
||||
queryText: string = this.value && (new Address(this.value)).fullName || '';
|
||||
queryText: string = (this.value && (new Address(this.value)).fullName) || '';
|
||||
addressModalActive: boolean = false;
|
||||
private gettingLocation: boolean = false;
|
||||
private location!: Position;
|
||||
@@ -164,6 +164,7 @@ export default class AddressAutoComplete extends Vue {
|
||||
|
||||
@Watch('value')
|
||||
updateEditing() {
|
||||
if (!(this.value && this.value.id)) return;
|
||||
this.selected = this.value;
|
||||
const address = new Address(this.selected);
|
||||
this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`;
|
||||
|
||||
@@ -26,11 +26,11 @@ A button to set your participation
|
||||
<div class="participation-button">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" v-if="participation && participation.role === ParticipantRole.PARTICIPANT">
|
||||
<button class="button is-success" type="button" slot="trigger">
|
||||
<b-icon icon="check"></b-icon>
|
||||
<b-icon icon="check" />
|
||||
<template>
|
||||
<span>{{ $t('I participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<!-- <b-dropdown-item :value="false" aria-role="listitem">-->
|
||||
@@ -45,11 +45,11 @@ A button to set your participation
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.NOT_APPROVED">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" class="dropdown-disabled">
|
||||
<button class="button is-success" type="button" slot="trigger">
|
||||
<b-icon icon="timer-sand-empty"></b-icon>
|
||||
<b-icon icon="timer-sand-empty" />
|
||||
<template>
|
||||
<span>{{ $t('I participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<!-- <b-dropdown-item :value="false" aria-role="listitem">-->
|
||||
@@ -73,7 +73,7 @@ A button to set your participation
|
||||
<template>
|
||||
<span>{{ $t('Participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<b-dropdown-item :value="true" aria-role="listitem" @click="joinEvent(currentActor)">
|
||||
@@ -84,12 +84,12 @@ A button to set your participation
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span>{{ $t('with {identity}', {identity: currentActor.preferredUsername }) }}</span>
|
||||
<span>{{ $t('as {identity}', {identity: currentActor.preferredUsername }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="joinModal">
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="joinModal" v-if="identities.length > 1">
|
||||
{{ $t('with another identity…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
@@ -99,14 +99,32 @@ A button to set your participation
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
import { IDENTITIES } from '@/graphql/actor';
|
||||
import { CURRENT_USER_CLIENT } from '@/graphql/user';
|
||||
import { ICurrentUser } from '@/types/current-user.model';
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
apollo: {
|
||||
currentUser: {
|
||||
query: CURRENT_USER_CLIENT,
|
||||
},
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
update: ({ identities }) => identities ? identities.map(identity => new Person(identity)) : [],
|
||||
skip() {
|
||||
return this.currentUser.isLoggedIn === false;
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class ParticipationButton extends Vue {
|
||||
@Prop({ required: true }) participation!: IParticipant;
|
||||
@Prop({ required: true }) currentActor!: IPerson;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
currentUser!: ICurrentUser;
|
||||
identities: IPerson[] = [];
|
||||
|
||||
joinEvent(actor: IPerson) {
|
||||
this.$emit('joinEvent', actor);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component({
|
||||
beforeDestroy() {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
this.parentContainer.removeLayer(this);
|
||||
},
|
||||
})
|
||||
|
||||
@@ -20,7 +20,14 @@
|
||||
</div>
|
||||
|
||||
<div class="content columns">
|
||||
<div class="column is-one-quarter-desktop">Reported by <img v-if="report.reporter.avatar" class="image" :src="report.reporter.avatar.url" /> @{{ report.reporter.preferredUsername }}</div>
|
||||
<div class="column is-one-quarter-desktop">
|
||||
<span v-if="report.reporter.type === ActorType.APPLICATION">
|
||||
{{ $t('Reported by someone on {domain}', { domain: report.reporter.domain}) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t('Reported by {reporter}', { reporter: report.reporter.preferredUsername}) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="column" v-if="report.content">{{ report.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,10 +36,13 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IReport } from '@/types/report.model';
|
||||
import { ActorType } from '@/types/actor';
|
||||
|
||||
@Component
|
||||
export default class ReportCard extends Vue {
|
||||
@Prop({ required: true }) report!: IReport;
|
||||
|
||||
ActorType = ActorType;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@@ -44,11 +44,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="outsideDomain">
|
||||
{{ $t('The content came from another server. Transfer an anonymous copy of the report?') }}
|
||||
</p>
|
||||
|
||||
<div class="control" v-if="outsideDomain">
|
||||
<p>{{ $t('The content came from another server. Transfer an anonymous copy of the report?') }}</p>
|
||||
<b-switch v-model="forward">{{ $t('Transfer to {outsideDomain}', { outsideDomain }) }}</b-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,3 +19,87 @@ export const DASHBOARD = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RELAY_FRAGMENT = gql`
|
||||
fragment relayFragment on Follower {
|
||||
actor {
|
||||
id,
|
||||
preferredUsername,
|
||||
name,
|
||||
domain,
|
||||
type,
|
||||
summary
|
||||
},
|
||||
targetActor {
|
||||
id,
|
||||
preferredUsername,
|
||||
name,
|
||||
domain,
|
||||
type,
|
||||
summary
|
||||
},
|
||||
approved,
|
||||
insertedAt,
|
||||
updatedAt
|
||||
}
|
||||
`;
|
||||
|
||||
export const RELAY_FOLLOWERS = gql`
|
||||
query relayFollowers($page: Int, $limit: Int) {
|
||||
relayFollowers(page: $page, limit: $limit) {
|
||||
elements {
|
||||
...relayFragment
|
||||
},
|
||||
total
|
||||
}
|
||||
}
|
||||
${RELAY_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const RELAY_FOLLOWINGS = gql`
|
||||
query relayFollowings($page: Int, $limit: Int) {
|
||||
relayFollowings(page: $page, limit: $limit) {
|
||||
elements {
|
||||
...relayFragment
|
||||
},
|
||||
total
|
||||
}
|
||||
}
|
||||
${RELAY_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const ADD_RELAY = gql`
|
||||
mutation addRelay($address: String!) {
|
||||
addRelay(address: $address) {
|
||||
...relayFragment
|
||||
}
|
||||
}
|
||||
${RELAY_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const REMOVE_RELAY = gql`
|
||||
mutation removeRelay($address: String!) {
|
||||
removeRelay(address: $address) {
|
||||
...relayFragment
|
||||
}
|
||||
}
|
||||
${RELAY_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const ACCEPT_RELAY = gql`
|
||||
mutation acceptRelay($address: String!) {
|
||||
acceptRelay(address: $address) {
|
||||
...relayFragment
|
||||
}
|
||||
}
|
||||
${RELAY_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const REJECT_RELAY = gql`
|
||||
mutation rejectRelay($address: String!) {
|
||||
rejectRelay(address: $address) {
|
||||
...relayFragment
|
||||
}
|
||||
}
|
||||
${RELAY_FRAGMENT}
|
||||
`;
|
||||
|
||||
@@ -13,6 +13,7 @@ export const COMMENT_FIELDS_FRAGMENT = gql`
|
||||
url
|
||||
},
|
||||
id,
|
||||
domain,
|
||||
preferredUsername,
|
||||
name
|
||||
},
|
||||
|
||||
@@ -10,7 +10,8 @@ const participantQuery = `
|
||||
url
|
||||
},
|
||||
name,
|
||||
id
|
||||
id,
|
||||
domain
|
||||
},
|
||||
event {
|
||||
id
|
||||
@@ -441,3 +442,21 @@ export const EVENT_PERSON_PARTICIPATION = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const EVENT_PERSON_PARTICIPATION_SUBSCRIPTION_CHANGED = gql`
|
||||
subscription ($actorId: ID!, $eventId: ID!) {
|
||||
eventPersonParticipationChanged(personId: $actorId) {
|
||||
id,
|
||||
participations(eventId: $eventId) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -18,7 +18,9 @@ export const REPORTS = gql`
|
||||
name,
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
},
|
||||
domain,
|
||||
type
|
||||
},
|
||||
event {
|
||||
id,
|
||||
@@ -52,7 +54,9 @@ const REPORT_FRAGMENT = gql`
|
||||
name,
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
},
|
||||
domain,
|
||||
type
|
||||
},
|
||||
event {
|
||||
id,
|
||||
@@ -111,9 +115,10 @@ export const CREATE_REPORT = gql`
|
||||
$reporterId: ID!,
|
||||
$reportedId: ID!,
|
||||
$content: String,
|
||||
$commentsIds: [ID]
|
||||
$commentsIds: [ID],
|
||||
$forward: Boolean
|
||||
) {
|
||||
createReport(eventId: $eventId, reporterId: $reporterId, reportedId: $reportedId, content: $content, commentsIds: $commentsIds) {
|
||||
createReport(eventId: $eventId, reporterId: $reporterId, reportedId: $reportedId, content: $content, commentsIds: $commentsIds, forward: $forward) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,6 @@
|
||||
"resend confirmation email": "Bestätigungsmail erneut senden",
|
||||
"respect of the fundamental freedoms": "Respekt für die fundamentalen Freiheiten",
|
||||
"with another identity…": "mit einer anderen Identität.…",
|
||||
"with {identity}": "mit {identity}",
|
||||
"{approved} / {total} seats": "{approved} / {total} Plätze",
|
||||
"{count} participants": "Noch keine Teilnehmer | Ein Teilnehmer | {count} Teilnehmer",
|
||||
"{count} requests waiting": "{count} Anfragen ausstehend",
|
||||
|
||||
@@ -333,11 +333,59 @@
|
||||
"resend confirmation email": "resend confirmation email",
|
||||
"respect of the fundamental freedoms": "respect of the fundamental freedoms",
|
||||
"with another identity…": "with another identity…",
|
||||
"with {identity}": "with {identity}",
|
||||
"as {identity}": "as {identity}",
|
||||
"{approved} / {total} seats": "{approved} / {total} seats",
|
||||
"{count} participants": "No participants yet | One participant | {count} participants",
|
||||
"{count} requests waiting": "{count} requests waiting",
|
||||
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.",
|
||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
|
||||
"© The OpenStreetMap Contributors": "© The OpenStreetMap Contributors"
|
||||
"© The OpenStreetMap Contributors": "© The OpenStreetMap Contributors",
|
||||
"Reply": "Reply",
|
||||
"Accepted": "Accepted",
|
||||
"Pending": "Pending",
|
||||
"No instance to remove|Remove instance|Remove {number} instances": "No instances to remove|Remove instance|Remove {number} instances",
|
||||
"Dashboard": "Dashboard",
|
||||
"Reports": "Reports",
|
||||
"Mark as resolved": "Mark as resolved",
|
||||
"Reopen": "Reopen",
|
||||
"Close": "Close",
|
||||
"Reported identity": "Reported identity",
|
||||
"Reported by": "Reported by",
|
||||
"Reported": "Reported",
|
||||
"Updated": "Updated",
|
||||
"Open": "Open",
|
||||
"Closed": "Closed",
|
||||
"Resolved": "Resolved",
|
||||
"Unknown": "Unknown",
|
||||
"No comment": "No comment",
|
||||
"Notes": "Notes",
|
||||
"New note": "New note",
|
||||
"Add a note": "Add a note",
|
||||
"Deleting event": "Deleting event",
|
||||
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.": "Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.",
|
||||
"Delete Event": "Delete Event",
|
||||
"Type": "Type",
|
||||
"Domain": "Domain",
|
||||
"Date": "Date",
|
||||
"No instance to approve|Approve instance|Approve {number} instances": "No instance to approve|Approve instance|Approve {number} instances",
|
||||
"No instance to reject|Reject instance|Reject {number} instances": "No instance to reject|Reject instance|Reject {number} instances",
|
||||
"No instance follows your instance yet.": "No instance follows your instance yet.",
|
||||
"Followers": "Followers",
|
||||
"Add an instance": "Add an instance",
|
||||
"Ex: test.mobilizon.org": "Ex: test.mobilizon.org",
|
||||
"You don't follow any instances yet.": "You don't follow any instances yet.",
|
||||
"Followings": "Followings",
|
||||
"Instances": "Instances",
|
||||
"Reported by {reporter}": "Reported by {reporter}",
|
||||
"No open reports yet": "No open reports yet",
|
||||
"No resolved reports yet": "No resolved reports yet",
|
||||
"No closed reports yet": "No closed reports yet",
|
||||
"Reported by someone on {domain}": "Reported by someone on {domain}",
|
||||
"Your participation has been rejected": "Your participation has been rejected",
|
||||
"Your participation status has been changed": "Your participation status has been changed",
|
||||
"Unknown actor": "Unknown actor",
|
||||
"Deleting comment": "Deleting comment",
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Are you sure you want to <b>delete</b> this comment? This action cannot be undone.",
|
||||
"Delete Comment": "Delete Comment",
|
||||
"Comment deleted": "Comment deleted"
|
||||
}
|
||||
@@ -286,7 +286,7 @@
|
||||
"Update my event": "Éditer mon événement",
|
||||
"User accounts and every other data is currently deleted every 48 hours, so you may want to register again.": "Les comptes utilisateurs et toutes les autres données sont actuellement supprimées toutes les 48 heures, donc vous voulez peut-être vous inscrire à nouveau.",
|
||||
"Username": "Pseudo",
|
||||
"Users": "Utilisateurs",
|
||||
"Users": "Utilisateur⋅ice⋅s",
|
||||
"View a reply": "Aucune réponse | Voir une réponse | Voir {totalReplies} réponses",
|
||||
"View event page": "Voir la page de l'événement",
|
||||
"View everything": "Voir tout",
|
||||
@@ -337,11 +337,57 @@
|
||||
"resend confirmation email": "réenvoyer l'email de confirmation",
|
||||
"respect of the fundamental freedoms": "le respect des libertés fondamentales",
|
||||
"with another identity…": "avec une autre identité…",
|
||||
"with {identity}": "avec {identity}",
|
||||
"as {identity}": "en tant que {identity}",
|
||||
"{approved} / {total} seats": "{approved} / {total} places",
|
||||
"{count} participants": "Aucun⋅e participant⋅e | Un⋅e participant⋅e | {count} participant⋅e⋅s",
|
||||
"{count} requests waiting": "Une demande en attente|{count} demandes en attente",
|
||||
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.",
|
||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
|
||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
|
||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
|
||||
"Reply": "Répondre",
|
||||
"Accepted": "Accepté",
|
||||
"Pending": "En attente",
|
||||
"No instance to remove|Remove instance|Remove {number} instances": "Pas d'instances à supprimer|Supprimer une instance|Supprimer {number} instances",
|
||||
"Mark as resolved": "Marquer comme résolu",
|
||||
"Reopen": "Réouvrir",
|
||||
"Close": "Fermé",
|
||||
"Reported identity": "Identité signalée",
|
||||
"Reported by": "Signalée par",
|
||||
"Reported": "Signalée",
|
||||
"Updated": "Mis à jour",
|
||||
"Open": "Ouvert",
|
||||
"Closed": "Fermé",
|
||||
"Resolved": "Résolu",
|
||||
"Unknown": "Inconnu",
|
||||
"No comment": "Pas de commentaire",
|
||||
"Notes": "Notes",
|
||||
"New note": "Nouvelle note",
|
||||
"Add a note": "Ajouter une note",
|
||||
"Deleting event": "Suppression de l'événement",
|
||||
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> cet événement ? Cette action n'est pas réversible. Vous voulez peut-être engager la conversation avec le créateur de l'événement ou bien éditer son événement à la place.",
|
||||
"Delete Event": "Supprimer l'événement",
|
||||
"Type": "Type",
|
||||
"Domain": "Domaine",
|
||||
"Date": "Date",
|
||||
"No instance to approve|Approve instance|Approve {number} instances": "Aucune instance à approuver|Approuver une instance|Approuver {number} instances",
|
||||
"No instance to reject|Reject instance|Reject {number} instances": "Aucune instance à rejetter|Rejetter une instance|Rejetter {number} instances",
|
||||
"No instance follows your instance yet.": "Aucune instance ne suit votre instance pour le moment.",
|
||||
"Followers": "Abonnés",
|
||||
"Add an instance": "Ajouter une instance",
|
||||
"Ex: test.mobilizon.org": "Ex: test.mobilizon.org",
|
||||
"You don't follow any instances yet.": "Vous ne suivez aucune instance pour le moment.",
|
||||
"Followings": "Abonnements",
|
||||
"Instances": "Instances",
|
||||
"Reported by {reporter}": "Signalé par {reporter}",
|
||||
"No open reports yet": "Aucun signalement ouvert pour le moment",
|
||||
"No resolved reports yet": "Aucun signalement résolu pour le moment",
|
||||
"No closed reports yet": "Aucun signalement fermé pour le moment",
|
||||
"Reported by someone on {domain}": "Signalé par quelqu'un depuis {domain}",
|
||||
"Your participation has been rejected": "Votre participation a été rejettée",
|
||||
"Your participation status has been changed": "Le statut de votre participation a été mis à jour",
|
||||
"Unknown actor": "Acteur inconnu",
|
||||
"Deleting comment": "Suppression du commentaire en cours",
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> ce commentaire? Cette action ne peut pas être annulée.",
|
||||
"Delete Comment": "Supprimer le commentaire",
|
||||
"Comment deleted": "Commentaire supprimé"
|
||||
}
|
||||
@@ -321,7 +321,6 @@
|
||||
"resend confirmation email": "bevestigingsemail opnieuw versturen",
|
||||
"respect of the fundamental freedoms": "respect voor de fundamentele vrijheden",
|
||||
"with another identity…": "met een andere identiteit…",
|
||||
"with {identity}": "met {identity}",
|
||||
"{approved} / {total} seats": "{approved} / {total} plaatsen",
|
||||
"{count} participants": "Nog geen deelnemers | Eén deelnemer | {count} deelnemers",
|
||||
"{count} requests waiting": "{count} aanvragen in afwachting",
|
||||
|
||||
@@ -368,7 +368,6 @@
|
||||
"resend confirmation email": "tornar enviar lo messatge de confirmacion",
|
||||
"respect of the fundamental freedoms": "lo respet de las libertats fondamentalas",
|
||||
"with another identity…": "amb una autra identitat…",
|
||||
"with {identity}": "amb {identity}",
|
||||
"{actor}'s avatar": "Avatar de {actor}",
|
||||
"{approved} / {total} seats": "{approved} / {total} plaças",
|
||||
"{count} participants": "Cap de participacion pel moment|Un participant|{count} participants",
|
||||
|
||||
@@ -324,7 +324,6 @@
|
||||
"resend confirmation email": "skicka bekräftelsemail igen",
|
||||
"respect of the fundamental freedoms": "respektera våra grundläggande friheter",
|
||||
"with another identity…": "med en annan identitet…",
|
||||
"with {identity}": "med {identity}",
|
||||
"{approved} / {total} seats": "{approved} / {total} platser",
|
||||
"{count} participants": "Inga deltagande ännu|En deltagande|{count} deltagande",
|
||||
"{count} requests waiting": "{count} förfrågningar väntar",
|
||||
|
||||
44
js/src/mixins/relay.ts
Normal file
44
js/src/mixins/relay.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { Refs } from '@/shims-vue';
|
||||
import { ActorType, IActor } from '@/types/actor';
|
||||
import { IFollower } from '@/types/actor/follower.model';
|
||||
|
||||
@Component
|
||||
export default class RelayMixin extends Vue {
|
||||
$refs!: Refs<{
|
||||
table: any,
|
||||
}>;
|
||||
|
||||
checkedRows: IFollower[] = [];
|
||||
page: number = 1;
|
||||
perPage: number = 2;
|
||||
|
||||
toggle(row) {
|
||||
this.$refs.table.toggleDetails(row);
|
||||
}
|
||||
|
||||
async onPageChange(page: number) {
|
||||
this.page = page;
|
||||
await this.$apollo.queries.relayFollowings.fetchMore({
|
||||
variables: {
|
||||
page: this.page,
|
||||
limit: this.perPage,
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult) return previousResult;
|
||||
const newFollowings = fetchMoreResult.relayFollowings.elements;
|
||||
return {
|
||||
relayFollowings: {
|
||||
__typename: previousResult.relayFollowings.__typename,
|
||||
total: previousResult.relayFollowings.total,
|
||||
elements: [...previousResult.relayFollowings.elements, ...newFollowings],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
isInstance(actor: IActor): boolean {
|
||||
return actor.type === ActorType.APPLICATION && actor.preferredUsername === 'relay';
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import Vue from 'vue';
|
||||
import { ColorModifiers } from 'buefy/types/helpers';
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$notifier: {
|
||||
success: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
info: (message: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -17,21 +19,23 @@ export class Notifier {
|
||||
}
|
||||
|
||||
success(message: string) {
|
||||
this.vue.prototype.$buefy.notification.open({
|
||||
message,
|
||||
duration: 5000,
|
||||
position: 'is-bottom-right',
|
||||
type: 'is-success',
|
||||
hasIcon: true,
|
||||
});
|
||||
this.notification(message, 'is-success');
|
||||
}
|
||||
|
||||
error(message: string) {
|
||||
this.notification(message, 'is-danger');
|
||||
}
|
||||
|
||||
info(message: string) {
|
||||
this.notification(message, 'is-info');
|
||||
}
|
||||
|
||||
private notification(message: string, type: ColorModifiers) {
|
||||
this.vue.prototype.$buefy.notification.open({
|
||||
message,
|
||||
duration: 5000,
|
||||
position: 'is-bottom-right',
|
||||
type: 'is-danger',
|
||||
type,
|
||||
hasIcon: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { RouteConfig } from 'vue-router';
|
||||
import Dashboard from '@/views/Admin/Dashboard.vue';
|
||||
import Follows from '@/views/Admin/Follows.vue';
|
||||
import Followings from '@/components/Admin/Followings.vue';
|
||||
import Followers from '@/components/Admin/Followers.vue';
|
||||
|
||||
export enum AdminRouteName {
|
||||
DASHBOARD = 'Dashboard',
|
||||
RELAYS = 'Relays',
|
||||
RELAY_FOLLOWINGS = 'Followings',
|
||||
RELAY_FOLLOWERS = 'Followers',
|
||||
}
|
||||
|
||||
export const adminRoutes: RouteConfig[] = [
|
||||
@@ -13,4 +19,24 @@ export const adminRoutes: RouteConfig[] = [
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/admin/relays',
|
||||
name: AdminRouteName.RELAYS,
|
||||
redirect: { name: AdminRouteName.RELAY_FOLLOWINGS },
|
||||
component: Follows,
|
||||
children: [
|
||||
{
|
||||
path: 'followings',
|
||||
name: AdminRouteName.RELAY_FOLLOWINGS,
|
||||
component: Followings,
|
||||
},
|
||||
{
|
||||
path: 'followers',
|
||||
name: AdminRouteName.RELAY_FOLLOWERS,
|
||||
component: Followers,
|
||||
},
|
||||
],
|
||||
props: true,
|
||||
meta: { requiredAuth: true },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { IPicture } from '@/types/picture.model';
|
||||
|
||||
export enum ActorType {
|
||||
PERSON = 'PERSON',
|
||||
APPLICATION = 'APPLICATION',
|
||||
GROUP = 'GROUP',
|
||||
ORGANISATION = 'ORGANISATION',
|
||||
SERVICE = 'SERVICE',
|
||||
}
|
||||
|
||||
export interface IActor {
|
||||
id?: number;
|
||||
url: string;
|
||||
@@ -10,6 +18,7 @@ export interface IActor {
|
||||
suspended: boolean;
|
||||
avatar: IPicture | null;
|
||||
banner: IPicture | null;
|
||||
type: ActorType;
|
||||
}
|
||||
|
||||
export class Actor implements IActor {
|
||||
@@ -22,6 +31,7 @@ export class Actor implements IActor {
|
||||
summary: string = '';
|
||||
suspended: boolean = false;
|
||||
url: string = '';
|
||||
type: ActorType = ActorType.PERSON;
|
||||
|
||||
constructor (hash: IActor | {} = {}) {
|
||||
Object.assign(this, hash);
|
||||
|
||||
8
js/src/types/actor/follower.model.ts
Normal file
8
js/src/types/actor/follower.model.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { IActor } from '@/types/actor/actor.model';
|
||||
|
||||
export interface IFollower {
|
||||
id?: string;
|
||||
actor: IActor;
|
||||
targetActor: IActor;
|
||||
approved: boolean;
|
||||
}
|
||||
@@ -242,7 +242,7 @@ export class EventModel implements IEvent {
|
||||
|
||||
this.onlineAddress = hash.onlineAddress;
|
||||
this.phoneAddress = hash.phoneAddress;
|
||||
this.physicalAddress = new Address(hash.physicalAddress);
|
||||
this.physicalAddress = hash.physicalAddress ? new Address(hash.physicalAddress) : undefined;
|
||||
this.participantStats = hash.participantStats;
|
||||
|
||||
this.tags = hash.tags;
|
||||
|
||||
4
js/src/types/paginate.ts
Normal file
4
js/src/types/paginate.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Paginate<T> {
|
||||
elements: T[];
|
||||
total: number;
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="list is-hoverable">
|
||||
<a class="list-item" v-for="identity in identities" :class="{ 'is-active': identity.id === currentIdentity.id }" @click="changeCurrentIdentity(identity)">
|
||||
<div class="media">
|
||||
<img class="media-left image" v-if="identity.avatar" :src="identity.avatar.url" alt="" />
|
||||
<img class="media-left image is-48x48" v-if="identity.avatar" :src="identity.avatar.url" alt="" />
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<div class="media-content">
|
||||
<h3>@{{ identity.preferredUsername }}</h3>
|
||||
@@ -17,7 +17,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<slot name="footer"></slot>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="identity-picker">
|
||||
<span v-if="inline" class="inline">
|
||||
<img class="image" v-if="currentIdentity.avatar" :src="currentIdentity.avatar.url" :alt="currentIdentity.avatar.alt"/> {{ currentIdentity.name || `@${currentIdentity.preferredUsername}` }}
|
||||
<img class="image" v-if="currentIdentity.avatar" :src="currentIdentity.avatar.url" :alt="currentIdentity.avatar.alt"/>
|
||||
<b-icon v-else size="is-small" icon="account-circle" />
|
||||
{{ currentIdentity.name || `@${currentIdentity.preferredUsername}` }}
|
||||
<b-button type="is-text" @click="isComponentModalActive = true">
|
||||
{{ $t('Change') }}
|
||||
</b-button>
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
</article>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<router-link :to="{ name: RouteName.RELAYS }">
|
||||
<article class="tile is-child box">
|
||||
<p class="subtitle">{{ $t('Instances') }}</p>
|
||||
</article>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<article class="tile is-child box">
|
||||
@@ -67,6 +74,12 @@ import { RouteName } from '@/router';
|
||||
query: DASHBOARD,
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('Administration') as string,
|
||||
titleTemplate: '%s | Mobilizon',
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Dashboard extends Vue {
|
||||
dashboard!: IDashboard;
|
||||
|
||||
57
js/src/views/Admin/Follows.vue
Normal file
57
js/src/views/Admin/Follows.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 class="title">{{ $t('Instances') }}</h1>
|
||||
<div class="tabs is-boxed">
|
||||
<ul>
|
||||
<router-link tag="li" active-class="is-active" :to="{name: RouteName.RELAY_FOLLOWINGS}" exact>
|
||||
<a>
|
||||
<b-icon icon="inbox-arrow-down"></b-icon>
|
||||
<span>{{ $t('Followings') }} <b-tag rounded> {{ relayFollowings.total }} </b-tag> </span>
|
||||
</a>
|
||||
</router-link>
|
||||
<router-link tag="li" active-class="is-active" :to="{name: RouteName.RELAY_FOLLOWERS}" exact>
|
||||
<a>
|
||||
<b-icon icon="inbox-arrow-up"></b-icon>
|
||||
<span>{{ $t('Followers') }} <b-tag rounded> {{ relayFollowers.total }} </b-tag> </span>
|
||||
</a>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { RouteName } from '@/router';
|
||||
import { RELAY_FOLLOWERS, RELAY_FOLLOWINGS } from '@/graphql/admin';
|
||||
import { Paginate } from '@/types/paginate';
|
||||
import { IFollower } from '@/types/actor/follower.model';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
relayFollowings: {
|
||||
query: RELAY_FOLLOWINGS,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
relayFollowers: {
|
||||
query: RELAY_FOLLOWERS,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Follows extends Vue {
|
||||
RouteName = RouteName;
|
||||
activeTab: number = 0;
|
||||
|
||||
relayFollowings: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.tab-item {
|
||||
form {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -29,7 +29,7 @@
|
||||
<address-auto-complete v-model="event.physicalAddress" />
|
||||
|
||||
<b-field :label="$t('Organizer')">
|
||||
<identity-picker-wrapper v-model="event.organizerActor"></identity-picker-wrapper>
|
||||
<identity-picker-wrapper v-model="event.organizerActor" />
|
||||
</b-field>
|
||||
|
||||
<div class="field">
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
<div class="box" v-if="limitedPlaces">
|
||||
<b-field :label="$t('Number of places')">
|
||||
<b-numberinput controls-position="compact" min="0" v-model="event.options.maximumAttendeeCapacity"></b-numberinput>
|
||||
<b-numberinput controls-position="compact" min="0" v-model="event.options.maximumAttendeeCapacity" />
|
||||
</b-field>
|
||||
<!--
|
||||
<b-field>
|
||||
@@ -145,21 +145,21 @@
|
||||
name="status"
|
||||
type="is-warning"
|
||||
:native-value="EventStatus.TENTATIVE">
|
||||
<b-icon icon="calendar-question"></b-icon>
|
||||
<b-icon icon="calendar-question" />
|
||||
{{ $t('Tentative: Will be confirmed later') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-success"
|
||||
:native-value="EventStatus.CONFIRMED">
|
||||
<b-icon icon="calendar-check"></b-icon>
|
||||
<b-icon icon="calendar-check" />
|
||||
{{ $t('Confirmed: Will happen') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-danger"
|
||||
:native-value="EventStatus.CANCELLED">
|
||||
<b-icon icon="calendar-remove"></b-icon>
|
||||
<b-icon icon="calendar-remove" />
|
||||
{{ $t("Cancelled: Won't happen") }}
|
||||
</b-radio-button>
|
||||
</b-field>
|
||||
@@ -191,7 +191,7 @@
|
||||
</div>
|
||||
</form>
|
||||
</b-modal>
|
||||
<span ref="bottomObserver"></span>
|
||||
<span ref="bottomObserver" />
|
||||
<nav role="navigation" aria-label="main navigation" class="navbar" :class="{'is-fixed-bottom': showFixedNavbar }">
|
||||
<div class="container">
|
||||
<div class="navbar-menu">
|
||||
@@ -395,6 +395,11 @@ export default class EditEvent extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('currentActor')
|
||||
setCurrentActor() {
|
||||
this.event.organizerActor = this.currentActor;
|
||||
}
|
||||
|
||||
private validateForm() {
|
||||
const form = this.$refs.form as HTMLFormElement;
|
||||
if (form.checkValidity()) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
<template>
|
||||
<div class="container">
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<b-loading :active.sync="$apollo.loading" />
|
||||
<transition appear name="fade" mode="out-in">
|
||||
<div>
|
||||
<div class="header-picture" v-if="event.picture" :style="`background-image: url('${event.picture.url}')`" />
|
||||
@@ -9,7 +11,7 @@
|
||||
<div class="title-and-participate-button">
|
||||
<div class="title-wrapper">
|
||||
<div class="date-component">
|
||||
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
|
||||
<date-calendar-icon :date="event.beginsOn" />
|
||||
</div>
|
||||
<div class="title-and-informations">
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
@@ -49,7 +51,7 @@
|
||||
<template>
|
||||
<span>{{ $t('Event already passed')}}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,6 +67,9 @@
|
||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
|
||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
|
||||
</span>
|
||||
<span v-if="!event.local">
|
||||
<b-tag type="is-primary">{{ event.organizerActor.domain }}</b-tag>
|
||||
</span>
|
||||
<router-link
|
||||
v-if="event.tags && event.tags.length > 0"
|
||||
v-for="tag in event.tags"
|
||||
@@ -136,7 +141,7 @@
|
||||
</b-modal>
|
||||
</div>
|
||||
<span class="online-address" v-if="event.onlineAddress && urlToHostname(event.onlineAddress)">
|
||||
<b-icon icon="link"></b-icon>
|
||||
<b-icon icon="link" />
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -250,8 +255,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { EVENT_PERSON_PARTICIPATION, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
import {
|
||||
EVENT_PERSON_PARTICIPATION,
|
||||
EVENT_PERSON_PARTICIPATION_SUBSCRIPTION_CHANGED,
|
||||
FETCH_EVENT,
|
||||
JOIN_EVENT,
|
||||
LEAVE_EVENT,
|
||||
} from '@/graphql/event';
|
||||
import { Component, Prop, Watch } from 'vue-property-decorator';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { EventModel, EventStatus, EventVisibility, IEvent, IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
@@ -311,6 +322,15 @@ import 'intersection-observer';
|
||||
actorId: this.currentActor.id,
|
||||
};
|
||||
},
|
||||
subscribeToMore: {
|
||||
document: EVENT_PERSON_PARTICIPATION_SUBSCRIPTION_CHANGED,
|
||||
variables() {
|
||||
return {
|
||||
eventId: this.event.id,
|
||||
actorId: this.currentActor.id,
|
||||
};
|
||||
},
|
||||
},
|
||||
update: (data) => {
|
||||
if (data && data.person) return data.person.participations;
|
||||
return [];
|
||||
@@ -341,6 +361,7 @@ export default class Event extends EventMixin {
|
||||
currentActor!: IPerson;
|
||||
identity: IPerson = new Person();
|
||||
participations: IParticipant[] = [];
|
||||
oldParticipationRole!: String;
|
||||
showMap: boolean = false;
|
||||
isReportModalActive: boolean = false;
|
||||
isJoinModalActive: boolean = false;
|
||||
@@ -432,14 +453,10 @@ export default class Event extends EventMixin {
|
||||
reporterId: this.currentActor.id,
|
||||
reportedId: this.event.organizerActor.id,
|
||||
content,
|
||||
forward,
|
||||
},
|
||||
});
|
||||
this.$buefy.notification.open({
|
||||
message: this.$t('Event {eventTitle} reported', { eventTitle }) as string,
|
||||
type: 'is-success',
|
||||
position: 'is-bottom-right',
|
||||
duration: 5000,
|
||||
});
|
||||
this.$notifier.success(this.$t('Event {eventTitle} reported', { eventTitle }) as string);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -493,12 +510,11 @@ export default class Event extends EventMixin {
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
this.$buefy.notification.open({
|
||||
message: (data.joinEvent.role === ParticipantRole.NOT_APPROVED ? this.$t('Your participation has been requested') : this.$t('Your participation has been confirmed')) as string,
|
||||
type: 'is-success',
|
||||
position: 'is-bottom-right',
|
||||
duration: 5000,
|
||||
});
|
||||
if (data.joinEvent.role === ParticipantRole.NOT_APPROVED) {
|
||||
this.participationRequestedMessage();
|
||||
} else {
|
||||
this.participationConfirmedMessage();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -563,18 +579,55 @@ export default class Event extends EventMixin {
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
this.$buefy.notification.open({
|
||||
message: this.$t('You have cancelled your participation') as string,
|
||||
type: 'is-success',
|
||||
position: 'is-bottom-right',
|
||||
duration: 5000,
|
||||
});
|
||||
this.participationCancelledMessage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('participations')
|
||||
watchParticipations() {
|
||||
if (this.participations.length > 0) {
|
||||
if (this.oldParticipationRole
|
||||
&& this.participations[0].role !== ParticipantRole.NOT_APPROVED
|
||||
&& this.oldParticipationRole !== this.participations[0].role) {
|
||||
switch (this.participations[0].role) {
|
||||
case ParticipantRole.PARTICIPANT:
|
||||
this.participationConfirmedMessage();
|
||||
break;
|
||||
case ParticipantRole.REJECTED:
|
||||
this.participationRejectedMessage();
|
||||
break;
|
||||
default:
|
||||
this.participationChangedMessage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.oldParticipationRole = this.participations[0].role;
|
||||
}
|
||||
}
|
||||
|
||||
private participationConfirmedMessage() {
|
||||
this.$notifier.success(this.$t('Your participation has been confirmed') as string);
|
||||
}
|
||||
|
||||
private participationRequestedMessage() {
|
||||
this.$notifier.success(this.$t('Your participation has been requested') as string);
|
||||
}
|
||||
|
||||
private participationRejectedMessage() {
|
||||
this.$notifier.error(this.$t('Your participation has been rejected') as string);
|
||||
}
|
||||
|
||||
private participationChangedMessage() {
|
||||
this.$notifier.info(this.$t('Your participation status has been changed') as string);
|
||||
}
|
||||
|
||||
private participationCancelledMessage() {
|
||||
this.$notifier.success(this.$t('You have cancelled your participation') as string);
|
||||
}
|
||||
|
||||
async downloadIcsEvent() {
|
||||
const data = await (await fetch(`${GRAPHQL_API_ENDPOINT}/events/${this.uuid}/export/ics`)).text();
|
||||
const blob = new Blob([data], { type: 'text/calendar' });
|
||||
|
||||
@@ -64,7 +64,7 @@ export default class ReportList extends Vue {
|
||||
RouteName = RouteName;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.container li {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('Reported by') }}</td>
|
||||
<td>
|
||||
<td v-if="report.reporter.type === ActorType.APPLICATION">
|
||||
{{ report.reporter.domain }}
|
||||
</td>
|
||||
<td v-else>
|
||||
<router-link :to="{ name: RouteName.PROFILE, params: { name: report.reporter.preferredUsername } }">
|
||||
<img v-if="report.reporter.avatar" class="image" :src="report.reporter.avatar.url" /> @{{ report.reporter.preferredUsername }}
|
||||
</router-link>
|
||||
@@ -55,15 +58,15 @@
|
||||
<td>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: report.event.uuid }}">{{ report.event.title }}</router-link>
|
||||
<span class="is-pulled-right">
|
||||
<b-button
|
||||
tag="router-link"
|
||||
type="is-primary"
|
||||
:to="{ name: RouteName.EDIT_EVENT, params: {eventId: report.event.uuid } }"
|
||||
icon-left="pencil"
|
||||
size="is-small">{{ $t('Edit') }}</b-button>
|
||||
<!-- <b-button-->
|
||||
<!-- tag="router-link"-->
|
||||
<!-- type="is-primary"-->
|
||||
<!-- :to="{ name: RouteName.EDIT_EVENT, params: {eventId: report.event.uuid } }"-->
|
||||
<!-- icon-left="pencil"-->
|
||||
<!-- size="is-small">{{ $t('Edit') }}</b-button>-->
|
||||
<b-button
|
||||
type="is-danger"
|
||||
@click="confirmDelete()"
|
||||
@click="confirmEventDelete()"
|
||||
icon-left="delete"
|
||||
size="is-small">{{ $t('Delete') }}</b-button>
|
||||
</span>
|
||||
@@ -74,24 +77,24 @@
|
||||
</div>
|
||||
|
||||
<div class="box report-content">
|
||||
<p v-if="report.content" v-html="nl2br(report.content)"></p>
|
||||
<p v-if="report.content" v-html="nl2br(report.content)" />
|
||||
<p v-else>{{ $t('No comment') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="box" v-if="report.event && report.comments.length === 0">
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: report.event.uuid }}">
|
||||
<h3 class="title">{{ report.event.title }}</h3>
|
||||
<p v-html="report.event.description"></p>
|
||||
<p v-html="report.event.description" />
|
||||
</router-link>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
type="is-primary"
|
||||
:to="{ name: RouteName.EDIT_EVENT, params: {eventId: report.event.uuid } }"
|
||||
icon-left="pencil"
|
||||
size="is-small">{{ $t('Edit') }}</b-button>
|
||||
<!-- <b-button-->
|
||||
<!-- tag="router-link"-->
|
||||
<!-- type="is-primary"-->
|
||||
<!-- :to="{ name: RouteName.EDIT_EVENT, params: {eventId: report.event.uuid } }"-->
|
||||
<!-- icon-left="pencil"-->
|
||||
<!-- size="is-small">{{ $t('Edit') }}</b-button>-->
|
||||
<b-button
|
||||
type="is-danger"
|
||||
@click="confirmDelete()"
|
||||
@click="confirmEventDelete()"
|
||||
icon-left="delete"
|
||||
size="is-small">{{ $t('Delete') }}</b-button>
|
||||
</div>
|
||||
@@ -101,17 +104,25 @@
|
||||
<div class="box" v-if="comment">
|
||||
<article class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="comment.actor.avatar">
|
||||
<figure class="image is-48x48" v-if="comment.actor && comment.actor.avatar">
|
||||
<img :src="comment.actor.avatar.url" alt="Image">
|
||||
</figure>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<strong>{{ comment.actor.name }}</strong> <small>@{{ comment.actor.preferredUsername }}</small>
|
||||
<span v-if="comment.actor">
|
||||
<strong>{{ comment.actor.name }}</strong> <small>@{{ comment.actor.preferredUsername }}</small>
|
||||
</span>
|
||||
<span v-else>{{ $t('Unknown actor') }}</span>
|
||||
<br>
|
||||
<p v-html="comment.text"></p>
|
||||
<p v-html="comment.text" />
|
||||
</div>
|
||||
<b-button
|
||||
type="is-danger"
|
||||
@click="confirmCommentDelete(comment)"
|
||||
icon-left="delete"
|
||||
size="is-small">{{ $t('Delete') }}</b-button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@@ -131,21 +142,23 @@
|
||||
<b-field :label="$t('New note')">
|
||||
<b-input type="textarea" v-model="noteContent"></b-input>
|
||||
</b-field>
|
||||
<b-button type="submit" @click="addNote">{{ $t('Ajouter une note') }}</b-button>
|
||||
<b-button type="submit" @click="addNote">{{ $t('Add a note') }}</b-button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { CREATE_REPORT_NOTE, REPORT, REPORTS, UPDATE_REPORT } from '@/graphql/report';
|
||||
import { CREATE_REPORT_NOTE, REPORT, UPDATE_REPORT } from '@/graphql/report';
|
||||
import { IReport, IReportNote, ReportStatusEnum } from '@/types/report.model';
|
||||
import { RouteName } from '@/router';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { IPerson, ActorType } from '@/types/actor';
|
||||
import { DELETE_EVENT } from '@/graphql/event';
|
||||
import { uniq } from 'lodash';
|
||||
import { nl2br } from '@/utils/html';
|
||||
import { DELETE_COMMENT } from '@/graphql/comment';
|
||||
import { IComment } from '@/types/comment.model';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@@ -164,6 +177,12 @@ import { nl2br } from '@/utils/html';
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('Report') as string,
|
||||
titleTemplate: '%s | Mobilizon',
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Report extends Vue {
|
||||
@Prop({ required: true }) reportId!: number;
|
||||
@@ -173,6 +192,7 @@ export default class Report extends Vue {
|
||||
|
||||
ReportStatusEnum = ReportStatusEnum;
|
||||
RouteName = RouteName;
|
||||
ActorType = ActorType;
|
||||
nl2br = nl2br;
|
||||
|
||||
noteContent: string = '';
|
||||
@@ -210,7 +230,7 @@ export default class Report extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
confirmDelete() {
|
||||
confirmEventDelete() {
|
||||
this.$buefy.dialog.confirm({
|
||||
title: this.$t('Deleting event') as string,
|
||||
message: this.$t('Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.') as string,
|
||||
@@ -221,6 +241,17 @@ export default class Report extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
confirmCommentDelete(comment: IComment) {
|
||||
this.$buefy.dialog.confirm({
|
||||
title: this.$t('Deleting comment') as string,
|
||||
message: this.$t('Are you sure you want to <b>delete</b> this comment? This action cannot be undone.') as string,
|
||||
confirmText: this.$t('Delete Comment') as string,
|
||||
type: 'is-danger',
|
||||
hasIcon: true,
|
||||
onConfirm: () => this.deleteComment(comment),
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEvent() {
|
||||
if (!this.report.event || !this.report.event.id) return;
|
||||
const eventTitle = this.report.event.title;
|
||||
@@ -245,6 +276,21 @@ export default class Report extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteComment(comment: IComment) {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: DELETE_COMMENT,
|
||||
variables: {
|
||||
commentId: comment.id,
|
||||
actorId: this.currentActor.id,
|
||||
},
|
||||
});
|
||||
this.$notifier.success(this.$t('Comment deleted') as string);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateReport(status: ReportStatusEnum) {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
@@ -289,10 +335,6 @@ export default class Report extends Vue {
|
||||
<style lang="scss" scoped>
|
||||
@import "@/variables.scss";
|
||||
|
||||
.container li {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
tbody td img.image, .note img.image {
|
||||
display: inline;
|
||||
height: 1.5em;
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
<b-field>
|
||||
<b-radio-button v-model="filterReports"
|
||||
:native-value="ReportStatusEnum.OPEN">
|
||||
Ouvert
|
||||
{{ $t('Open') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="filterReports"
|
||||
:native-value="ReportStatusEnum.RESOLVED">
|
||||
Résolus
|
||||
{{ $t('Resolved') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="filterReports"
|
||||
:native-value="ReportStatusEnum.CLOSED">
|
||||
Fermés
|
||||
{{ $t('Closed') }}
|
||||
</b-radio-button>
|
||||
</b-field>
|
||||
<ul v-if="reports.length > 0">
|
||||
@@ -28,9 +28,9 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else>
|
||||
<b-message v-if="filterReports === ReportStatusEnum.OPEN" type="is-info">No open reports yet</b-message>
|
||||
<b-message v-if="filterReports === ReportStatusEnum.RESOLVED" type="is-info">No resolved reports yet</b-message>
|
||||
<b-message v-if="filterReports === ReportStatusEnum.CLOSED" type="is-info">No closed reports yet</b-message>
|
||||
<b-message v-if="filterReports === ReportStatusEnum.OPEN" type="is-info">{{ $t('No open reports yet') }}</b-message>
|
||||
<b-message v-if="filterReports === ReportStatusEnum.RESOLVED" type="is-info">{{ $t('No resolved reports yet') }}</b-message>
|
||||
<b-message v-if="filterReports === ReportStatusEnum.CLOSED" type="is-info">{{ $t('No closed reports yet') }}</b-message>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -80,8 +80,3 @@ export default class ReportList extends Vue {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.container li {
|
||||
margin: 10px auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,10 @@
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { ApolloLink, Observable } from 'apollo-link';
|
||||
import { ApolloLink, Observable, split } from 'apollo-link';
|
||||
import { defaultDataIdFromObject, InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||
import { onError } from 'apollo-link-error';
|
||||
import { createLink } from 'apollo-absinthe-upload-link';
|
||||
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from './api/_entrypoint';
|
||||
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH, MOBILIZON_INSTANCE_HOST } from './api/_entrypoint';
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
import { buildCurrentUserResolver } from '@/apollo/user';
|
||||
import { isServerError } from '@/types/apollo';
|
||||
@@ -13,13 +13,18 @@ import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from '@/constants';
|
||||
import { logout, saveTokenData } from '@/utils/auth';
|
||||
import { SnackbarProgrammatic as Snackbar } from 'buefy';
|
||||
import { defaultError, errors, IError, refreshSuggestion } from '@/utils/errors';
|
||||
import { Socket as PhoenixSocket } from 'phoenix';
|
||||
import * as AbsintheSocket from '@absinthe/socket';
|
||||
import { createAbsintheSocketLink } from '@absinthe/socket-apollo-link';
|
||||
import { getMainDefinition } from 'apollo-utilities';
|
||||
|
||||
// Install the vue plugin
|
||||
Vue.use(VueApollo);
|
||||
|
||||
// Http endpoint
|
||||
// Endpoints
|
||||
const httpServer = GRAPHQL_API_ENDPOINT || 'http://localhost:4000';
|
||||
const httpEndpoint = GRAPHQL_API_FULL_PATH || `${httpServer}/api`;
|
||||
const wsEndpoint = `ws${httpServer.substring(httpServer.indexOf(':'))}/graphql_socket`;
|
||||
|
||||
const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData: {
|
||||
@@ -60,10 +65,6 @@ const authMiddleware = new ApolloLink((operation, forward) => {
|
||||
return null;
|
||||
});
|
||||
|
||||
const uploadLink = createLink({
|
||||
uri: httpEndpoint,
|
||||
});
|
||||
|
||||
let refreshingTokenPromise: Promise<boolean> | undefined;
|
||||
let alreadyRefreshedToken = false;
|
||||
const errorLink = onError(({ graphQLErrors, networkError, forward, operation }) => {
|
||||
@@ -126,9 +127,38 @@ const computeErrorMessage = (message) => {
|
||||
return error.suggestRefresh === false ? error.value : `${error.value}<br>${refreshSuggestion}`;
|
||||
};
|
||||
|
||||
const link = authMiddleware
|
||||
const uploadLink = createLink({
|
||||
uri: httpEndpoint,
|
||||
});
|
||||
|
||||
const phoenixSocket = new PhoenixSocket(wsEndpoint, {
|
||||
params: () => {
|
||||
const token = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
if (token) {
|
||||
return { token };
|
||||
}
|
||||
return {};
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
const absintheSocket = AbsintheSocket.create(phoenixSocket);
|
||||
const wsLink = createAbsintheSocketLink(absintheSocket);
|
||||
|
||||
const link = split(
|
||||
// split based on operation type
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return definition.kind === 'OperationDefinition' &&
|
||||
definition.operation === 'subscription';
|
||||
},
|
||||
wsLink,
|
||||
uploadLink,
|
||||
);
|
||||
|
||||
const fullLink = authMiddleware
|
||||
.concat(errorLink)
|
||||
.concat(uploadLink);
|
||||
.concat(link);
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
fragmentMatcher,
|
||||
@@ -143,7 +173,7 @@ const cache = new InMemoryCache({
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
cache,
|
||||
link,
|
||||
link: fullLink,
|
||||
connectToDevTools: true,
|
||||
resolvers: buildCurrentUserResolver(cache),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user