Fix actor auto-complete

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2022-01-18 12:51:37 +01:00
parent fbe5a8d0c4
commit c57d192abe
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
5 changed files with 27 additions and 135 deletions

View file

@ -1,118 +0,0 @@
<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 #default="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/debounce";
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(): void {
this.selected = this.defaultSelected;
}
data(): Record<string, unknown> {
return {
getAsyncData: debounce(this.doGetAsyncData, 500),
};
}
@Watch("defaultSelected")
updateDefaultSelected(defaultSelected: IPerson): void {
console.log("update defaultSelected", defaultSelected);
this.selected = defaultSelected;
this.name = defaultSelected.preferredUsername;
}
handleSelect(selected: IPerson): void {
this.selected = selected;
this.$emit("change", selected);
}
async doGetAsyncData(name: string): Promise<void> {
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

@ -9,20 +9,22 @@
<div class="actor-name"> <div class="actor-name">
<p> <p>
{{ actor.name || `@${usernameWithDomain(actor)}` }} {{ displayName(actor) }}
</p> </p>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Vue, Prop } from "vue-property-decorator";
import { IActor, usernameWithDomain } from "../../types/actor"; import { displayName, IActor, usernameWithDomain } from "../../types/actor";
@Component @Component
export default class ActorInline extends Vue { export default class ActorInline extends Vue {
@Prop({ required: true, type: Object }) actor!: IActor; @Prop({ required: true, type: Object }) actor!: IActor;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
displayName = displayName;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -7,6 +7,8 @@ import apolloProvider from "@/vue-apollo";
import { IPerson } from "@/types/actor"; import { IPerson } from "@/types/actor";
import pDebounce from "p-debounce"; import pDebounce from "p-debounce";
import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types";
import { MentionOptions } from "@tiptap/extension-mention";
import { Editor } from "@tiptap/core";
const client = const client =
apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>; apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>;
@ -24,13 +26,21 @@ const fetchItems = async (query: string): Promise<IPerson[]> => {
const debouncedFetchItems = pDebounce(fetchItems, 200); const debouncedFetchItems = pDebounce(fetchItems, 200);
const mentionOptions: Partial<any> = { const mentionOptions: MentionOptions = {
HTMLAttributes: { HTMLAttributes: {
class: "mention", class: "mention",
dir: "ltr", dir: "ltr",
}, },
renderLabel({ options, node }) {
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
},
suggestion: { suggestion: {
items: async (query: string): Promise<IPerson[]> => { items: async ({
query,
}: {
query: string;
editor: Editor;
}): Promise<IPerson[]> => {
if (query === "") { if (query === "") {
return []; return [];
} }
@ -70,8 +80,12 @@ const mentionOptions: Partial<any> = {
return component.ref?.onKeyDown(props); return component.ref?.onKeyDown(props);
}, },
onExit() { onExit() {
popup[0].destroy(); if (popup && popup[0]) {
component.destroy(); popup[0].destroy();
}
if (component) {
component.destroy();
}
}, },
}; };
}, },

View file

@ -7,7 +7,7 @@
:key="index" :key="index"
@click="selectItem(index)" @click="selectItem(index)"
> >
<actor-card :actor="item" /> <actor-inline :actor="item" />
</button> </button>
</div> </div>
</template> </template>
@ -16,11 +16,11 @@
import { Vue, Component, Prop, Watch } from "vue-property-decorator"; import { Vue, Component, Prop, Watch } from "vue-property-decorator";
import { displayName, usernameWithDomain } from "@/types/actor/actor.model"; import { displayName, usernameWithDomain } from "@/types/actor/actor.model";
import { IPerson } from "@/types/actor"; import { IPerson } from "@/types/actor";
import ActorCard from "../../components/Account/ActorCard.vue"; import ActorInline from "../../components/Account/ActorInline.vue";
@Component({ @Component({
components: { components: {
ActorCard, ActorInline,
}, },
}) })
export default class MentionList extends Vue { export default class MentionList extends Vue {

View file

@ -7,9 +7,7 @@
<b-field :label="$t('Title')"> <b-field :label="$t('Title')">
<b-input v-model="title" /> <b-input v-model="title" />
</b-field> </b-field>
<b-field :label="$t('Assigned to')"> <b-field :label="$t('Assigned to')"> </b-field>
<actor-auto-complete v-model="assignedTo" />
</b-field>
<b-field :label="$t('Due on')"> <b-field :label="$t('Due on')">
<b-datepicker v-model="dueDate" :first-day-of-week="firstDayOfWeek" /> <b-datepicker v-model="dueDate" :first-day-of-week="firstDayOfWeek" />
</b-field> </b-field>
@ -17,19 +15,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Prop, Vue } from "vue-property-decorator";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { DebouncedFunc } from "lodash"; import { DebouncedFunc } from "lodash";
import { SnackbarProgrammatic as Snackbar } from "buefy"; import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ITodo } from "../../types/todos"; import { ITodo } from "../../types/todos";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { UPDATE_TODO } from "../../graphql/todos"; import { UPDATE_TODO } from "../../graphql/todos";
import ActorAutoComplete from "../Account/ActorAutoComplete.vue";
import { IPerson } from "../../types/actor"; import { IPerson } from "../../types/actor";
@Component({
components: { ActorAutoComplete },
})
export default class Todo extends Vue { export default class Todo extends Vue {
@Prop({ required: true, type: Object }) todo!: ITodo; @Prop({ required: true, type: Object }) todo!: ITodo;