Fix actor auto-complete
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
fbe5a8d0c4
commit
c57d192abe
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue