mobilizon/js/src/components/Editor/Mention.ts

83 lines
2.3 KiB
TypeScript

import { SEARCH_PERSONS } from "@/graphql/search";
import { VueRenderer } from "@tiptap/vue-3";
import tippy from "tippy.js";
import MentionList from "./MentionList.vue";
import { ApolloClient } from "@apollo/client/core/ApolloClient";
import { apolloClient } from "@/vue-apollo";
import { IPerson } from "@/types/actor";
import pDebounce from "p-debounce";
import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types";
const client = apolloClient as ApolloClient<NormalizedCacheObject>;
const fetchItems = async (query: string): Promise<IPerson[]> => {
const result = await client.query({
query: SEARCH_PERSONS,
variables: {
searchText: query,
},
});
// TipTap doesn't handle async for onFilter, hence the following line.
return result.data.searchPersons.elements;
};
const debouncedFetchItems = pDebounce(fetchItems, 200);
const mentionOptions: Partial<any> = {
HTMLAttributes: {
class: "mention",
dir: "ltr",
},
suggestion: {
items: async (query: string): Promise<IPerson[]> => {
if (query === "") {
return [];
}
return await debouncedFetchItems(query);
},
render: () => {
let component: VueRenderer;
let popup: any;
return {
onStart: (props: any) => {
component = new VueRenderer(MentionList, {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parent: this,
propsData: props,
});
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: "manual",
placement: "bottom-start",
});
},
onUpdate(props: any) {
component.updateProps(props);
popup[0].setProps({
getReferenceClientRect: props.clientRect,
});
},
onKeyDown(props: any) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return component.ref?.onKeyDown(props);
},
onExit() {
popup[0].destroy();
component.destroy();
},
};
},
},
};
export default mentionOptions;