Skip to content

Instantly share code, notes, and snippets.

@lawso017
Last active July 6, 2022 10:13
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lawso017/44df47968be36222b874b8c4d94b779b to your computer and use it in GitHub Desktop.
Save lawso017/44df47968be36222b874b8c4d94b779b to your computer and use it in GitHub Desktop.
@mentions with trix-editor and selectize.js
window.addEventListener "trix-initialize", (e) =>
Utility.TrixMentions.prepare($(e.target))
# Filter for use with https://github.com/jch/html-pipeline
# to extract mentioned user_ids
module HTML
class Pipeline
class TrixMentionFilter < Filter
attr_reader :mentioned_user_ids
def initialize(doc, context = nil, result = nil)
super doc, context, result
@mentioned_user_ids = []
end
def call
process_attachments! do |attachment|
@mentioned_user_ids << attachment["user-id"].to_i
end
doc
end
def process_attachments!
doc.css("[data-trix-attachment]").each do |attachment_node|
attachment = JSON.parse(attachment_node.attribute('data-trix-attachment'))
next unless attachment["attachment-type"] == "trix-mention"
yield attachment
end
end
end
end
end
window.Utility ||= {}
class Utility.TrixMentions
MENTIONS_PATH = "mentions-path"
MENTIONS_DIV_ID = "#trix-mentions-"
MENTIONS_INPUT_ID = MENTIONS_DIV_ID + "input-"
@prepare: ($trix) ->
mentionsDivForTrix = ($trix) ->
$(MENTIONS_DIV_ID + $trix.attr('trix-id'))
mentionsInputForTrix = ($trix) ->
$(MENTIONS_INPUT_ID + $trix.attr('trix-id'))
mentionsSelectizeForTrix = ($trix) ->
mentionsInputForTrix($trix).next('.selectize-control')
mentionsSelectizeDropdownForTrix = ($trix) ->
mentionsInputForTrix($trix).next('.selectize-dropdown')
createMentionsDivForTrix = ($trix) ->
$trix.after("<trix-mentions id='trix-mentions-"+ $trix.attr('trix-id') + "'><input id='trix-mentions-input-" + $trix.attr('trix-id') + "'></input></trix-mentions>")
initializeMentionsSelectize = ($trix) ->
$.getJSON $trix.data(MENTIONS_PATH), (result) ->
mentionsInputForTrix($trix).selectize
create: false
valueField: 'id'
labelField: 'name'
searchField: 'name'
sortField: 'name'
options: result.data
selectOnTab: true
onInitialize: ->
@.disable()
mentionsSelectizeForTrix($trix).css('left', -1000)
onItemAdd: (value, $item) ->
embed = "<span class='trix-mention'>@" + $item.text() + "</span>"
attachment = new Trix.Attachment
content: embed
'attachment-type': 'trix-mention'
'user-id': $item.data('value')
$trix[0].editor.setSelectedRange([$trix.data('last-position') - 1, $trix.data('last-position')])
$trix[0].editor.deleteInDirection('forward')
$trix[0].editor.insertAttachment attachment
mentionsDivForTrix($trix).hide()
onBlur: ->
mentionsDivForTrix($trix).hide()
@.disable()
mentionsSelectizeForTrix($trix).css('left', -1000)
$trix.focus()
if @.items.length == 0
$trix[0].editor.setSelectedRange([$trix.data('last-position') - 1, $trix.data('last-position')])
$trix[0].editor.deleteInDirection('forward')
onDropdownClose: ->
mentionsDivForTrix($trix).hide()
onDropdownOpen: ($dropdown) ->
rect = $trix[0].editor.getClientRectAtPosition($trix[0].editor.getPosition())
if rect != undefined
$dropdown.css('top', 34)
if rect.left + 200 > $trix.offset().left + $trix.width()
$dropdown.css('left', $trix.offset().left + $trix.width() - (rect.left + 210))
onType: (str) ->
rect = $trix[0].editor.getClientRectAtPosition($trix[0].editor.getPosition())
if rect.left + 200 > $trix.offset().left + $trix.width()
mentionsSelectizeDropdownForTrix($trix).css('left', $trix.offset().left + $trix.width() - (rect.left + 210))
openMentionsSelectize = ($trix, editor) ->
$trix.data 'last-position', editor.getPosition()
mentionsInput = mentionsInputForTrix($trix)
mentionsSelectize = mentionsSelectizeForTrix($trix)
rect = editor.getClientRectAtPosition(editor.getPosition())
mentionsSelectize.css('left', rect.left - 10)
mentionsSelectize.css('top', rect.top - 4)
mentionsInput[0].selectize.clear()
mentionsInput[0].selectize.enable()
mentionsDivForTrix($trix).show()
mentionsInput[0].selectize.focus()
if $trix.data(MENTIONS_PATH).length > 0
createMentionsDivForTrix($trix)
initializeMentionsSelectize($trix)
$trix.on 'trix-change', ->
editor = @.editor
char = editor.getDocument().toString().charAt(editor.getPosition() - 1)
openMentionsSelectize($(@), editor) if char == '@'
[data-trix-attachment] {
display: inline-block;
}
span.trix-mention + figcaption {
display: none;
}
trix-mentions {
.selectize-control {
position: fixed
}
.selectize-input {
border: none !important;
background: none !important;
box-shadow: none !important;
}
.selectize-control, .selectize-dropdown {
width: 200px !important;
z-index: 9999;
}
}
@mitar
Copy link

