diff --git a/js/src/components/Account/ActorAutoComplete.vue b/js/src/components/Account/ActorAutoComplete.vue deleted file mode 100644 index 50a77e1b4..000000000 --- a/js/src/components/Account/ActorAutoComplete.vue +++ /dev/null @@ -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> diff --git a/js/src/components/Account/ActorInline.vue b/js/src/components/Account/ActorInline.vue index 29fa40359..867bd1689 100644 --- a/js/src/components/Account/ActorInline.vue +++ b/js/src/components/Account/ActorInline.vue @@ -9,20 +9,22 @@ <div class="actor-name"> <p> - {{ actor.name || `@${usernameWithDomain(actor)}` }} + {{ displayName(actor) }} </p> </div> </div> </template> <script lang="ts"> import { Component, Vue, Prop } from "vue-property-decorator"; -import { IActor, usernameWithDomain } from "../../types/actor"; +import { displayName, IActor, usernameWithDomain } from "../../types/actor"; @Component export default class ActorInline extends Vue { @Prop({ required: true, type: Object }) actor!: IActor; usernameWithDomain = usernameWithDomain; + + displayName = displayName; } </script> <style lang="scss" scoped> diff --git a/js/src/components/Editor/Mention.ts b/js/src/components/Editor/Mention.ts index 05b9f6001..a3c2903e8 100644 --- a/js/src/components/Editor/Mention.ts +++ b/js/src/components/Editor/Mention.ts @@ -7,6 +7,8 @@ import apolloProvider from "@/vue-apollo"; import { IPerson } from "@/types/actor"; import pDebounce from "p-debounce"; import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; +import { MentionOptions } from "@tiptap/extension-mention"; +import { Editor } from "@tiptap/core"; const client = apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>; @@ -24,13 +26,21 @@ const fetchItems = async (query: string): Promise<IPerson[]> => { const debouncedFetchItems = pDebounce(fetchItems, 200); -const mentionOptions: Partial<any> = { +const mentionOptions: MentionOptions = { HTMLAttributes: { class: "mention", dir: "ltr", }, + renderLabel({ options, node }) { + return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`; + }, suggestion: { - items: async (query: string): Promise<IPerson[]> => { + items: async ({ + query, + }: { + query: string; + editor: Editor; + }): Promise<IPerson[]> => { if (query === "") { return []; } @@ -70,8 +80,12 @@ const mentionOptions: Partial<any> = { return component.ref?.onKeyDown(props); }, onExit() { - popup[0].destroy(); - component.destroy(); + if (popup && popup[0]) { + popup[0].destroy(); + } + if (component) { + component.destroy(); + } }, }; }, diff --git a/js/src/components/Editor/MentionList.vue b/js/src/components/Editor/MentionList.vue index 7712ffe6f..004eb9866 100644 --- a/js/src/components/Editor/MentionList.vue +++ b/js/src/components/Editor/MentionList.vue @@ -7,7 +7,7 @@ :key="index" @click="selectItem(index)" > - <actor-card :actor="item" /> + <actor-inline :actor="item" /> </button> </div> </template> @@ -16,11 +16,11 @@ import { Vue, Component, Prop, Watch } from "vue-property-decorator"; import { displayName, usernameWithDomain } from "@/types/actor/actor.model"; import { IPerson } from "@/types/actor"; -import ActorCard from "../../components/Account/ActorCard.vue"; +import ActorInline from "../../components/Account/ActorInline.vue"; @Component({ components: { - ActorCard, + ActorInline, }, }) export default class MentionList extends Vue { diff --git a/js/src/components/Todo/FullTodo.vue b/js/src/components/Todo/FullTodo.vue index ee515fcdb..7719b3cde 100644 --- a/js/src/components/Todo/FullTodo.vue +++ b/js/src/components/Todo/FullTodo.vue @@ -7,9 +7,7 @@ <b-field :label="$t('Title')"> <b-input v-model="title" /> </b-field> - <b-field :label="$t('Assigned to')"> - <actor-auto-complete v-model="assignedTo" /> - </b-field> + <b-field :label="$t('Assigned to')"> </b-field> <b-field :label="$t('Due on')"> <b-datepicker v-model="dueDate" :first-day-of-week="firstDayOfWeek" /> </b-field> @@ -17,19 +15,15 @@ </div> </template> <script lang="ts"> -import { Component, Prop, Vue } from "vue-property-decorator"; +import { Prop, Vue } from "vue-property-decorator"; import debounce from "lodash/debounce"; import { DebouncedFunc } from "lodash"; import { SnackbarProgrammatic as Snackbar } from "buefy"; import { ITodo } from "../../types/todos"; import RouteName from "../../router/name"; import { UPDATE_TODO } from "../../graphql/todos"; -import ActorAutoComplete from "../Account/ActorAutoComplete.vue"; import { IPerson } from "../../types/actor"; -@Component({ - components: { ActorAutoComplete }, -}) export default class Todo extends Vue { @Prop({ required: true, type: Object }) todo!: ITodo;