* 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
		
	
			
		
			
				
	
	
		
			69 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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 }
 |