Skip to content

Conversation

@bmspereira-07
Copy link
Contributor

Description

Add mentions functionality to the core Rich Editor form field

Visual changes

image

Functional changes

  • Code style has been fixed by running the composer cs command.
  • Changes have been tested to not break existing functionality.
  • Documentation is up-to-date.

@github-project-automation github-project-automation bot moved this to Todo in Roadmap Aug 22, 2025
@bmspereira-07 bmspereira-07 changed the title Add mentions to rich editor [v4] Add mentions to rich editor Aug 22, 2025
@bmspereira-07 bmspereira-07 changed the title [v4] Add mentions to rich editor [v4.x] Add mentions to rich editor Aug 23, 2025
@bmspereira-07 bmspereira-07 changed the title [v4.x] Add mentions to rich editor [4.x] Add mentions to rich editor Aug 23, 2025
@danharrin danharrin added enhancement New feature or request pending review labels Aug 25, 2025
@danharrin danharrin added this to the v4 milestone Aug 25, 2025

return {
editor: tiptapEditor,
char: '@',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be cool to be able to customize the character.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ is quite common. What other char do you have in mind?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saade
Copy link
Member

saade commented Aug 28, 2025

It would be cool to have an API just like select does.

->getMentionSearchResultsUsing(
    fn (string $search): array => User::query()
            ->where('name', 'like', "%{$search}%")
            ->limit(50)
            ->pluck('name', 'id')
            ->all()
)

This would allow dynamic searching and not have to return all the mentionables from the database.

@bmspereira-07
Copy link
Contributor Author

It would be cool to have an API just like select does.

->getMentionSearchResultsUsing(
    fn (string $search): array => User::query()
            ->where('name', 'like', "%{$search}%")
            ->limit(50)
            ->pluck('name', 'id')
            ->all()
)

This would allow dynamic searching and not have to return all the mentionables from the database.

That would be cool

@bmspereira-07
Copy link
Contributor Author

Hey there! I updated the pull request with some changes, now you can add multiple symbols with the respective items and a global function to limit the number of results it shows when your using the mentions.

@GregoireGaonach
Copy link

It would be great to get mentions back in the rich editor like it was with awcodes/filament-tiptap-editor. I'm rooting for this PR!

Copy link
Member

@danharrin danharrin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would accept this if it allowed a server request to load mention search results. Otherwise, I think the scope is quite limited as loading every user into JS in a large app would be slow.

@github-project-automation github-project-automation bot moved this from Todo to In Progress in Roadmap Sep 16, 2025
@bmspereira-07
Copy link
Contributor Author

I would accept this if it allowed a server request to load mention search results. Otherwise, I think the scope is quite limited as loading every user into JS in a large app would be slow.

That would be great. But I never tackled something like that. Does the tiptap extension need to have a function to fetch asynchronously the items? I would love some guidelines on how to approach this if that's alright ofc.

@saade
Copy link
Member

saade commented Sep 17, 2025

@bmspereira-07

Does the tiptap extension need to have a function to fetch asynchronously the items?

Yes, I've already played with mentions before, and it's done using the same api you're already using.

items: ({ query }) => getMentionSearchResultsUsing(query),

This function needs to be passed down to the Alpine component. You can examine the FileUpload. component to see how it passes a Livewire component function to the inside of the Alpine component.

Let me know if you need any help implementing that.

@bmspereira-07
Copy link
Contributor Author

bmspereira-07 commented Sep 17, 2025

@saade @danharrin

Latest commit includes the async fetch of the data. I based of the select component because I got lost in the fileupload one.

Please let me know, if its alright or anything needs to be changed.

Bruno Pereira added 2 commits September 17, 2025 12:38
Copy link
Member

@danharrin danharrin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention labels shouldnt be stored in the content, so we need a way to fetch the corresponding labels for mentioned IDs asynchronously too, for when existing content with custom labels is loaded into the editor. Similar to getOptionLabelsUsing() on a Select.

@bmspereira-07
Copy link
Contributor Author

bmspereira-07 commented Sep 17, 2025

Mention labels shouldnt be stored in the content, so we need a way to fetch the corresponding labels for mentioned IDs asynchronously too, for when existing content with custom labels is loaded into the editor. Similar to getOptionLabelsUsing() on a Select.

I understand what you're saying but does it make sense in this case? Because once you select a mention item, the plugin injects a span in the editor. once you refresh the page after saving changes it's just plain text.

Or am I missing something.

I know that in the select is used to prepopulate the dropdown with data (being user->name, user->email) based on the key of the option without extra queries.

@danharrin
Copy link
Member

If each mention has an ID and a label, and we are storing mentioned IDs in the content, then we need something to translate those IDs into labels again for the preview when the content is loaded back into the editor

@bmspereira-07
Copy link
Contributor Author

bmspereira-07 commented Sep 17, 2025

If each mention has an ID and a label, and we are storing mentioned IDs in the content, then we need something to translate those IDs into labels again for the preview when the content is loaded back into the editor

That is useful if the content isn't already present because in the database it already saves the full html.

Example:

You have a richeditor for comments of a task.

you start typing "@bruno can you finish this task?" the editor will output something like <p><span data-mention-id="69">Bruno</span> can you finish this task?<p> and in the DB it will be:
{"type":"doc","content":[{"type":"paragraph","attrs":{"class":null,"style":null},"content":[{"type":"mention","attrs":{"id":69,"label":"Bruno"}},{"type":"text","text":" "},{"type":"text","text":"can you finish this task?a"}]}]}

So the information is already there, there's no need for extra queries. If I change my name from Bruno to John, the already mention will still be Bruno.

Or what you're trying to say is that when the info is shown it always get the most updated info (taking the change of name as an example, if you in the morning mention me by bruno and in the afternoon I change my name to John, so when I open the task the mention should be @John instead of @Bruno or whatever is defined in the getOptionLabelsUsing()

I'm clearly missing something that should make sense, but in my mind it doesnt 😅

@saade
Copy link
Member

saade commented Sep 17, 2025

Or what you're trying to say is that when the info is shown it always get the most updated info (taking the change of name as an example, if you in the morning mention me by bruno and in the afternoon I change my name to John, so when I open the task the mention should be @John instead of @Bruno or whatever is defined in the getOptionLabelsUsing()

Yes, it should only store the id and fetch the label when rendering content for that exact reason

@bmspereira-07
Copy link
Contributor Author

bmspereira-07 commented Sep 17, 2025

Or what you're trying to say is that when the info is shown it always get the most updated info (taking the change of name as an example, if you in the morning mention me by bruno and in the afternoon I change my name to John, so when I open the task the mention should be @John instead of @Bruno or whatever is defined in the getOptionLabelsUsing()

Yes, it should only store the id and fetch the label when rendering content for that exact reason

Gotcha, it's gonna be tricky if I use multiple options ( @ for users and # for channels, for example)

Unless you guys prefer to keep it simple as a hardcoded '@' and one option for mentions.

@danharrin
Copy link
Member

Ideally the JSON would not contain the label at all, it would be stripped out completely.

Could configure it like ->mentions([MentionProvider::make('@')->getSearchResultsUsing(...), MentionProvider::make('#')->getSearchResultsUsing(...)])

@bmspereira-07
Copy link
Contributor Author

Hey @danharrin and others, sorry for tagging but do you have any feedback on this, or you're still planning my comedy central roast? 😬🥲

@danharrin
Copy link
Member

I'll let you know when the roast is ready...

Jokes aside, I'll take it from here and add commits or create a new PR. Thanks for your work

@bmspereira-07
Copy link
Contributor Author

I'll let you know when the roast is ready...

Jokes aside, I'll take it from here and add commits or create a new PR. Thanks for your work

Cheers, thanks.

PS Don't invite Jeff Ross for the roast

@Luk4veck1
Copy link
Contributor

Luk4veck1 commented Oct 3, 2025

Issue: When RichEditor is rendered on modal, the dropdown with mentions pops up behind the modal and you are unable to select anything. I found a quick solution for this, just add z-index to the style of the dropdown in mention-suggestion.js file when we are creating dropdown.

Solution:

element = createDropdown()
                element.style.position = 'absolute'
                element.style.zIndex = '50' // add this line

Recommendation:

Another thing I've had issue with is saving the actual mention. So, if I wanted to save some text 'Test @Admin', it would result in my DB as <p>Test</p> and not mention saved. What should be specified in docs is we need to add ->json() to our RichEditor, otherwise this doesn't work. So by adding ->json() this is now working as intended and we have json stored in DB.

RichEditor::make('email_message')
  ->required()
  ->mentions([
      MentionProvider::make('@')
          ->getSearchResultsUsing(fn (string $search): array => User::query()
              ->where('name', 'like', "%{$search}%")
              ->orderBy('name')
              ->limit(10)
              ->pluck('name', 'id')
              ->all())
          ->getOptionLabelUsing(fn ($value): ?string => User::find($value)?->name),
  ])->json()

@thijskuilman
Copy link
Contributor

thijskuilman commented Oct 7, 2025

Jokes aside, I'll take it from here and add commits or create a new PR. Thanks for your work

Hi @danharrin. I was wondering if you'd appreciate some help to get this issue over the finish line. I've contributed a mention feature in the awcodes package and would love to see it natively in the Rich Editor.

thijskuilman added a commit to sibizwolle/filament-fork that referenced this pull request Oct 9, 2025
@albertobenavides
Copy link

How I can use this branch in order to implement the mentions addon?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request pending review

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

9 participants