Trix: Support for @mentions

Created on 4 Aug 2016  路  13Comments  路  Source: basecamp/trix

It would be great to allow @mentions in some way. Probably through pluggable list of keywords and content to display in the drop-down list.

cc @jshirley, @lpsBetty, @jwilsjustin

(Opening a new issue because the previous one #45 was closed in favor of #31, which was then also closed.)

Most helpful comment

Thanks to @lpsBetty, got it run by Tribute: https://github.com/zurb/tribute

  var tribute = new Tribute({
          values: [
              {key: 'Phil Heartman', value: 'pheartman'},
              {key: 'Gordon Ramsey', value: 'gramsey'}]
  });           
  tribute.attach($('trix-editor'));
  var editor = $('trix-editor')[0].editor;
  if (editor != null) {
      editor.composition.delegate.inputController.events.keypress = function() {};
          editor.composition.delegate.inputController.events.keydown = function() {};
  }

All 13 comments

I have got it working with https://github.com/yuku-t/jquery-textcomplete

you just need to do 1 thing to prevent that trix makes a line break on ENTER instead of inserting the mention:

var editor = $element[0].editor;
var events = angular.copy(editor.composition.delegate.inputController.events);

// Prevent line break by Trix if user selects a mention.
$element.on('textComplete:show', function() {
  editor.composition.delegate.inputController.events.keypress = angular.noop;
  editor.composition.delegate.inputController.events.keydown = angular.noop;
});
$element.on('textComplete:hide', function() {
  editor.composition.delegate.inputController.events.keypress = events.keypress;
  editor.composition.delegate.inputController.events.keydown = events.keydown;
});

(I am using angular and created an angular component)

Do you have a demo with it and Trix somewhere?

No sorry, not at the moment.. I can create a jsfiddle when I have time! ;)

We don't plan to add built in support @mentions or autocomplete, but it would make a nice plugin. I described Basecamp 3's implementation at a high level in https://github.com/basecamp/trix/issues/270#issuecomment-233347093.

I also have issues with enter. Is the solution above really the cleanest?

I found it easier to patch Trix.InputController::keys.return which is the only keystroke I really care (I would want that return selects current user, and not make a new line, while a dialog for selecting users is shown).

I have something like:

originalReturn = Trix.InputController::keys.return
Trix.InputController::keys.return = (event) ->  
  return if isDialogShown()

  originalReturn.call @, event

Here's another approach using selectize.js as the container for managing @mentions:

https://gist.github.com/lawso017/44df47968be36222b874b8c4d94b779b

And one with Meteor, Blaze, and custom pop-up dialog: https://github.com/peer/mind/blob/master/packages/peermind/base/editor.coffee

It works pretty well once you figure out all the details. :-)

Thanks to @lpsBetty, got it run by Tribute: https://github.com/zurb/tribute

  var tribute = new Tribute({
          values: [
              {key: 'Phil Heartman', value: 'pheartman'},
              {key: 'Gordon Ramsey', value: 'gramsey'}]
  });           
  tribute.attach($('trix-editor'));
  var editor = $('trix-editor')[0].editor;
  if (editor != null) {
      editor.composition.delegate.inputController.events.keypress = function() {};
          editor.composition.delegate.inputController.events.keydown = function() {};
  }

Just to add to @hailiangwangutd and @lpsBetty solutions, you will have to include jquery prior the Tribute script. It's working for me.

    // Patch Tribute
    this.tribute.attach(this.element)
    this.tribute.range.pasteHtml = function(html, startPos, endPos) {
      editor.deleteInDirection("backward")

      let attachment = new Trix.Attachment({ content: html })
      editor.insertAttachment(attachment)
    }
    this.tribute.events.getKeyCode = function(instance, el, event) {
      if (event.isComposing) return

      let tribute = instance.tribute
      let info = tribute.range.getTriggerInfo(false, false, true, tribute.allowSpaces)

      if (info) {
        return info.mentionTriggerChar.charCodeAt(0)
      } else {
        return false
      }
    }

Just to add another implementation here:

~~~ coffeescript
document.addEventListener 'trix-initialize', (event) ->
element = event.target
editor = element.editor
tribute.attach element

replaced = (event) ->
# delete the matching text and the at sign
match_size = (event.detail.item.string.match(//g) || []).length + 1
editor.deleteInDirection("backward") for [0...match_size]

# add the mention as an attachment
mention = event.detail.item.original
attachment = new Trix.Attachment
  user_id: mention.id,
  content: "<span class='mention'>@#{ mention.value }</span>",
editor.insertAttachment attachment
editor.insertString " " # add an empty space to continue

element.addEventListener 'tribute-replaced', replaced
~~~

The main challenge is working without hooks, as both tribute and trix give a pretty small interface to play with.

It doesn't solve the enter or tab issue (tab does change the indentation when working on a list).

It would be awesome to have a hook on input to preventDefault if tributte.isActive, but there are not.

Just to add another implementation here:

 document.addEventListener 'trix-initialize', (event) ->
  element = event.target
  editor = element.editor
  tribute.attach element

  replaced = (event) ->
    # delete the matching text and the at sign
    match_size = (event.detail.item.string.match(/<span>/g) || []).length + 1
    editor.deleteInDirection("backward") for [0...match_size]

    # add the mention as an attachment
    mention = event.detail.item.original
    attachment = new Trix.Attachment
      user_id: mention.id,
      content: "<span class='mention'>@#{ mention.value }</span>",
    editor.insertAttachment attachment
    editor.insertString " " # add an empty space to continue

  element.addEventListener 'tribute-replaced', replaced

The main challenge is working without hooks, as both tribute and trix give a pretty small interface to play with.

It doesn't solve the enter or tab issue (tab does change the indentation when working on a list).

It would be awesome to have a hook on input to preventDefault if tributte.isActive, but there are not.

Thanks!

Also added this line:

this.tribute.range.pasteHtml = function(html, startPos, endPos) {}

to fix an non-replacing issue in chrome.

Was this page helpful?
0 / 5 - 0 ratings