Introduce group basic federation, event new page and notifications

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-02-18 08:57:00 +01:00
parent 300ef8f245
commit 4144e9ffd0
416 changed files with 32220 additions and 16750 deletions

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>