suggestion.ts raw
1 import customEmojiService from '@/services/custom-emoji.service'
2 import postEditor from '@/services/post-editor.service'
3 import type { Editor } from '@tiptap/core'
4 import { ReactRenderer } from '@tiptap/react'
5 import { SuggestionKeyDownProps } from '@tiptap/suggestion'
6 import tippy, { GetReferenceClientRect, Instance, Props } from 'tippy.js'
7 import { EmojiList, EmojiListHandler, EmojiListProps } from './EmojiList'
8
9 const suggestion = {
10 items: async ({ query }: { query: string }) => {
11 return await customEmojiService.searchEmojis(query)
12 },
13
14 render: () => {
15 let component: ReactRenderer<EmojiListHandler, EmojiListProps> | undefined
16 let popup: Instance[] = []
17 let touchListener: (e: TouchEvent) => void
18 let closePopup: () => void
19
20 return {
21 onBeforeStart: () => {
22 touchListener = (e: TouchEvent) => {
23 if (popup && popup[0] && postEditor.isSuggestionPopupOpen) {
24 const popupElement = popup[0].popper
25 if (popupElement && !popupElement.contains(e.target as Node)) {
26 popup[0].hide()
27 }
28 }
29 }
30 document.addEventListener('touchstart', touchListener)
31
32 closePopup = () => {
33 if (popup && popup[0]) {
34 popup[0].hide()
35 }
36 }
37 postEditor.addEventListener('closeSuggestionPopup', closePopup)
38 },
39 onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
40 component = new ReactRenderer(EmojiList, {
41 props,
42 editor: props.editor
43 })
44
45 if (!props.clientRect) {
46 return
47 }
48
49 popup = tippy('body', {
50 getReferenceClientRect: props.clientRect as GetReferenceClientRect,
51 appendTo: () => document.body,
52 content: component.element,
53 showOnCreate: true,
54 interactive: true,
55 trigger: 'manual',
56 placement: 'bottom-start',
57 hideOnClick: true,
58 touch: true,
59 onShow() {
60 postEditor.isSuggestionPopupOpen = true
61 },
62 onHide() {
63 postEditor.isSuggestionPopupOpen = false
64 }
65 })
66 },
67
68 onUpdate(props: { clientRect?: (() => DOMRect | null) | null | undefined }) {
69 component?.updateProps(props)
70
71 if (!props.clientRect) {
72 return
73 }
74
75 popup[0]?.setProps({
76 getReferenceClientRect: props.clientRect
77 } as Partial<Props>)
78 },
79
80 onKeyDown(props: SuggestionKeyDownProps) {
81 if (props.event.key === 'Escape') {
82 popup[0]?.hide()
83 return true
84 }
85 return component?.ref?.onKeyDown(props) ?? false
86 },
87
88 onExit() {
89 postEditor.isSuggestionPopupOpen = false
90 popup[0]?.destroy()
91 component?.destroy()
92
93 document.removeEventListener('touchstart', touchListener)
94 postEditor.removeEventListener('closeSuggestionPopup', closePopup)
95 }
96 }
97 }
98 }
99
100 export default suggestion
101