diff --git a/web/components/editor.tsx b/web/components/editor.tsx index b36571ba..bb947579 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -18,7 +18,6 @@ import { uploadImage } from 'web/lib/firebase/storage' import { useMutation } from 'react-query' import { FileUploadButton } from './file-upload-button' import { linkClass } from './site-link' -import { useUsers } from 'web/hooks/use-users' import { mentionSuggestion } from './editor/mention-suggestion' import { DisplayMention } from './editor/mention' import Iframe from 'common/util/tiptap-iframe' @@ -68,8 +67,6 @@ export function useTextEditor(props: { }) { const { placeholder, max, defaultValue = '', disabled, simple } = props - const users = useUsers() - const editorClass = clsx( proseClass, !simple && 'min-h-[6em]', @@ -78,32 +75,27 @@ export function useTextEditor(props: { '[&_.ProseMirror-selectednode]:outline-dotted [&_*]:outline-indigo-300' // selected img, emebeds ) - const editor = useEditor( - { - editorProps: { attributes: { class: editorClass } }, - extensions: [ - StarterKit.configure({ - heading: simple ? false : { levels: [1, 2, 3] }, - horizontalRule: simple ? false : {}, - }), - Placeholder.configure({ - placeholder, - emptyEditorClass: - 'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0 cursor-text', - }), - CharacterCount.configure({ limit: max }), - simple ? DisplayImage : Image, - DisplayLink, - DisplayMention.configure({ - suggestion: mentionSuggestion(users), - }), - Iframe, - TiptapTweet, - ], - content: defaultValue, - }, - [!users.length] // passed as useEffect dependency. (re-render editor when users load, to update mention menu) - ) + const editor = useEditor({ + editorProps: { attributes: { class: editorClass } }, + extensions: [ + StarterKit.configure({ + heading: simple ? false : { levels: [1, 2, 3] }, + horizontalRule: simple ? false : {}, + }), + Placeholder.configure({ + placeholder, + emptyEditorClass: + 'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0 cursor-text', + }), + CharacterCount.configure({ limit: max }), + simple ? DisplayImage : Image, + DisplayLink, + DisplayMention.configure({ suggestion: mentionSuggestion }), + Iframe, + TiptapTweet, + ], + content: defaultValue, + }) const upload = useUploadMutation(editor) diff --git a/web/components/editor/mention-suggestion.ts b/web/components/editor/mention-suggestion.ts index e21789c9..9f016d47 100644 --- a/web/components/editor/mention-suggestion.ts +++ b/web/components/editor/mention-suggestion.ts @@ -1,9 +1,9 @@ import type { MentionOptions } from '@tiptap/extension-mention' import { ReactRenderer } from '@tiptap/react' -import { User } from 'common/user' import { searchInAny } from 'common/util/parse' import { orderBy } from 'lodash' import tippy from 'tippy.js' +import { getCachedUsers } from 'web/hooks/use-users' import { MentionList } from './mention-list' type Suggestion = MentionOptions['suggestion'] @@ -12,10 +12,12 @@ const beginsWith = (text: string, query: string) => text.toLocaleLowerCase().startsWith(query.toLocaleLowerCase()) // copied from https://tiptap.dev/api/nodes/mention#usage -export const mentionSuggestion = (users: User[]): Suggestion => ({ - items: ({ query }) => +export const mentionSuggestion: Suggestion = { + items: async ({ query }) => orderBy( - users.filter((u) => searchInAny(query, u.username, u.name)), + (await getCachedUsers()).filter((u) => + searchInAny(query, u.username, u.name) + ), [ (u) => [u.name, u.username].some((s) => beginsWith(s, query)), 'followerCountCached', @@ -38,7 +40,7 @@ export const mentionSuggestion = (users: User[]): Suggestion => ({ popup = tippy('body', { getReferenceClientRect: props.clientRect as any, appendTo: () => document.body, - content: component.element, + content: component?.element, showOnCreate: true, interactive: true, trigger: 'manual', @@ -46,27 +48,27 @@ export const mentionSuggestion = (users: User[]): Suggestion => ({ }) }, onUpdate(props) { - component.updateProps(props) + component?.updateProps(props) if (!props.clientRect) { return } - popup[0].setProps({ + popup?.[0].setProps({ getReferenceClientRect: props.clientRect as any, }) }, onKeyDown(props) { if (props.event.key === 'Escape') { - popup[0].hide() + popup?.[0].hide() return true } - return (component.ref as any)?.onKeyDown(props) + return (component?.ref as any)?.onKeyDown(props) }, onExit() { - popup[0].destroy() - component.destroy() + popup?.[0].destroy() + component?.destroy() }, } }, -}) +} diff --git a/web/hooks/use-users.ts b/web/hooks/use-users.ts index 659395b8..330b9c1d 100644 --- a/web/hooks/use-users.ts +++ b/web/hooks/use-users.ts @@ -6,7 +6,8 @@ import { useFollows } from './use-follows' import { useUser } from './use-user' import { useFirestoreQueryData } from '@react-query-firebase/firestore' import { DocumentData } from 'firebase/firestore' -import { users, privateUsers } from 'web/lib/firebase/users' +import { users, privateUsers, getUsers } from 'web/lib/firebase/users' +import { QueryClient } from 'react-query' export const useUsers = () => { const result = useFirestoreQueryData(['users'], users, { @@ -16,6 +17,10 @@ export const useUsers = () => { return result.data ?? [] } +const q = new QueryClient() +export const getCachedUsers = async () => + q.fetchQuery(['users'], getUsers, { staleTime: Infinity }) + export const usePrivateUsers = () => { const result = useFirestoreQueryData( ['private users'],