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