mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-27 02:10:27 +00:00
Fix actor auto-complete
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
fbe5a8d0c4
commit
c57d192abe
5 changed files with 27 additions and 135 deletions
|
@ -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">
|
||||
<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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue