Introduce group basic federation, event new page and notifications
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
109
js/src/components/Account/ActorAutoComplete.vue
Normal file
109
js/src/components/Account/ActorAutoComplete.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<b-autocomplete
|
||||
:data="baseData"
|
||||
:placeholder="$t('Actor')"
|
||||
v-model="name"
|
||||
field="preferredUsername"
|
||||
:loading="$apollo.loading"
|
||||
check-infinite-scroll
|
||||
@typing="getAsyncData"
|
||||
@select="handleSelect"
|
||||
@infinite-scroll="getAsyncData"
|
||||
>
|
||||
<template slot-scope="props">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<img width="32" :src="props.option.avatar.url" v-if="props.option.avatar" alt="" />
|
||||
<b-icon v-else icon="account-circle" />
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span v-if="props.option.name">
|
||||
{{ props.option.name }}
|
||||
<br />
|
||||
<small>{{ `@${props.option.preferredUsername}` }}</small>
|
||||
<small v-if="props.option.domain">{{ `@${props.option.domain}` }}</small>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ `@${props.option.preferredUsername}` }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<span class="has-text-grey" v-show="page > totalPages">
|
||||
Thats it! No more movies found.
|
||||
</span>
|
||||
</template>
|
||||
</b-autocomplete>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Model, Vue, Watch } from "vue-property-decorator";
|
||||
import { debounce } from "lodash";
|
||||
import { IPerson } from "@/types/actor";
|
||||
import { SEARCH_PERSONS } from "@/graphql/search";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
|
||||
const SEARCH_PERSON_LIMIT = 10;
|
||||
|
||||
@Component
|
||||
export default class ActorAutoComplete extends Vue {
|
||||
@Model("change", { type: Object }) readonly defaultSelected!: IPerson | null;
|
||||
|
||||
baseData: IPerson[] = [];
|
||||
|
||||
selected: IPerson | null = this.defaultSelected;
|
||||
|
||||
name: string = this.defaultSelected ? this.defaultSelected.preferredUsername : "";
|
||||
|
||||
page = 1;
|
||||
|
||||
totalPages = 1;
|
||||
|
||||
mounted() {
|
||||
this.selected = this.defaultSelected;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
getAsyncData: debounce(this.doGetAsyncData, 500),
|
||||
};
|
||||
}
|
||||
|
||||
@Watch("defaultSelected")
|
||||
updateDefaultSelected(defaultSelected: IPerson) {
|
||||
console.log("update defaultSelected", defaultSelected);
|
||||
this.selected = defaultSelected;
|
||||
this.name = defaultSelected.preferredUsername;
|
||||
}
|
||||
|
||||
handleSelect(selected: IPerson) {
|
||||
this.selected = selected;
|
||||
this.$emit("change", selected);
|
||||
}
|
||||
|
||||
async doGetAsyncData(name: string) {
|
||||
this.baseData = [];
|
||||
if (this.name !== name) {
|
||||
this.name = name;
|
||||
this.page = 1;
|
||||
}
|
||||
if (!name.length) {
|
||||
this.page = 1;
|
||||
this.totalPages = 1;
|
||||
return;
|
||||
}
|
||||
const {
|
||||
data: { searchPersons },
|
||||
} = await this.$apollo.query<{ searchPersons: Paginate<IPerson> }>({
|
||||
query: SEARCH_PERSONS,
|
||||
variables: {
|
||||
searchText: this.name,
|
||||
page: this.page,
|
||||
limit: SEARCH_PERSON_LIMIT,
|
||||
},
|
||||
});
|
||||
this.totalPages = Math.ceil(searchPersons.total / SEARCH_PERSON_LIMIT);
|
||||
this.baseData.push(...searchPersons.elements);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
152
js/src/components/Account/ActorCard.vue
Normal file
152
js/src/components/Account/ActorCard.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="clickable">
|
||||
<div class="media" style="align-items: top;">
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32" v-if="actor.avatar">
|
||||
<img class="is-rounded" :src="actor.avatar.url" alt="" />
|
||||
</figure>
|
||||
<b-icon v-else size="is-medium" icon="account-circle" />
|
||||
</div>
|
||||
|
||||
<div class="media-content">
|
||||
<p>
|
||||
{{ actor.name || `@${usernameWithDomain(actor)}` }}
|
||||
</p>
|
||||
<p class="has-text-grey" v-if="actor.name">@{{ usernameWithDomain(actor) }}</p>
|
||||
<p v-if="full">{{ actor.summary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { IActor, usernameWithDomain } from "../../types/actor";
|
||||
|
||||
@Component
|
||||
export default class ActorCard extends Vue {
|
||||
@Prop({ required: true, type: Object }) actor!: IActor;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) full!: boolean;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: true }) popover!: boolean;
|
||||
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.tooltip {
|
||||
display: block !important;
|
||||
z-index: 10000;
|
||||
|
||||
.tooltip-inner {
|
||||
background: black;
|
||||
color: white;
|
||||
border-radius: 16px;
|
||||
padding: 5px 10px 4px;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
border-color: black;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="bottom"] {
|
||||
margin-top: 5px;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="right"] {
|
||||
margin-left: 5px;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
border-left-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
left: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="left"] {
|
||||
margin-right: 5px;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
right: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.popover {
|
||||
$color: #f9f9f9;
|
||||
|
||||
.popover-inner {
|
||||
background: $color;
|
||||
color: black;
|
||||
padding: 24px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 5px 30px rgba(black, 0.1);
|
||||
}
|
||||
|
||||
.popover-arrow {
|
||||
border-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-hidden="true"] {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, visibility 0.15s;
|
||||
}
|
||||
|
||||
&[aria-hidden="false"] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +0,0 @@
|
||||
<docs>
|
||||
A simple link to an actor, local or remote link
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ActorLink :actor="localActor">
|
||||
<template>
|
||||
<span>{{ localActor.preferredUsername }}</span>
|
||||
</template>
|
||||
</ActorLink>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
localActor: {
|
||||
domain: null,
|
||||
preferredUsername: 'localActor'
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ActorLink :actor="remoteActor">
|
||||
<template>
|
||||
<span>{{ remoteActor.preferredUsername }}</span>
|
||||
</template>
|
||||
</ActorLink>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
remoteActor: {
|
||||
domain: 'mobilizon.org',
|
||||
url: 'https://mobilizon.org/@Framasoft',
|
||||
preferredUsername: 'Framasoft'
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<span v-if="actor.domain === null"
|
||||
:to="{name: 'Profile', params: { name: actor.preferredUsername } }"
|
||||
>
|
||||
<!-- @slot What to put inside the link -->
|
||||
<slot></slot>
|
||||
</span>
|
||||
<a v-else :href="actor.url">
|
||||
<!-- @slot What to put inside the link -->
|
||||
<slot></slot>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IActor } from '@/types/actor';
|
||||
|
||||
@Component
|
||||
export default class ActorLink extends Vue {
|
||||
/**
|
||||
* The actor you want to make a link to
|
||||
*/
|
||||
@Prop({ required: true }) actor!: IActor;
|
||||
}
|
||||
</script>
|
||||
@@ -1,18 +1,19 @@
|
||||
<template>
|
||||
<section>
|
||||
<h1 class="title">
|
||||
{{ $t('My identities') }}
|
||||
{{ $t("My identities") }}
|
||||
</h1>
|
||||
|
||||
<ul class="identities">
|
||||
<li v-for="identity in identities" :key="identity.id">
|
||||
<router-link
|
||||
:to="{ name: 'UpdateIdentity', params: { identityName: identity.preferredUsername } }"
|
||||
class="media identity" v-bind:class="{ 'is-current-identity': isCurrentIdentity(identity) }"
|
||||
class="media identity"
|
||||
v-bind:class="{ 'is-current-identity': isCurrentIdentity(identity) }"
|
||||
>
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="identity.avatar">
|
||||
<img class="is-rounded" :src="identity.avatar.url">
|
||||
<img class="is-rounded" :src="identity.avatar.url" />
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
@@ -23,24 +24,24 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<router-link :to="{ name: 'CreateIdentity' }" class="button create-identity is-primary" >
|
||||
{{ $t('Create a new identity') }}
|
||||
<router-link :to="{ name: 'CreateIdentity' }" class="button create-identity is-primary">
|
||||
{{ $t("Create a new identity") }}
|
||||
</router-link>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IDENTITIES } from '@/graphql/actor';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IDENTITIES } from "../../graphql/actor";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
|
||||
update (result) {
|
||||
return result.identities.map(i => new Person(i));
|
||||
update(result) {
|
||||
return result.identities.map((i: IPerson) => new Person(i));
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -49,6 +50,7 @@ export default class Identities extends Vue {
|
||||
@Prop({ type: String }) currentIdentityName!: string;
|
||||
|
||||
identities: Person[] = [];
|
||||
|
||||
errors: string[] = [];
|
||||
|
||||
isCurrentIdentity(identity: IPerson) {
|
||||
@@ -58,25 +60,25 @@ export default class Identities extends Vue {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.identities {
|
||||
border-right: 1px solid grey;
|
||||
.identities {
|
||||
border-right: 1px solid grey;
|
||||
|
||||
padding: 15px 0;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.media.identity {
|
||||
align-items: center;
|
||||
font-size: 1.3rem;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 15px;
|
||||
color: #000;
|
||||
|
||||
&.is-current-identity {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.media.identity {
|
||||
align-items: center;
|
||||
font-size: 1.3rem;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 15px;
|
||||
color: #000;
|
||||
|
||||
&.is-current-identity {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,32 +25,58 @@
|
||||
</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>
|
||||
<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>
|
||||
<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 { Person } from '@/types/actor';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { Person } from "../../types/actor";
|
||||
import { IParticipant, ParticipantRole } from "../../types/event.model";
|
||||
|
||||
@Component
|
||||
export default class ParticipantCard extends Vue {
|
||||
@Prop({ required: true }) participant!: IParticipant;
|
||||
@Prop({ type: Function }) accept;
|
||||
@Prop({ type: Function }) reject;
|
||||
@Prop({ type: Function }) exclude;
|
||||
|
||||
@Prop({ type: Function }) accept!: Function;
|
||||
|
||||
@Prop({ type: Function }) reject!: Function;
|
||||
|
||||
@Prop({ type: Function }) exclude!: Function;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
@@ -58,13 +84,12 @@ export default class ParticipantCard extends Vue {
|
||||
const actor = new Person(this.participant.actor);
|
||||
return actor.displayName();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../variables.scss";
|
||||
.card-footer-item {
|
||||
height: $control-height;
|
||||
}
|
||||
@import "../../variables.scss";
|
||||
.card-footer-item {
|
||||
height: $control-height;
|
||||
}
|
||||
</style>
|
||||
|
||||
33
js/src/components/Account/PopoverActorCard.vue
Normal file
33
js/src/components/Account/PopoverActorCard.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<v-popover offset="16" trigger="hover" :class="{ inline }" class="clickable">
|
||||
<slot></slot>
|
||||
<template slot="popover" class="popover">
|
||||
<actor-card :full="true" :actor="actor" :popover="false" />
|
||||
</template>
|
||||
</v-popover>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { IActor } from "../../types/actor";
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ActorCard,
|
||||
},
|
||||
})
|
||||
export default class PopoverActorCard extends Vue {
|
||||
@Prop({ required: true, type: Object }) actor!: IActor;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) inline!: boolean;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user