This PR adds support for including/excluding specific items from team filtering. Users can use the filter modal to only show teams that include specific items, only show teams that _don't_ include specific items, or combine the two to create a very powerful filter.
145 lines
3.7 KiB
TypeScript
145 lines
3.7 KiB
TypeScript
import type { JSONContent } from '@tiptap/core'
|
|
import { ReactRenderer } from '@tiptap/react'
|
|
import tippy, { Instance as TippyInstance } from 'tippy.js'
|
|
import { getCookie } from 'cookies-next'
|
|
|
|
import {
|
|
MentionList,
|
|
MentionRef,
|
|
MentionSuggestion,
|
|
} from '~components/MentionList'
|
|
import api from '~utils/api'
|
|
import { numberToElement } from '~utils/elements'
|
|
import { SuggestionOptions } from '~extensions/CustomSuggestion'
|
|
|
|
interface RawSearchResponse {
|
|
searchable_type: string
|
|
granblue_id: string
|
|
name_en: string
|
|
name_jp: string
|
|
element: number
|
|
}
|
|
|
|
interface SearchResponse {
|
|
name: {
|
|
[key: string]: string
|
|
en: string
|
|
ja: string
|
|
}
|
|
type: string
|
|
granblue_id: string
|
|
element: GranblueElement
|
|
}
|
|
|
|
function transform(object: RawSearchResponse) {
|
|
const result: SearchResponse = {
|
|
name: {
|
|
en: object.name_en,
|
|
ja: object.name_jp,
|
|
},
|
|
type: object.searchable_type.toLowerCase(),
|
|
granblue_id: object.granblue_id,
|
|
element: numberToElement(object.element),
|
|
}
|
|
return result
|
|
}
|
|
|
|
function parseMentions(data: JSONContent) {
|
|
const mentions: string[] = (data.content || []).flatMap(parseMentions)
|
|
if (data.type === 'mention') {
|
|
const granblueId = data.attrs?.id.granblue_id
|
|
mentions.push(granblueId)
|
|
}
|
|
|
|
return [...new Set(mentions)]
|
|
}
|
|
|
|
export const mentionSuggestionOptions: Omit<SuggestionOptions, 'editor'> = {
|
|
items: async ({ query, editor }): Promise<MentionSuggestion[]> => {
|
|
const locale = getCookie('NEXT_LOCALE')
|
|
? (getCookie('NEXT_LOCALE') as string)
|
|
: 'en'
|
|
const response = await api.searchAll(query, [], locale)
|
|
const results = response.data.results
|
|
|
|
return results
|
|
.map((rawObject: RawSearchResponse, index: number) => {
|
|
const object = transform(rawObject)
|
|
return {
|
|
granblue_id: object.granblue_id,
|
|
element: object.element,
|
|
type: object.type,
|
|
name: {
|
|
en: object.name.en,
|
|
ja: object.name.ja,
|
|
},
|
|
}
|
|
})
|
|
.slice(0, 7)
|
|
},
|
|
|
|
render: () => {
|
|
let component: ReactRenderer<MentionRef> | undefined
|
|
let popup: TippyInstance | undefined
|
|
return {
|
|
onStart: (props) => {
|
|
component = new ReactRenderer(MentionList, {
|
|
props,
|
|
editor: props.editor,
|
|
})
|
|
|
|
console.log('in onStart')
|
|
const rect = props.clientRect?.()
|
|
console.log(rect)
|
|
|
|
popup = tippy('body', {
|
|
getReferenceClientRect: props.clientRect,
|
|
appendTo: () => document.body,
|
|
content: component.element,
|
|
showOnCreate: true,
|
|
interactive: true,
|
|
trigger: 'manual',
|
|
placement: 'bottom-start',
|
|
})[0]
|
|
},
|
|
|
|
onUpdate(props) {
|
|
component?.updateProps(props)
|
|
|
|
popup?.setProps({
|
|
getReferenceClientRect: props.clientRect,
|
|
})
|
|
},
|
|
|
|
onKeyDown(props) {
|
|
if (props.event.key === 'Escape') {
|
|
popup?.hide()
|
|
return true
|
|
}
|
|
|
|
if (props.event.key === 'Tab') {
|
|
popup?.hide()
|
|
}
|
|
|
|
if (!component?.ref) {
|
|
return false
|
|
}
|
|
|
|
return component?.ref.onKeyDown(props)
|
|
},
|
|
|
|
onExit() {
|
|
popup?.destroy()
|
|
component?.destroy()
|
|
|
|
// Remove references to the old popup and component upon destruction/exit.
|
|
// (This should prevent redundant calls to `popup.destroy()`, which Tippy
|
|
// warns in the console is a sign of a memory leak, as the `suggestion`
|
|
// plugin seems to call `onExit` both when a suggestion menu is closed after
|
|
// a user chooses an option, *and* when the editor itself is destroyed.)
|
|
popup = undefined
|
|
component = undefined
|
|
},
|
|
}
|
|
},
|
|
}
|