mitar commented Sep 17, 2016

This triggers autocomplete also inside the code element. Is there a way to prevent that?

@lawso017
Copy link
Author

lawso017 commented Sep 27, 2016

@mitar, the autocomplete trigger is extremely primitive -- any time you hit "@", it's going to open the selectize control:

$trix.on 'trix-change', ->
  editor = @.editor
  char = editor.getDocument().toString().charAt(editor.getPosition() - 1)
  openMentionsSelectize($(@), editor) if char == '@'

I expect you can expand the if char == '@' conditional to do a better evaluation of context and decide if you want to call openMentionsSelectize.

@blimey85
Copy link

blimey85 commented Jul 7, 2017

I've spent the last couple hours trying to debug an error I'm getting and my JS skills just aren't there, especially with coffee thrown into the mix. Hopefully this will be an easy error on my part that you can direct me on how to resolve.

Line 82 of trix_mentions.js.coffee is what's triggering the error.
if $trix.data(MENTIONS_PATH).length > 0

Error as seen in Firefox:
TypeError: $trix.data(...) is undefined

Error as seen in Chrome:

Uncaught TypeError: Cannot read property 'length' of undefined
    at Function.Utility.TrixMentions.TrixMentions.prepare (trix_mentions.self-857d9c9….js?body=1:105)
    at mentionable_trix.self-9ca102d….js?body=1:4
    at triggerEvent (trix.self-34b57b1….js?body=1:16)
    at HTMLElement.notify (trix.self-34b57b1….js?body=1:21)
    at trix.self-34b57b1….js?body=1:21
Utility.TrixMentions.TrixMentions.prepare @ trix_mentions.self-857d9c9….js?body=1:105
(anonymous) @ mentionable_trix.self-9ca102d….js?body=1:4
triggerEvent @ trix.self-34b57b1….js?body=1:16
notify @ trix.self-34b57b1….js?body=1:21
(anonymous) @ trix.self-34b57b1….js?body=1:21

I wasn't sure how I was supposed to set this up. I think all I need to configure is the path to my mentions, which I've done. Here is my config block:

  MENTIONS_PATH = "/user/mentions?r"
  MENTIONS_DIV_ID = "#trix-mentions-"
  MENTIONS_INPUT_ID = MENTIONS_DIV_ID + "input-"

@lawso017, Any thoughts on how I should proceed? I'm running selectize.js 0.12.4 which appears to be the latest. Rails 5.1.2 on Ruby 2.4.1. Thanks!

@lawso017
Copy link
Author

@blimey85, the MENTIONS_PATH constant shouldn't need to be changed in your implementation. It's just defined as a constant to prevent typos in the code... we use the defined value "mentions-path" to identify the data attribute containing the path in the control.

In your case, you just need to ensure that your trix element has the data attribute:

data-mentions-path="/user/mentions?r"

defined.

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