diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 6dc5ba8f..0620a112 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -7,7 +7,6 @@ import { JSONContent, Content, Editor, - ReactRenderer, } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import { Image } from '@tiptap/extension-image' @@ -18,12 +17,11 @@ import { useEffect } from 'react' import { Linkify } from './linkify' import { uploadImage } from 'web/lib/firebase/storage' import { useMutation } from 'react-query' -import { exhibitExts, searchInAny } from 'common/util/parse' +import { exhibitExts } from 'common/util/parse' import { FileUploadButton } from './file-upload-button' import { linkClass } from './site-link' import { useUsers } from 'web/hooks/use-users' -import { MentionList } from './editor/mention-list' -import tippy from 'tippy.js' +import { mentionSuggestion } from './editor/mention-suggestion' const proseClass = clsx( 'prose prose-p:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed', @@ -75,59 +73,7 @@ export function useTextEditor(props: { { href: node.attrs.label }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`, ] as any, - suggestion: { - items: ({ query }) => - users - .filter((u) => searchInAny(query, u.username, u.name)) - .slice(0, 5), - render: () => { - let component: any - let popup: any - return { - onStart: (props) => { - component = new ReactRenderer(MentionList, { - props, - editor: props.editor, - }) - if (!props.clientRect) { - return - } - - popup = tippy('body', { - getReferenceClientRect: props.clientRect as any, - appendTo: () => document.body, - content: component.element, - showOnCreate: true, - interactive: true, - trigger: 'manual', - placement: 'bottom-start', - }) - }, - onUpdate(props) { - component.updateProps(props) - - if (!props.clientRect) { - return - } - - popup[0].setProps({ - getReferenceClientRect: props.clientRect, - }) - }, - onKeyDown(props) { - if (props.event.key === 'Escape') { - popup[0].hide() - return true - } - return component.ref?.onKeyDown(props) - }, - onExit() { - popup[0].destroy() - component.destroy() - }, - } - }, - }, + suggestion: mentionSuggestion(users), }), ], content: defaultValue, diff --git a/web/components/editor/mention-list.tsx b/web/components/editor/mention-list.tsx index 179a818f..f9e67daf 100644 --- a/web/components/editor/mention-list.tsx +++ b/web/components/editor/mention-list.tsx @@ -4,7 +4,7 @@ import { User } from 'common/user' import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' import { Avatar } from '../avatar' -// copied from https://tiptap.dev/api/nodes/mention +// copied from https://tiptap.dev/api/nodes/mention#usage export const MentionList = forwardRef((props: SuggestionProps, ref) => { const { items: users, command } = props diff --git a/web/components/editor/mention-suggestion.ts b/web/components/editor/mention-suggestion.ts new file mode 100644 index 00000000..9a858b6b --- /dev/null +++ b/web/components/editor/mention-suggestion.ts @@ -0,0 +1,61 @@ +import type { MentionOptions } from '@tiptap/extension-mention' +import { ReactRenderer } from '@tiptap/react' +import { User } from 'common/lib/user' +import { searchInAny } from 'common/util/parse' +import tippy from 'tippy.js' +import { MentionList } from './mention-list' + +type Suggestion = MentionOptions['suggestion'] + +// copied from https://tiptap.dev/api/nodes/mention#usage +export const mentionSuggestion = (users: User[]): Suggestion => ({ + items: ({ query }) => + users.filter((u) => searchInAny(query, u.username, u.name)).slice(0, 5), + render: () => { + let component: any + let popup: any + return { + onStart: (props) => { + component = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }) + if (!props.clientRect) { + return + } + + popup = tippy('body', { + getReferenceClientRect: props.clientRect as any, + appendTo: () => document.body, + content: component.element, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'bottom-start', + }) + }, + onUpdate(props) { + component.updateProps(props) + + if (!props.clientRect) { + return + } + + popup[0].setProps({ + getReferenceClientRect: props.clientRect, + }) + }, + onKeyDown(props) { + if (props.event.key === 'Escape') { + popup[0].hide() + return true + } + return component.ref?.onKeyDown(props) + }, + onExit() { + popup[0].destroy() + component.destroy() + }, + } + }, +})