manifold/web/components/editor/contract-mention-list.tsx
Austin Chen 140628692f
Use %mention to embed a contract card in rich text editor (#869)
* Bring up a list of contracts with @

* Fix hot reload for RichContent

* Render contracts as half-size cards

* Use % as the prompt; allow for spaces

* WIP: When there's no matching question, create a new contract

* Revert "WIP: When there's no matching question, create a new contract"

This reverts commit efae1bf715.

* Rename to contract-mention

* WIP: Try to merge in @ and % side by side

* Add a different pluginKey

* Track the prosemirror-state dep
2022-09-15 15:12:26 -07:00

69 lines
2.1 KiB
TypeScript

import { SuggestionProps } from '@tiptap/suggestion'
import clsx from 'clsx'
import { Contract } from 'common/contract'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import { contractPath } from 'web/lib/firebase/contracts'
import { Avatar } from '../avatar'
// copied from https://tiptap.dev/api/nodes/mention#usage
const M = forwardRef((props: SuggestionProps<Contract>, ref) => {
const { items: contracts, command } = props
const [selectedIndex, setSelectedIndex] = useState(0)
useEffect(() => setSelectedIndex(0), [contracts])
const submitUser = (index: number) => {
const contract = contracts[index]
if (contract)
command({ id: contract.id, label: contractPath(contract) } as any)
}
const onUp = () =>
setSelectedIndex((i) => (i + contracts.length - 1) % contracts.length)
const onDown = () => setSelectedIndex((i) => (i + 1) % contracts.length)
const onEnter = () => submitUser(selectedIndex)
useImperativeHandle(ref, () => ({
onKeyDown: ({ event }: any) => {
if (event.key === 'ArrowUp') {
onUp()
return true
}
if (event.key === 'ArrowDown') {
onDown()
return true
}
if (event.key === 'Enter') {
onEnter()
return true
}
return false
},
}))
return (
<div className="w-42 absolute z-10 overflow-x-hidden rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{!contracts.length ? (
<span className="m-1 whitespace-nowrap">No results...</span>
) : (
contracts.map((contract, i) => (
<button
className={clsx(
'flex h-8 w-full cursor-pointer select-none items-center gap-2 truncate px-4 hover:bg-indigo-200',
selectedIndex === i ? 'bg-indigo-500 text-white' : 'text-gray-900'
)}
onClick={() => submitUser(i)}
key={contract.id}
>
<Avatar avatarUrl={contract.creatorAvatarUrl} size="xs" />
{contract.question}
</button>
))
)}
</div>
)
})
// Just to keep the formatting pretty
export { M as MentionList }