66 Block as BlockNoteBlock ,
77 BlockNoteSchema ,
88 defaultBlockSpecs ,
9+ defaultInlineContentSpecs ,
910 filterSuggestionItems
1011} from '@blocknote/core'
1112import {
@@ -29,6 +30,7 @@ import {
2930 cleanupCollaborationProvider
3031} from '@/lib/collaboration/yjs-provider'
3132import type YPartyKitProvider from 'y-partykit/provider'
33+ import { Mention } from './Mention'
3234
3335// Dynamically import DatabaseBlock to avoid SSR issues
3436const DatabaseBlock = dynamic ( ( ) => import ( './DatabaseBlock' ) , {
@@ -52,6 +54,11 @@ interface BlockNoteEditorProps {
5254 email ?: string | null
5355 }
5456 enableCollaboration ?: boolean
57+ workspaceMembers ?: Array < {
58+ id : string
59+ name ?: string | null
60+ email ?: string | null
61+ } >
5562}
5663
5764type SaveStatus = 'saved' | 'saving' | 'error' | 'unsaved'
@@ -67,7 +74,8 @@ export default function BlockNoteEditorComponent({
6774 showSaveStatus = true ,
6875 userId,
6976 user,
70- enableCollaboration = true
77+ enableCollaboration = true ,
78+ workspaceMembers = [ ]
7179} : BlockNoteEditorProps ) {
7280 const [ saveStatus , setSaveStatus ] = useState < SaveStatus > ( 'saved' )
7381 const autoSaveTimeoutRef = useRef < NodeJS . Timeout | null > ( null )
@@ -165,6 +173,10 @@ export default function BlockNoteEditorComponent({
165173 const schema = useMemo ( ( ) => {
166174 return BlockNoteSchema . create ( {
167175 blockSpecs : customBlockSpecs ,
176+ inlineContentSpecs : {
177+ ...defaultInlineContentSpecs ,
178+ mention : Mention ,
179+ } ,
168180 } )
169181 } , [ customBlockSpecs ] )
170182
@@ -275,6 +287,54 @@ export default function BlockNoteEditorComponent({
275287 [ insertDatabaseItem ]
276288 )
277289
290+ // Get mention menu items from workspace members
291+ const getMentionMenuItems = useCallback (
292+ ( editor : any ) : DefaultReactSuggestionItem [ ] => {
293+ // Include the current user
294+ const allUsers = [
295+ ...( user ? [ user ] : [ ] ) ,
296+ ...workspaceMembers . filter ( member => member . id !== user ?. id )
297+ ]
298+
299+ return allUsers . map ( ( member ) => ( {
300+ title : member . name || member . email || 'Unknown User' ,
301+ onItemClick : async ( ) => {
302+ editor . insertInlineContent ( [
303+ {
304+ type : "mention" ,
305+ props : {
306+ user : member . name || member . email || 'Unknown' ,
307+ userId : member . id ,
308+ email : member . email || undefined
309+ } ,
310+ } ,
311+ " " , // add a space after the mention
312+ ] )
313+
314+ // Send notification for the mention
315+ if ( member . id !== user ?. id ) {
316+ try {
317+ await fetch ( '/api/notifications/mention' , {
318+ method : 'POST' ,
319+ headers : { 'Content-Type' : 'application/json' } ,
320+ body : JSON . stringify ( {
321+ userId : member . id ,
322+ pageId,
323+ workspaceId,
324+ message : `mentioned you in a document`
325+ } )
326+ } )
327+ } catch ( error ) {
328+ console . error ( 'Failed to send mention notification:' , error )
329+ }
330+ }
331+ } ,
332+ subtext : member . email || undefined ,
333+ } ) )
334+ } ,
335+ [ user , workspaceMembers , pageId , workspaceId ]
336+ )
337+
278338 // Convert BlockNote blocks back to our storage format
279339 const convertFromBlockNoteFormat = useCallback ( ( blocks : BlockNoteBlock [ ] ) : AppBlockType [ ] => {
280340 try {
@@ -441,6 +501,12 @@ export default function BlockNoteEditorComponent({
441501 filterSuggestionItems ( getCustomSlashMenuItems ( editor ) , query )
442502 }
443503 />
504+ < SuggestionMenuController
505+ triggerCharacter = "@"
506+ getItems = { async ( query ) =>
507+ filterSuggestionItems ( getMentionMenuItems ( editor as any ) , query )
508+ }
509+ />
444510 </ BlockNoteView >
445511 </ div >
446512
0 commit comments