diff --git .gitignore .gitignore
index cc3f92c79..c5008ece8 100644
--- .gitignore
+++ .gitignore
@@ -17,6 +17,7 @@ logs
 results
 src/vendor
 vendor
+!src/bp-core/js/vendor
 
 node_modules
 npm-debug.log
diff --git Gruntfile.js Gruntfile.js
index 9a184513d..6e3b8a1aa 100644
--- Gruntfile.js
+++ Gruntfile.js
@@ -37,7 +37,8 @@ module.exports = function( grunt ) {
 			'!bp-members/css/blocks/member.css',
 			'!bp-groups/css/blocks/group.css',
 			'!bp-members/css/blocks/members.css',
-			'!bp-groups/css/blocks/groups.css'
+			'!bp-groups/css/blocks/groups.css',
+			'!bp-activity/css/mentions.css'
 		],
 
 		autoprefixer = require('autoprefixer');
@@ -130,6 +131,15 @@ module.exports = function( grunt ) {
 				src: ['bp-core/admin/sass/*.scss'],
 				dest: SOURCE_DIR + 'bp-core/admin/css/'
 			},
+			activity: {
+				cwd: SOURCE_DIR,
+				extDot: 'last',
+				expand: true,
+				ext: '.css',
+				flatten: true,
+				src: ['bp-activity/sass/*.scss'],
+				dest: SOURCE_DIR + 'bp-activity/css/'
+			},
 			members_blocks: {
 				cwd: SOURCE_DIR,
 				extDot: 'last',
diff --git src/bp-activity/bp-activity-cssjs.php src/bp-activity/bp-activity-cssjs.php
index 26748b440..2495272f7 100644
--- src/bp-activity/bp-activity-cssjs.php
+++ src/bp-activity/bp-activity-cssjs.php
@@ -33,7 +33,7 @@ function bp_activity_mentions_script() {
 
 	$min = bp_core_get_minified_asset_suffix();
 
-	wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'jquery', 'jquery-atwho' ), bp_get_version(), true );
+	wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'bp-api-request', 'zurb-tribute' ), bp_get_version(), true );
 	wp_enqueue_style( 'bp-mentions-css', buddypress()->plugin_url . "bp-activity/css/mentions{$min}.css", array(), bp_get_version() );
 
 	wp_style_add_data( 'bp-mentions-css', 'rtl', 'replace' );
@@ -41,9 +41,6 @@ function bp_activity_mentions_script() {
 		wp_style_add_data( 'bp-mentions-css', 'suffix', $min );
 	}
 
-	// If the script has been enqueued, let's attach our mentions TinyMCE init callback.
-	add_filter( 'tiny_mce_before_init', 'bp_add_mentions_on_tinymce_init', 10, 2 );
-
 	/**
 	 * Fires at the end of the Activity Mentions script.
 	 *
@@ -57,20 +54,3 @@ function bp_activity_mentions_script() {
 add_action( 'bp_enqueue_scripts', 'bp_activity_mentions_script' );
 add_action( 'bp_admin_enqueue_scripts', 'bp_activity_mentions_script' );
 
-/**
- * Bind the mentions listener to a wp_editor instance when TinyMCE initializes.
- *
- * @since 2.3.3
- *
- * @param array  $settings   An array with TinyMCE config.
- * @param string $editor_id Unique editor identifier, e.g. 'content'.
- * @return array  $mceInit   An array with TinyMCE config.
- */
-function bp_add_mentions_on_tinymce_init( $settings, $editor_id ) {
-	// We only apply the mentions init to the visual post editor in the WP dashboard.
-	if ( 'content' === $editor_id ) {
-		$settings['init_instance_callback'] = 'window.bp.mentions.tinyMCEinit';
-	}
-
-	return $settings;
-}
diff --git src/bp-activity/css/mentions-rtl.css src/bp-activity/css/mentions-rtl.css
index 3ec124f9f..c18a6d2b1 100644
--- src/bp-activity/css/mentions-rtl.css
+++ src/bp-activity/css/mentions-rtl.css
@@ -1,28 +1,25 @@
-.atwho-view {
+.tribute-container {
 	background: rgba(204, 204, 204, 0.8);
 	border-radius: 2px;
-	border: 1px solid rgb(204, 204, 204);
+	border: 1px solid #cccccc;
 	box-shadow: 0 0 5px rgba(204, 204, 204, 0.25), 0 0 1px #fff;
-	color: #d84800;
+	color: #555;
 	display: none;
-	font-family: sans-serif;
 	margin-top: 18px;
 	position: absolute;
 	top: 0;
-	z-index: 1000; /* >999 for wp-admin */
-}
-.atwho-view {
-	left: 0;
+	z-index: 1000;
+	/* >999 for wp-admin */
 }
 
-.atwho-view ul {
+.tribute-container ul {
 	background: #fff;
 	list-style: none;
 	margin: auto;
 	padding: 0;
 }
 
-.atwho-view ul li {
+.tribute-container ul li {
 	border-bottom: 1px solid #efefef;
 	box-sizing: content-box;
 	cursor: pointer;
@@ -35,71 +32,42 @@
 	padding: 5px 10px;
 }
 
-.atwho-view img {
-	border-radius: 2px;
-	float: left;
-	height: 20px;
-	margin-top: 0;
-	width: 20px;
+.tribute-container ul li.highlight {
+	background: #ddd;
 }
 
-.atwho-view strong {
-	background: #efefef;
-	font-weight: 700;
+.tribute-container ul li .username {
+	color: #555;
 }
 
-.atwho-view .username strong {
-	color: #d54e21;
+.tribute-container ul li .username span {
+	font-weight: 600;
 }
 
-.atwho-view small {
-	color: #aaa;
-	float: left;
+.tribute-container ul li small {
 	font-size: smaller;
+	float: left;
 	font-weight: 400;
 	margin: 0 40px 0 10px;
 }
 
-.atwho-view .cur {
-	background: rgba(239, 239, 239, 0.5);
+.tribute-container ul li small span {
+	font-weight: 600;
 }
 
-@media (max-width: 900px) {
-
-	.atwho-view img {
-		float: right;
-		margin: 0 0 0 10px;
-	}
+.tribute-container ul li .no-match {
+	cursor: default;
 }
 
-@media (max-width: 400px) {
-
-	.atwho-view ul li {
-		font-size: 16px;
-		line-height: 23px;
-		padding: 13px;
-	}
-
-	.atwho-view ul li img {
-		height: 30px;
-		margin-top: -5px;
-		width: 30px;
-	}
-
-	.atwho-view {
-		border-radius: 0;
-		right: 0 !important;
-		width: 100%;
-	}
-
-	.atwho-view ul li .username {
-		display: inline-block;
-		margin: -10px 0 0 0;
-		padding: 10px 0;
-	}
+.tribute-container img {
+	border-radius: 2px;
+	float: left;
+	height: 20px;
+	margin-top: 0;
+	width: 20px;
+	margin-left: 5px;
+}
 
-	.atwho-view ul li small {
-		display: inline-block;
-		margin-right: 20px;
-	}
+.tribute-container .menu-highlighted {
+	font-weight: 700;
 }
diff --git src/bp-activity/css/mentions.css src/bp-activity/css/mentions.css
index 7773c75ec..7cd874e6f 100644
--- src/bp-activity/css/mentions.css
+++ src/bp-activity/css/mentions.css
@@ -1,30 +1,25 @@
-.atwho-view {
+.tribute-container {
 	background: rgba(204, 204, 204, 0.8);
 	border-radius: 2px;
-	border: 1px solid rgb(204, 204, 204);
+	border: 1px solid #cccccc;
 	box-shadow: 0 0 5px rgba(204, 204, 204, 0.25), 0 0 1px #fff;
-	color: #d84800;
+	color: #555;
 	display: none;
-	font-family: sans-serif;
 	margin-top: 18px;
 	position: absolute;
 	top: 0;
-	z-index: 1000; /* >999 for wp-admin */
+	z-index: 1000;
+	/* >999 for wp-admin */
 }
 
-/* rtl:ignore */
-.atwho-view {
-	left: 0;
-}
-
-.atwho-view ul {
+.tribute-container ul {
 	background: #fff;
 	list-style: none;
 	margin: auto;
 	padding: 0;
 }
 
-.atwho-view ul li {
+.tribute-container ul li {
 	border-bottom: 1px solid #efefef;
 	box-sizing: content-box;
 	cursor: pointer;
@@ -37,71 +32,42 @@
 	padding: 5px 10px;
 }
 
-.atwho-view img {
-	border-radius: 2px;
-	float: right;
-	height: 20px;
-	margin-top: 0;
-	width: 20px;
+.tribute-container ul li.highlight {
+	background: #ddd;
 }
 
-.atwho-view strong {
-	background: #efefef;
-	font-weight: 700;
+.tribute-container ul li .username {
+	color: #555;
 }
 
-.atwho-view .username strong {
-	color: #d54e21;
+.tribute-container ul li .username span {
+	font-weight: 600;
 }
 
-.atwho-view small {
-	color: #aaa;
-	float: right;
+.tribute-container ul li small {
 	font-size: smaller;
+	float: right;
 	font-weight: 400;
 	margin: 0 10px 0 40px;
 }
 
-.atwho-view .cur {
-	background: rgba(239, 239, 239, 0.5);
+.tribute-container ul li small span {
+	font-weight: 600;
 }
 
-@media (max-width: 900px) {
-
-	.atwho-view img {
-		float: left;
-		margin: 0 10px 0 0;
-	}
+.tribute-container ul li .no-match {
+	cursor: default;
 }
 
-@media (max-width: 400px) {
-
-	.atwho-view ul li {
-		font-size: 16px;
-		line-height: 23px;
-		padding: 13px;
-	}
-
-	.atwho-view ul li img {
-		height: 30px;
-		margin-top: -5px;
-		width: 30px;
-	}
-
-	.atwho-view {
-		border-radius: 0;
-		left: 0 !important;
-		width: 100%;
-	}
-
-	.atwho-view ul li .username {
-		display: inline-block;
-		margin: -10px 0 0 0;
-		padding: 10px 0;
-	}
+.tribute-container img {
+	border-radius: 2px;
+	float: right;
+	height: 20px;
+	margin-top: 0;
+	width: 20px;
+	margin-right: 5px;
+}
 
-	.atwho-view ul li small {
-		display: inline-block;
-		margin-left: 20px;
-	}
+.tribute-container .menu-highlighted {
+	font-weight: 700;
 }
diff --git src/bp-activity/js/mentions.js src/bp-activity/js/mentions.js
index 3fbdd9d0c..c4e6d9a99 100644
--- src/bp-activity/js/mentions.js
+++ src/bp-activity/js/mentions.js
@@ -1,8 +1,8 @@
-/* global bp */
+/* global bp, Tribute */
 
 window.bp = window.bp || {};
 
-( function( bp, $, undefined ) {
+( function( bp, $ ) {
 	var mentionsQueryCache = [],
 		mentionsItem;
 
@@ -16,241 +16,100 @@ window.bp = window.bp || {};
 	/**
 	 * Adds BuddyPress @mentions to form inputs.
 	 *
-	 * @param {array|object} options If array, becomes the suggestions' data source. If object, passed as config to $.atwho().
+	 * @param {array} defaultList If array, becomes the suggestions' default data source.
 	 * @since 2.1.0
 	 */
-	$.fn.bp_mentions = function( options ) {
-		if ( Array.isArray( options ) ) {
-			options = { data: options };
-		}
-
-		/**
-		 * Default options for at.js; see https://github.com/ichord/At.js/.
-		 */
-		var suggestionsDefaults = {
-			delay:             200,
-			hideWithoutSuffix: true,
-			insertTpl:         '@${ID}',
-			limit:             10,
-			startWithSpace:    false,
-			suffix:            '',
-
-			callbacks: {
-				/**
-				 * Custom filter to only match the start of spaced words.
-				 * Based on the core/default one.
-				 *
-				 * @param {string} query
-				 * @param {array} data
-				 * @param {string} search_key
-				 * @return {array}
-				 * @since 2.1.0
-				 */
-				filter: function( query, data, search_key ) {
-					var item, _i, _len, _results = [],
-					regxp = new RegExp( '^' + query + '| ' + query, 'ig' ); // start of string, or preceded by a space.
-
-					for ( _i = 0, _len = data.length; _i < _len; _i++ ) {
-						item = data[ _i ];
-						if ( item[ search_key ].toLowerCase().match( regxp ) ) {
-							_results.push( item );
-						}
-					}
-
-					return _results;
-				},
-
-				/**
-				 * Removes some spaces around highlighted string and tweaks regex to allow spaces
-				 * (to match display_name). Based on the core default.
-				 *
-				 * @param {unknown} li
-				 * @param {string} query
-				 * @return {string}
-				 * @since 2.1.0
-				 */
-				highlighter: function( li, query ) {
-					if ( ! query ) {
-						return li;
-					}
-
-					var regexp = new RegExp( '>(\\s*|[\\w\\s]*)(' + this.at.replace( '+', '\\+') + '?' + query.replace( '+', '\\+' ) + ')([\\w ]*)\\s*<', 'ig' );
-					return li.replace( regexp, function( str, $1, $2, $3 ) {
-						return '>' + $1 + '<strong>' + $2 + '</strong>' + $3 + '<';
-					});
-				},
-
-				/**
-				 * Reposition the suggestion list dynamically.
-				 *
-				 * @param {unknown} offset
-				 * @since 2.1.0
-				 */
-				before_reposition: function( offset ) {
-					// get the iframe, if any, already applied with atwho.js library.
-					var caret,
-							line,
-							iframeOffset,
-							move,
-							$view = $( '#atwho-ground-' + this.id + ' .atwho-view' ),
-							$body = $( 'body' ),
-							atwhoDataValue = this.$inputor.data( 'atwho' );
-
-					if ( 'undefined' !== atwhoDataValue && 'undefined' !== atwhoDataValue.iframe && null !== atwhoDataValue.iframe ) {
-						caret = this.$inputor.caret( 'offset', { iframe: atwhoDataValue.iframe } );
-						// Caret.js no longer calculates iframe caret position from the window (it's now just within the iframe).
-						// We need to get the iframe offset from the window and merge that into our object.
-						iframeOffset = $( atwhoDataValue.iframe ).offset();
-						if ( 'undefined' !== iframeOffset ) {
-							caret.left += iframeOffset.left;
-							caret.top  += iframeOffset.top;
-						}
-					} else {
-						caret = this.$inputor.caret( 'offset' );
-					}
-
-					// If the caret is past horizontal half, then flip it, yo.
-					if ( caret.left > ( $body.width() / 2 ) ) {
-						$view.addClass( 'right' );
-						move = caret.left - offset.left - this.view.$el.width();
-					} else {
-						$view.removeClass( 'right' );
-						move = caret.left - offset.left + 1;
-					}
-
-					// If we're on a small screen, scroll to caret.
-					if ( $body.width() <= 400 ) {
-						$( document ).scrollTop( caret.top - 6 );
-					}
-
-					// New position is under the caret (never above) and positioned to follow.
-					// Dynamic sizing based on the input area (remove 'px' from end).
-					line = parseInt( this.$inputor.css( 'line-height' ).substr( 0, this.$inputor.css( 'line-height' ).length - 2 ), 10 );
-					if ( !line || line < 5 ) { // Sanity check, and catch no line-height.
-						line = 19;
-					}
-
-					offset.top   = caret.top + line;
-					offset.left += move;
-				},
-
-				/**
-				 * Override default behavior which inserts junk tags in the WordPress Visual editor.
-				 *
-				 * @param {unknown} $inputor Element which we're inserting content into.
-				 * @param {string} content The content that will be inserted.
-				 * @param {string} suffix Applied to the end of the content string.
-				 * @return {string}
-				 * @since 2.1.0
-				 */
-				inserting_wrapper: function( $inputor, content, suffix ) {
-					return '' + content + suffix;
-				}
+	$.fn.bp_mentions = function( defaultList ) {
+		var debouncer = function( func, wait ) {
+			var timeout;
+			return function() {
+				var context = this;
+				var args = arguments;
+
+				var callFunction = function() {
+				   func.apply( context, args );
+				};
+
+				clearTimeout( timeout );
+				timeout = setTimeout( callFunction, wait );
+			};
+		};
+
+		var remoteSearch = function( text, cb ) {
+			/**
+			* Immediately show the pre-created friends list, if it's populated,
+			* and the user has hesitated after hitting @ (no search text provided).
+			*/
+			if ( text.length === 0 && Array.isArray( defaultList ) && defaultList.length > 0 ) {
+				cb( defaultList ) ;
+				return;
 			}
-		},
 
-		/**
-		 * Default options for our @mentions; see https://github.com/ichord/At.js/.
-		 */
-		mentionsDefaults = {
-			callbacks: {
-				/**
-				 * If there are no matches for the query in this.data, then query BuddyPress.
-				 *
-				 * @param {string} query Partial @mention to search for.
-				 * @param {function} render_view Render page callback function.
-				 * @since 2.1.0
-				 * @since 3.0.0. Renamed from "remote_filter" for at.js v1.5.4 support.
-				 */
-				remoteFilter: function( query, render_view ) {
-					var self = $( this ),
-						params = {};
-
-					mentionsItem = mentionsQueryCache[ query ];
-					if ( typeof mentionsItem === 'object' ) {
-						render_view( mentionsItem );
-						return;
-					}
-
-					if ( self.xhr ) {
-						self.xhr.abort();
-					}
-
-					params = { 'action': 'bp_get_suggestions', 'term': query, 'type': 'members' };
+			mentionsItem = mentionsQueryCache[ text ];
+			if ( typeof mentionsItem === 'object' ) {
+				cb( mentionsItem );
+				return;
+			}
 
-					if ( $.isNumeric( this.$inputor.data( 'suggestions-group-id' ) ) ) {
-						params['group-id'] = parseInt( this.$inputor.data( 'suggestions-group-id' ), 10 );
+			return bp.apiRequest( {
+				path: 'buddypress/v1/members/?search=' + text,
+				type: 'GET'
+			} ).done( function( data ) {
+				var retval = $.map( data,
+					/**
+					 * Create a composite index to determine ordering of results;
+					 * nicename matches will appear on top.
+					 *
+					 * @param {object} suggestion A suggestion's original data.
+					 * @return {object} A suggestion's new data.
+					 * @since 8.0.0
+					 */
+					function( suggestion ) {
+						suggestion.search = suggestion.search || suggestion.mention_name + '|' + suggestion.name;
+						return suggestion;
 					}
-
-					self.xhr = $.getJSON( ajaxurl, params )
-						/**
-						 * Success callback for the @suggestions lookup.
-						 *
-						 * @param {object} response Details of users matching the query.
-						 * @since 2.1.0
-						 */
-						.done(function( response ) {
-							if ( ! response.success ) {
-								return;
-							}
-
-							var data = $.map( response.data,
-								/**
-								 * Create a composite index to determine ordering of results;
-								 * nicename matches will appear on top.
-								 *
-								 * @param {array} suggestion A suggestion's original data.
-								 * @return {array} A suggestion's new data.
-								 * @since 2.1.0
-								 */
-								function( suggestion ) {
-									suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name;
-									return suggestion;
-								}
-							);
-
-							mentionsQueryCache[ query ] = data;
-							render_view( data );
-						});
+				);
+
+				mentionsQueryCache[ text ] = retval;
+				cb( retval );
+			} ).fail( function( error ) {
+				return error;
+			} );
+		};
+
+		var tributeParams = {
+			values: debouncer( function( text, cb ) {
+				var users = function( users ) { return cb( users ); };
+
+				remoteSearch( text, users );
+			}, 250),
+			lookup: 'search',
+			fillAttr: 'mention_name',
+			menuItemTemplate: function( user ) {
+				var template = '', item = user.string.split( '|' );
+
+				if ( user.original.avatar_urls && user.original.avatar_urls.thumb ) {
+					template = '<img src="' + user.original.avatar_urls.thumb + '" />';
 				}
-			},
 
-			data: $.map( options.data,
-				/**
-				 * Create a composite index to search against of nicename + display name.
-				 * This will also determine ordering of results, so nicename matches will appear on top.
-				 *
-				 * @param {array} suggestion A suggestion's original data.
-				 * @return {array} A suggestion's new data.
-				 * @since 2.1.0
-				 */
-				function( suggestion ) {
-					suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name;
-					return suggestion;
-				}
-			),
+				template += '<span class="username">@' + item[0] + '</span><small>' + item[1] + '</small>';
 
-			at:         '@',
-			searchKey:  'search',
-			displayTpl: '<li data-value="@${ID}"><img src="${image}" alt="" /><span class="username">@${ID}</span><small>${name}</small></li>'
-		},
+				return template;
+			}
+		};
+
+		var tribute = new Tribute( tributeParams );
 
-		opts = $.extend( true, {}, suggestionsDefaults, mentionsDefaults, options );
-		return $.fn.atwho.call( this, opts );
+		$( this ).each( function( i, input ) {
+			if ( ! $( input ).attr( 'data-tribute' ) ) {
+				tribute.attach( $( input ) );
+			}
+		} );
 	};
 
-	$( document ).ready(function() {
-		// Activity/reply, post comments, dashboard post 'text' editor.
-		$( '.bp-suggestions, #comments form textarea, .wp-editor-area' ).bp_mentions( bp.mentions.users );
-	});
+	$( function() {
+		// Activity/reply, post comments, bp-nouveau messages composer.
+		$( '.bp-suggestions, #comments form textarea, .bp-messages-content .send-to-input' ).bp_mentions( bp.mentions.users );
+	} );
 
-	bp.mentions.tinyMCEinit = function() {
-		if ( typeof window.tinyMCE === 'undefined' || window.tinyMCE.activeEditor === null || typeof window.tinyMCE.activeEditor === 'undefined' ) {
-			return;
-		} else {
-			$( window.tinyMCE.activeEditor.contentDocument.activeElement )
-				.atwho( 'setIframe', $( '.wp-editor-wrap iframe' )[0] )
-				.bp_mentions( bp.mentions.users );
-		}
-	};
-})( bp, jQuery );
+} )( bp, jQuery );
diff --git src/bp-activity/sass/mentions.scss src/bp-activity/sass/mentions.scss
new file mode 100644
index 000000000..46364b001
--- /dev/null
+++ src/bp-activity/sass/mentions.scss
@@ -0,0 +1,72 @@
+.tribute-container {
+	background: rgba(204, 204, 204, 0.8);
+	border-radius: 2px;
+	border: 1px solid rgb(204, 204, 204);
+	box-shadow: 0 0 5px rgba(204, 204, 204, 0.25), 0 0 1px #fff;
+	color: #555;
+	display: none;
+	margin-top: 18px;
+	position: absolute;
+	top: 0;
+	z-index: 1000; /* >999 for wp-admin */
+
+	ul {
+		background: #fff;
+		list-style: none;
+		margin: auto;
+		padding: 0;
+
+		li {
+			border-bottom: 1px solid #efefef;
+			box-sizing: content-box;
+			cursor: pointer;
+			display: block;
+			font-size: 14px;
+			height: 20px;
+			line-height: 20px;
+			margin: 0;
+			overflow: hidden;
+			padding: 5px 10px;
+
+			&.highlight {
+				background: #ddd;
+			}
+
+			.username {
+				color: #555;
+
+				span {
+					font-weight: 600;
+				}
+			}
+
+			small {
+				font-size: smaller;
+				float: right;
+				font-weight: 400;
+				margin: 0 10px 0 40px;
+
+				span {
+					font-weight: 600;
+				}
+			}
+
+			.no-match {
+				cursor: default;
+			}
+		}
+	}
+
+	img {
+		border-radius: 2px;
+		float: right;
+		height: 20px;
+		margin-top: 0;
+		width: 20px;
+		margin-right: 5px;
+	}
+
+	.menu-highlighted {
+		font-weight: 700;
+	}
+}
diff --git src/bp-core/bp-core-cssjs.php src/bp-core/bp-core-cssjs.php
index 5e5691f51..3d9f06022 100644
--- src/bp-core/bp-core-cssjs.php
+++ src/bp-core/bp-core-cssjs.php
@@ -70,10 +70,6 @@ function bp_core_register_common_scripts() {
 		'bp-jquery-cookie'  => array( 'file' => "{$url}vendor/jquery-cookie{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => false ),
 		'bp-jquery-scroll-to' => array( 'file' => "{$url}vendor/jquery-scroll-to{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => false ),
 
-		// Version 2.1.
-		'jquery-caret' => array( 'file' => "{$url}vendor/jquery.caret{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => true ),
-		'jquery-atwho' => array( 'file' => "{$url}vendor/jquery.atwho{$min}.js", 'dependencies' => array( 'jquery', 'jquery-caret' ), 'footer' => true ),
-
 		// Version 2.3.
 		'bp-plupload' => array( 'file' => "{$url}bp-plupload{$min}.js", 'dependencies' => array( 'plupload', 'jquery', 'json2', 'wp-backbone' ), 'footer' => true ),
 		'bp-avatar'   => array( 'file' => "{$url}avatar{$min}.js", 'dependencies' => array( 'jcrop' ), 'footer' => true ),
@@ -85,6 +81,9 @@ function bp_core_register_common_scripts() {
 		// Version 2.7.
 		'bp-moment'    => array( 'file' => "{$url}vendor/moment-js/moment{$min}.js", 'dependencies' => array(), 'footer' => true ),
 		'bp-livestamp' => array( 'file' => "{$url}vendor/livestamp{$min}.js", 'dependencies' => array( 'jquery', 'bp-moment' ), 'footer' => true ),
+
+		// Version 8
+		'zurb-tribute' => array( 'file' => "{$url}vendor/tribute{$min}.js", 'dependencies' => array(), 'footer' => true ),
 	);
 
 	// Version 2.7 - Add Moment.js locale to our $scripts array if we found one.
diff --git src/bp-core/js/vendor/jquery.atwho.js src/bp-core/js/vendor/jquery.atwho.js
deleted file mode 100755
index b0685d2b1..000000000
--- src/bp-core/js/vendor/jquery.atwho.js
+++ /dev/null
@@ -1,1212 +0,0 @@
-/**
- * at.js - 1.5.4
- * Copyright (c) 2017 chord.luo <chord.luo@gmail.com>;
- * Homepage: http://ichord.github.com/At.js
- * License: MIT
- */
-(function (root, factory) {
-  if (typeof define === 'function' && define.amd) {
-    // AMD. Register as an anonymous module unless amdModuleId is set.
-    define(["jquery"], function (a0) {
-      return (factory(a0));
-    });
-  } else if (typeof exports === 'object') {
-    // Node. Does not work with strict CommonJS, but
-    // only CommonJS-like environments that support module.exports,
-    // like Node.
-    module.exports = factory(require("jquery"));
-  } else {
-    factory(jQuery);
-  }
-}(this, function ($) {
-var DEFAULT_CALLBACKS, KEY_CODE;
-
-KEY_CODE = {
-  ESC: 27,
-  TAB: 9,
-  ENTER: 13,
-  CTRL: 17,
-  A: 65,
-  P: 80,
-  N: 78,
-  LEFT: 37,
-  UP: 38,
-  RIGHT: 39,
-  DOWN: 40,
-  BACKSPACE: 8,
-  SPACE: 32
-};
-
-DEFAULT_CALLBACKS = {
-  beforeSave: function(data) {
-    return Controller.arrayToDefaultHash(data);
-  },
-  matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
-    var _a, _y, match, regexp, space;
-    flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
-    if (should_startWithSpace) {
-      flag = '(?:^|\\s)' + flag;
-    }
-    _a = decodeURI("%C3%80");
-    _y = decodeURI("%C3%BF");
-    space = acceptSpaceBar ? "\ " : "";
-    regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
-    match = regexp.exec(subtext);
-    if (match) {
-      return match[2] || match[1];
-    } else {
-      return null;
-    }
-  },
-  filter: function(query, data, searchKey) {
-    var _results, i, item, len;
-    _results = [];
-    for (i = 0, len = data.length; i < len; i++) {
-      item = data[i];
-      if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) {
-        _results.push(item);
-      }
-    }
-    return _results;
-  },
-  remoteFilter: null,
-  sorter: function(query, items, searchKey) {
-    var _results, i, item, len;
-    if (!query) {
-      return items;
-    }
-    _results = [];
-    for (i = 0, len = items.length; i < len; i++) {
-      item = items[i];
-      item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase());
-      if (item.atwho_order > -1) {
-        _results.push(item);
-      }
-    }
-    return _results.sort(function(a, b) {
-      return a.atwho_order - b.atwho_order;
-    });
-  },
-  tplEval: function(tpl, map) {
-    var error, error1, template;
-    template = tpl;
-    try {
-      if (typeof tpl !== 'string') {
-        template = tpl(map);
-      }
-      return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
-        return map[key];
-      });
-    } catch (error1) {
-      error = error1;
-      return "";
-    }
-  },
-  highlighter: function(li, query) {
-    var regexp;
-    if (!query) {
-      return li;
-    }
-    regexp = new RegExp(">\\s*([^\<]*?)(" + query.replace("+", "\\+") + ")([^\<]*)\\s*<", 'ig');
-    return li.replace(regexp, function(str, $1, $2, $3) {
-      return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
-    });
-  },
-  beforeInsert: function(value, $li, e) {
-    return value;
-  },
-  beforeReposition: function(offset) {
-    return offset;
-  },
-  afterMatchFailed: function(at, el) {}
-};
-
-var App;
-
-App = (function() {
-  function App(inputor) {
-    this.currentFlag = null;
-    this.controllers = {};
-    this.aliasMaps = {};
-    this.$inputor = $(inputor);
-    this.setupRootElement();
-    this.listen();
-  }
-
-  App.prototype.createContainer = function(doc) {
-    var ref;
-    if ((ref = this.$el) != null) {
-      ref.remove();
-    }
-    return $(doc.body).append(this.$el = $("<div class='atwho-container'></div>"));
-  };
-
-  App.prototype.setupRootElement = function(iframe, asRoot) {
-    var error, error1;
-    if (asRoot == null) {
-      asRoot = false;
-    }
-    if (iframe) {
-      this.window = iframe.contentWindow;
-      this.document = iframe.contentDocument || this.window.document;
-      this.iframe = iframe;
-    } else {
-      this.document = this.$inputor[0].ownerDocument;
-      this.window = this.document.defaultView || this.document.parentWindow;
-      try {
-        this.iframe = this.window.frameElement;
-      } catch (error1) {
-        error = error1;
-        this.iframe = null;
-        if ($.fn.atwho.debug) {
-          throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
-        }
-      }
-    }
-    return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
-  };
-
-  App.prototype.controller = function(at) {
-    var c, current, currentFlag, ref;
-    if (this.aliasMaps[at]) {
-      current = this.controllers[this.aliasMaps[at]];
-    } else {
-      ref = this.controllers;
-      for (currentFlag in ref) {
-        c = ref[currentFlag];
-        if (currentFlag === at) {
-          current = c;
-          break;
-        }
-      }
-    }
-    if (current) {
-      return current;
-    } else {
-      return this.controllers[this.currentFlag];
-    }
-  };
-
-  App.prototype.setContextFor = function(at) {
-    this.currentFlag = at;
-    return this;
-  };
-
-  App.prototype.reg = function(flag, setting) {
-    var base, controller;
-    controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
-    if (setting.alias) {
-      this.aliasMaps[setting.alias] = flag;
-    }
-    controller.init(setting);
-    return this;
-  };
-
-  App.prototype.listen = function() {
-    return this.$inputor.on('compositionstart', (function(_this) {
-      return function(e) {
-        var ref;
-        if ((ref = _this.controller()) != null) {
-          ref.view.hide();
-        }
-        _this.isComposing = true;
-        return null;
-      };
-    })(this)).on('compositionend', (function(_this) {
-      return function(e) {
-        _this.isComposing = false;
-        setTimeout(function(e) {
-          return _this.dispatch(e);
-        });
-        return null;
-      };
-    })(this)).on('keyup.atwhoInner', (function(_this) {
-      return function(e) {
-        return _this.onKeyup(e);
-      };
-    })(this)).on('keydown.atwhoInner', (function(_this) {
-      return function(e) {
-        return _this.onKeydown(e);
-      };
-    })(this)).on('blur.atwhoInner', (function(_this) {
-      return function(e) {
-        var c;
-        if (c = _this.controller()) {
-          c.expectedQueryCBId = null;
-          return c.view.hide(e, c.getOpt("displayTimeout"));
-        }
-      };
-    })(this)).on('click.atwhoInner', (function(_this) {
-      return function(e) {
-        return _this.dispatch(e);
-      };
-    })(this)).on('scroll.atwhoInner', (function(_this) {
-      return function() {
-        var lastScrollTop;
-        lastScrollTop = _this.$inputor.scrollTop();
-        return function(e) {
-          var currentScrollTop, ref;
-          currentScrollTop = e.target.scrollTop;
-          if (lastScrollTop !== currentScrollTop) {
-            if ((ref = _this.controller()) != null) {
-              ref.view.hide(e);
-            }
-          }
-          lastScrollTop = currentScrollTop;
-          return true;
-        };
-      };
-    })(this)());
-  };
-
-  App.prototype.shutdown = function() {
-    var _, c, ref;
-    ref = this.controllers;
-    for (_ in ref) {
-      c = ref[_];
-      c.destroy();
-      delete this.controllers[_];
-    }
-    this.$inputor.off('.atwhoInner');
-    return this.$el.remove();
-  };
-
-  App.prototype.dispatch = function(e) {
-    var _, c, ref, results;
-    ref = this.controllers;
-    results = [];
-    for (_ in ref) {
-      c = ref[_];
-      results.push(c.lookUp(e));
-    }
-    return results;
-  };
-
-  App.prototype.onKeyup = function(e) {
-    var ref;
-    switch (e.keyCode) {
-      case KEY_CODE.ESC:
-        e.preventDefault();
-        if ((ref = this.controller()) != null) {
-          ref.view.hide();
-        }
-        break;
-      case KEY_CODE.DOWN:
-      case KEY_CODE.UP:
-      case KEY_CODE.CTRL:
-      case KEY_CODE.ENTER:
-        $.noop();
-        break;
-      case KEY_CODE.P:
-      case KEY_CODE.N:
-        if (!e.ctrlKey) {
-          this.dispatch(e);
-        }
-        break;
-      default:
-        this.dispatch(e);
-    }
-  };
-
-  App.prototype.onKeydown = function(e) {
-    var ref, view;
-    view = (ref = this.controller()) != null ? ref.view : void 0;
-    if (!(view && view.visible())) {
-      return;
-    }
-    switch (e.keyCode) {
-      case KEY_CODE.ESC:
-        e.preventDefault();
-        view.hide(e);
-        break;
-      case KEY_CODE.UP:
-        e.preventDefault();
-        view.prev();
-        break;
-      case KEY_CODE.DOWN:
-        e.preventDefault();
-        view.next();
-        break;
-      case KEY_CODE.P:
-        if (!e.ctrlKey) {
-          return;
-        }
-        e.preventDefault();
-        view.prev();
-        break;
-      case KEY_CODE.N:
-        if (!e.ctrlKey) {
-          return;
-        }
-        e.preventDefault();
-        view.next();
-        break;
-      case KEY_CODE.TAB:
-      case KEY_CODE.ENTER:
-      case KEY_CODE.SPACE:
-        if (!view.visible()) {
-          return;
-        }
-        if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) {
-          return;
-        }
-        if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) {
-          return;
-        }
-        if (view.highlighted()) {
-          e.preventDefault();
-          view.choose(e);
-        } else {
-          view.hide(e);
-        }
-        break;
-      default:
-        $.noop();
-    }
-  };
-
-  return App;
-
-})();
-
-var Controller,
-  slice = [].slice;
-
-Controller = (function() {
-  Controller.prototype.uid = function() {
-    return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
-  };
-
-  function Controller(app, at1) {
-    this.app = app;
-    this.at = at1;
-    this.$inputor = this.app.$inputor;
-    this.id = this.$inputor[0].id || this.uid();
-    this.expectedQueryCBId = null;
-    this.setting = null;
-    this.query = null;
-    this.pos = 0;
-    this.range = null;
-    if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
-      this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
-    }
-    this.model = new Model(this);
-    this.view = new View(this);
-  }
-
-  Controller.prototype.init = function(setting) {
-    this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
-    this.view.init();
-    return this.model.reload(this.setting.data);
-  };
-
-  Controller.prototype.destroy = function() {
-    this.trigger('beforeDestroy');
-    this.model.destroy();
-    this.view.destroy();
-    return this.$el.remove();
-  };
-
-  Controller.prototype.callDefault = function() {
-    var args, error, error1, funcName;
-    funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
-    try {
-      return DEFAULT_CALLBACKS[funcName].apply(this, args);
-    } catch (error1) {
-      error = error1;
-      return $.error(error + " Or maybe At.js doesn't have function " + funcName);
-    }
-  };
-
-  Controller.prototype.trigger = function(name, data) {
-    var alias, eventName;
-    if (data == null) {
-      data = [];
-    }
-    data.push(this);
-    alias = this.getOpt('alias');
-    eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
-    return this.$inputor.trigger(eventName, data);
-  };
-
-  Controller.prototype.callbacks = function(funcName) {
-    return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
-  };
-
-  Controller.prototype.getOpt = function(at, default_value) {
-    var e, error1;
-    try {
-      return this.setting[at];
-    } catch (error1) {
-      e = error1;
-      return null;
-    }
-  };
-
-  Controller.prototype.insertContentFor = function($li) {
-    var data, tpl;
-    tpl = this.getOpt('insertTpl');
-    data = $.extend({}, $li.data('item-data'), {
-      'atwho-at': this.at
-    });
-    return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
-  };
-
-  Controller.prototype.renderView = function(data) {
-    var searchKey;
-    searchKey = this.getOpt("searchKey");
-    data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey);
-    return this.view.render(data.slice(0, this.getOpt('limit')));
-  };
-
-  Controller.arrayToDefaultHash = function(data) {
-    var i, item, len, results;
-    if (!$.isArray(data)) {
-      return data;
-    }
-    results = [];
-    for (i = 0, len = data.length; i < len; i++) {
-      item = data[i];
-      if ($.isPlainObject(item)) {
-        results.push(item);
-      } else {
-        results.push({
-          name: item
-        });
-      }
-    }
-    return results;
-  };
-
-  Controller.prototype.lookUp = function(e) {
-    var query, wait;
-    if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
-      return;
-    }
-    if (this.getOpt('suspendOnComposing') && this.app.isComposing) {
-      return;
-    }
-    query = this.catchQuery(e);
-    if (!query) {
-      this.expectedQueryCBId = null;
-      return query;
-    }
-    this.app.setContextFor(this.at);
-    if (wait = this.getOpt('delay')) {
-      this._delayLookUp(query, wait);
-    } else {
-      this._lookUp(query);
-    }
-    return query;
-  };
-
-  Controller.prototype._delayLookUp = function(query, wait) {
-    var now, remaining;
-    now = Date.now ? Date.now() : new Date().getTime();
-    this.previousCallTime || (this.previousCallTime = now);
-    remaining = wait - (now - this.previousCallTime);
-    if ((0 < remaining && remaining < wait)) {
-      this.previousCallTime = now;
-      this._stopDelayedCall();
-      return this.delayedCallTimeout = setTimeout((function(_this) {
-        return function() {
-          _this.previousCallTime = 0;
-          _this.delayedCallTimeout = null;
-          return _this._lookUp(query);
-        };
-      })(this), wait);
-    } else {
-      this._stopDelayedCall();
-      if (this.previousCallTime !== now) {
-        this.previousCallTime = 0;
-      }
-      return this._lookUp(query);
-    }
-  };
-
-  Controller.prototype._stopDelayedCall = function() {
-    if (this.delayedCallTimeout) {
-      clearTimeout(this.delayedCallTimeout);
-      return this.delayedCallTimeout = null;
-    }
-  };
-
-  Controller.prototype._generateQueryCBId = function() {
-    return {};
-  };
-
-  Controller.prototype._lookUp = function(query) {
-    var _callback;
-    _callback = function(queryCBId, data) {
-      if (queryCBId !== this.expectedQueryCBId) {
-        return;
-      }
-      if (data && data.length > 0) {
-        return this.renderView(this.constructor.arrayToDefaultHash(data));
-      } else {
-        return this.view.hide();
-      }
-    };
-    this.expectedQueryCBId = this._generateQueryCBId();
-    return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
-  };
-
-  return Controller;
-
-})();
-
-var TextareaController,
-  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
-  hasProp = {}.hasOwnProperty;
-
-TextareaController = (function(superClass) {
-  extend(TextareaController, superClass);
-
-  function TextareaController() {
-    return TextareaController.__super__.constructor.apply(this, arguments);
-  }
-
-  TextareaController.prototype.catchQuery = function() {
-    var caretPos, content, end, isString, query, start, subtext;
-    content = this.$inputor.val();
-    caretPos = this.$inputor.caret('pos', {
-      iframe: this.app.iframe
-    });
-    subtext = content.slice(0, caretPos);
-    query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
-    isString = typeof query === 'string';
-    if (isString && query.length < this.getOpt('minLen', 0)) {
-      return;
-    }
-    if (isString && query.length <= this.getOpt('maxLen', 20)) {
-      start = caretPos - query.length;
-      end = start + query.length;
-      this.pos = start;
-      query = {
-        'text': query,
-        'headPos': start,
-        'endPos': end
-      };
-      this.trigger("matched", [this.at, query.text]);
-    } else {
-      query = null;
-      this.view.hide();
-    }
-    return this.query = query;
-  };
-
-  TextareaController.prototype.rect = function() {
-    var c, iframeOffset, scaleBottom;
-    if (!(c = this.$inputor.caret('offset', this.pos - 1, {
-      iframe: this.app.iframe
-    }))) {
-      return;
-    }
-    if (this.app.iframe && !this.app.iframeAsRoot) {
-      iframeOffset = $(this.app.iframe).offset();
-      c.left += iframeOffset.left;
-      c.top += iframeOffset.top;
-    }
-    scaleBottom = this.app.document.selection ? 0 : 2;
-    return {
-      left: c.left,
-      top: c.top,
-      bottom: c.top + c.height + scaleBottom
-    };
-  };
-
-  TextareaController.prototype.insert = function(content, $li) {
-    var $inputor, source, startStr, suffix, text;
-    $inputor = this.$inputor;
-    source = $inputor.val();
-    startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
-    suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
-    content += suffix;
-    text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
-    $inputor.val(text);
-    $inputor.caret('pos', startStr.length + content.length, {
-      iframe: this.app.iframe
-    });
-    if (!$inputor.is(':focus')) {
-      $inputor.focus();
-    }
-    return $inputor.change();
-  };
-
-  return TextareaController;
-
-})(Controller);
-
-var EditableController,
-  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
-  hasProp = {}.hasOwnProperty;
-
-EditableController = (function(superClass) {
-  extend(EditableController, superClass);
-
-  function EditableController() {
-    return EditableController.__super__.constructor.apply(this, arguments);
-  }
-
-  EditableController.prototype._getRange = function() {
-    var sel;
-    sel = this.app.window.getSelection();
-    if (sel.rangeCount > 0) {
-      return sel.getRangeAt(0);
-    }
-  };
-
-  EditableController.prototype._setRange = function(position, node, range) {
-    if (range == null) {
-      range = this._getRange();
-    }
-    if (!(range && node)) {
-      return;
-    }
-    node = $(node)[0];
-    if (position === 'after') {
-      range.setEndAfter(node);
-      range.setStartAfter(node);
-    } else {
-      range.setEndBefore(node);
-      range.setStartBefore(node);
-    }
-    range.collapse(false);
-    return this._clearRange(range);
-  };
-
-  EditableController.prototype._clearRange = function(range) {
-    var sel;
-    if (range == null) {
-      range = this._getRange();
-    }
-    sel = this.app.window.getSelection();
-    if (this.ctrl_a_pressed == null) {
-      sel.removeAllRanges();
-      return sel.addRange(range);
-    }
-  };
-
-  EditableController.prototype._movingEvent = function(e) {
-    var ref;
-    return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
-  };
-
-  EditableController.prototype._unwrap = function(node) {
-    var next;
-    node = $(node).unwrap().get(0);
-    if ((next = node.nextSibling) && next.nodeValue) {
-      node.nodeValue += next.nodeValue;
-      $(next).remove();
-    }
-    return node;
-  };
-
-  EditableController.prototype.catchQuery = function(e) {
-    var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
-    if (!(range = this._getRange())) {
-      return;
-    }
-    if (!range.collapsed) {
-      return;
-    }
-    if (e.which === KEY_CODE.ENTER) {
-      ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap();
-      if ($query.is(':empty')) {
-        $query.remove();
-      }
-      ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap();
-      this._clearRange();
-      return;
-    }
-    if (/firefox/i.test(navigator.userAgent)) {
-      if ($(range.startContainer).is(this.$inputor)) {
-        this._clearRange();
-        return;
-      }
-      if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) {
-        _range = range.cloneRange();
-        _range.setStart(range.startContainer, offset);
-        if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) {
-          inserted = $(range.startContainer).contents().get(offset);
-          this._setRange('after', $(inserted).contents().last());
-        }
-      } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) {
-        $inserted = $(range.startContainer.previousSibling);
-        if ($inserted.is('.atwho-inserted') && range.startOffset === 0) {
-          this._setRange('after', $inserted.contents().last());
-        }
-      }
-    }
-    $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
-    if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
-      $query.remove();
-    }
-    if (!this._movingEvent(e)) {
-      $query.removeClass('atwho-inserted');
-    }
-    if ($query.length > 0) {
-      switch (e.which) {
-        case KEY_CODE.LEFT:
-          this._setRange('before', $query.get(0), range);
-          $query.removeClass('atwho-query');
-          return;
-        case KEY_CODE.RIGHT:
-          this._setRange('after', $query.get(0).nextSibling, range);
-          $query.removeClass('atwho-query');
-          return;
-      }
-    }
-    if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
-      $query.empty().html(query_content).attr('data-atwho-at-query', null);
-      this._setRange('after', $query.get(0), range);
-    }
-    _range = range.cloneRange();
-    _range.setStart(range.startContainer, 0);
-    matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
-    isString = typeof matched === 'string';
-    if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
-      range.setStart(range.startContainer, index);
-      $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
-      range.surroundContents($query.get(0));
-      lastNode = $query.contents().last().get(0);
-      if (lastNode) {
-        if (/firefox/i.test(navigator.userAgent)) {
-          range.setStart(lastNode, lastNode.length);
-          range.setEnd(lastNode, lastNode.length);
-          this._clearRange(range);
-        } else {
-          this._setRange('after', lastNode, range);
-        }
-      }
-    }
-    if (isString && matched.length < this.getOpt('minLen', 0)) {
-      return;
-    }
-    if (isString && matched.length <= this.getOpt('maxLen', 20)) {
-      query = {
-        text: matched,
-        el: $query
-      };
-      this.trigger("matched", [this.at, query.text]);
-      return this.query = query;
-    } else {
-      this.view.hide();
-      this.query = {
-        el: $query
-      };
-      if ($query.text().indexOf(this.at) >= 0) {
-        if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) {
-          $query.removeClass('atwho-query');
-        } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) {
-          this._setRange("after", this._unwrap($query.text($query.text()).contents().first()));
-        }
-      }
-      return null;
-    }
-  };
-
-  EditableController.prototype.rect = function() {
-    var $iframe, iframeOffset, rect;
-    rect = this.query.el.offset();
-    if (!(rect && this.query.el[0].getClientRects().length)) {
-      return;
-    }
-    if (this.app.iframe && !this.app.iframeAsRoot) {
-      iframeOffset = ($iframe = $(this.app.iframe)).offset();
-      rect.left += iframeOffset.left - this.$inputor.scrollLeft();
-      rect.top += iframeOffset.top - this.$inputor.scrollTop();
-    }
-    rect.bottom = rect.top + this.query.el.height();
-    return rect;
-  };
-
-  EditableController.prototype.insert = function(content, $li) {
-    var data, overrides, range, suffix, suffixNode;
-    if (!this.$inputor.is(':focus')) {
-      this.$inputor.focus();
-    }
-    overrides = this.getOpt('functionOverrides');
-    if (overrides.insert) {
-      return overrides.insert.call(this, content, $li);
-    }
-    suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0";
-    data = $li.data('item-data');
-    this.query.el.removeClass('atwho-query').addClass('atwho-inserted').html(content).attr('data-atwho-at-query', "" + data['atwho-at'] + this.query.text).attr('contenteditable', "false");
-    if (range = this._getRange()) {
-      if (this.query.el.length) {
-        range.setEndAfter(this.query.el[0]);
-      }
-      range.collapse(false);
-      range.insertNode(suffixNode = this.app.document.createTextNode("" + suffix));
-      this._setRange('after', suffixNode, range);
-    }
-    if (!this.$inputor.is(':focus')) {
-      this.$inputor.focus();
-    }
-    return this.$inputor.change();
-  };
-
-  return EditableController;
-
-})(Controller);
-
-var Model;
-
-Model = (function() {
-  function Model(context) {
-    this.context = context;
-    this.at = this.context.at;
-    this.storage = this.context.$inputor;
-  }
-
-  Model.prototype.destroy = function() {
-    return this.storage.data(this.at, null);
-  };
-
-  Model.prototype.saved = function() {
-    return this.fetch() > 0;
-  };
-
-  Model.prototype.query = function(query, callback) {
-    var _remoteFilter, data, searchKey;
-    data = this.fetch();
-    searchKey = this.context.getOpt("searchKey");
-    data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || [];
-    _remoteFilter = this.context.callbacks('remoteFilter');
-    if (data.length > 0 || (!_remoteFilter && data.length === 0)) {
-      return callback(data);
-    } else {
-      return _remoteFilter.call(this.context, query, callback);
-    }
-  };
-
-  Model.prototype.fetch = function() {
-    return this.storage.data(this.at) || [];
-  };
-
-  Model.prototype.save = function(data) {
-    return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
-  };
-
-  Model.prototype.load = function(data) {
-    if (!(this.saved() || !data)) {
-      return this._load(data);
-    }
-  };
-
-  Model.prototype.reload = function(data) {
-    return this._load(data);
-  };
-
-  Model.prototype._load = function(data) {
-    if (typeof data === "string") {
-      return $.ajax(data, {
-        dataType: "json"
-      }).done((function(_this) {
-        return function(data) {
-          return _this.save(data);
-        };
-      })(this));
-    } else {
-      return this.save(data);
-    }
-  };
-
-  return Model;
-
-})();
-
-var View;
-
-View = (function() {
-  function View(context) {
-    this.context = context;
-    this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
-    this.$elUl = this.$el.children();
-    this.timeoutID = null;
-    this.context.$el.append(this.$el);
-    this.bindEvent();
-  }
-
-  View.prototype.init = function() {
-    var header_tpl, id;
-    id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
-    header_tpl = this.context.getOpt("headerTpl");
-    if (header_tpl && this.$el.children().length === 1) {
-      this.$el.prepend(header_tpl);
-    }
-    return this.$el.attr({
-      'id': "at-view-" + id
-    });
-  };
-
-  View.prototype.destroy = function() {
-    return this.$el.remove();
-  };
-
-  View.prototype.bindEvent = function() {
-    var $menu, lastCoordX, lastCoordY;
-    $menu = this.$el.find('ul');
-    lastCoordX = 0;
-    lastCoordY = 0;
-    return $menu.on('mousemove.atwho-view', 'li', (function(_this) {
-      return function(e) {
-        var $cur;
-        if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
-          return;
-        }
-        lastCoordX = e.clientX;
-        lastCoordY = e.clientY;
-        $cur = $(e.currentTarget);
-        if ($cur.hasClass('cur')) {
-          return;
-        }
-        $menu.find('.cur').removeClass('cur');
-        return $cur.addClass('cur');
-      };
-    })(this)).on('click.atwho-view', 'li', (function(_this) {
-      return function(e) {
-        $menu.find('.cur').removeClass('cur');
-        $(e.currentTarget).addClass('cur');
-        _this.choose(e);
-        return e.preventDefault();
-      };
-    })(this));
-  };
-
-  View.prototype.visible = function() {
-    return $.expr.filters.visible(this.$el[0]);
-  };
-
-  View.prototype.highlighted = function() {
-    return this.$el.find(".cur").length > 0;
-  };
-
-  View.prototype.choose = function(e) {
-    var $li, content;
-    if (($li = this.$el.find(".cur")).length) {
-      content = this.context.insertContentFor($li);
-      this.context._stopDelayedCall();
-      this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li, e), $li);
-      this.context.trigger("inserted", [$li, e]);
-      this.hide(e);
-    }
-    if (this.context.getOpt("hideWithoutSuffix")) {
-      return this.stopShowing = true;
-    }
-  };
-
-  View.prototype.reposition = function(rect) {
-    var _window, offset, overflowOffset, ref;
-    _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
-    if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
-      rect.bottom = rect.top - this.$el.height();
-    }
-    if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
-      rect.left = overflowOffset;
-    }
-    offset = {
-      left: rect.left,
-      top: rect.bottom
-    };
-    if ((ref = this.context.callbacks("beforeReposition")) != null) {
-      ref.call(this.context, offset);
-    }
-    this.$el.offset(offset);
-    return this.context.trigger("reposition", [offset]);
-  };
-
-  View.prototype.next = function() {
-    var cur, next, nextEl, offset;
-    cur = this.$el.find('.cur').removeClass('cur');
-    next = cur.next();
-    if (!next.length) {
-      next = this.$el.find('li:first');
-    }
-    next.addClass('cur');
-    nextEl = next[0];
-    offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
-    return this.scrollTop(Math.max(0, offset - this.$el.height()));
-  };
-
-  View.prototype.prev = function() {
-    var cur, offset, prev, prevEl;
-    cur = this.$el.find('.cur').removeClass('cur');
-    prev = cur.prev();
-    if (!prev.length) {
-      prev = this.$el.find('li:last');
-    }
-    prev.addClass('cur');
-    prevEl = prev[0];
-    offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
-    return this.scrollTop(Math.max(0, offset - this.$el.height()));
-  };
-
-  View.prototype.scrollTop = function(scrollTop) {
-    var scrollDuration;
-    scrollDuration = this.context.getOpt('scrollDuration');
-    if (scrollDuration) {
-      return this.$elUl.animate({
-        scrollTop: scrollTop
-      }, scrollDuration);
-    } else {
-      return this.$elUl.scrollTop(scrollTop);
-    }
-  };
-
-  View.prototype.show = function() {
-    var rect;
-    if (this.stopShowing) {
-      this.stopShowing = false;
-      return;
-    }
-    if (!this.visible()) {
-      this.$el.show();
-      this.$el.scrollTop(0);
-      this.context.trigger('shown');
-    }
-    if (rect = this.context.rect()) {
-      return this.reposition(rect);
-    }
-  };
-
-  View.prototype.hide = function(e, time) {
-    var callback;
-    if (!this.visible()) {
-      return;
-    }
-    if (isNaN(time)) {
-      this.$el.hide();
-      return this.context.trigger('hidden', [e]);
-    } else {
-      callback = (function(_this) {
-        return function() {
-          return _this.hide();
-        };
-      })(this);
-      clearTimeout(this.timeoutID);
-      return this.timeoutID = setTimeout(callback, time);
-    }
-  };
-
-  View.prototype.render = function(list) {
-    var $li, $ul, i, item, len, li, tpl;
-    if (!($.isArray(list) && list.length > 0)) {
-      this.hide();
-      return;
-    }
-    this.$el.find('ul').empty();
-    $ul = this.$el.find('ul');
-    tpl = this.context.getOpt('displayTpl');
-    for (i = 0, len = list.length; i < len; i++) {
-      item = list[i];
-      item = $.extend({}, item, {
-        'atwho-at': this.context.at
-      });
-      li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay");
-      $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
-      $li.data("item-data", item);
-      $ul.append($li);
-    }
-    this.show();
-    if (this.context.getOpt('highlightFirst')) {
-      return $ul.find("li:first").addClass("cur");
-    }
-  };
-
-  return View;
-
-})();
-
-var Api;
-
-Api = {
-  load: function(at, data) {
-    var c;
-    if (c = this.controller(at)) {
-      return c.model.load(data);
-    }
-  },
-  isSelecting: function() {
-    var ref;
-    return !!((ref = this.controller()) != null ? ref.view.visible() : void 0);
-  },
-  hide: function() {
-    var ref;
-    return (ref = this.controller()) != null ? ref.view.hide() : void 0;
-  },
-  reposition: function() {
-    var c;
-    if (c = this.controller()) {
-      return c.view.reposition(c.rect());
-    }
-  },
-  setIframe: function(iframe, asRoot) {
-    this.setupRootElement(iframe, asRoot);
-    return null;
-  },
-  run: function() {
-    return this.dispatch();
-  },
-  destroy: function() {
-    this.shutdown();
-    return this.$inputor.data('atwho', null);
-  }
-};
-
-$.fn.atwho = function(method) {
-  var _args, result;
-  _args = arguments;
-  result = null;
-  this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
-    var $this, app;
-    if (!(app = ($this = $(this)).data("atwho"))) {
-      $this.data('atwho', (app = new App(this)));
-    }
-    if (typeof method === 'object' || !method) {
-      return app.reg(method.at, method);
-    } else if (Api[method] && app) {
-      return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
-    } else {
-      return $.error("Method " + method + " does not exist on jQuery.atwho");
-    }
-  });
-  if (result != null) {
-    return result;
-  } else {
-    return this;
-  }
-};
-
-$.fn.atwho["default"] = {
-  at: void 0,
-  alias: void 0,
-  data: null,
-  displayTpl: "<li>${name}</li>",
-  insertTpl: "${atwho-at}${name}",
-  headerTpl: null,
-  callbacks: DEFAULT_CALLBACKS,
-  functionOverrides: {},
-  searchKey: "name",
-  suffix: void 0,
-  hideWithoutSuffix: false,
-  startWithSpace: true,
-  acceptSpaceBar: false,
-  highlightFirst: true,
-  limit: 5,
-  maxLen: 20,
-  minLen: 0,
-  displayTimeout: 300,
-  delay: null,
-  spaceSelectsMatch: false,
-  tabSelectsMatch: true,
-  editableAtwhoQueryAttrs: {},
-  scrollDuration: 150,
-  suspendOnComposing: true,
-  lookUpOnClick: true
-};
-
-$.fn.atwho.debug = false;
-
-}));
diff --git src/bp-core/js/vendor/jquery.atwho.txt src/bp-core/js/vendor/jquery.atwho.txt
deleted file mode 100644
index 36cd1c122..000000000
--- src/bp-core/js/vendor/jquery.atwho.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright (c) 2013 chord.luo@gmail.com
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
diff --git src/bp-core/js/vendor/jquery.caret.js src/bp-core/js/vendor/jquery.caret.js
deleted file mode 100755
index a63ceda93..000000000
--- src/bp-core/js/vendor/jquery.caret.js
+++ /dev/null
@@ -1,436 +0,0 @@
-(function (root, factory) {
-  if (typeof define === 'function' && define.amd) {
-    // AMD. Register as an anonymous module.
-    define(["jquery"], function ($) {
-      return (root.returnExportsGlobal = factory($));
-    });
-  } else if (typeof exports === 'object') {
-    // Node. Does not work with strict CommonJS, but
-    // only CommonJS-like environments that support module.exports,
-    // like Node.
-    module.exports = factory(require("jquery"));
-  } else {
-    factory(jQuery);
-  }
-}(this, function ($) {
-
-/*
-  Implement Github like autocomplete mentions
-  http://ichord.github.com/At.js
-
-  Copyright (c) 2013 chord.luo@gmail.com
-  Licensed under the MIT license.
-*/
-
-/*
-本插件操作 textarea 或者 input 内的插入符
-只实现了获得插入符在文本框中的位置，我设置
-插入符的位置.
-*/
-
-"use strict";
-var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
-
-pluginName = 'caret';
-
-EditableCaret = (function() {
-  function EditableCaret($inputor) {
-    this.$inputor = $inputor;
-    this.domInputor = this.$inputor[0];
-  }
-
-  EditableCaret.prototype.setPos = function(pos) {
-    var fn, found, offset, sel;
-    if (sel = oWindow.getSelection()) {
-      offset = 0;
-      found = false;
-      (fn = function(pos, parent) {
-        var node, range, _i, _len, _ref, _results;
-        _ref = parent.childNodes;
-        _results = [];
-        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
-          node = _ref[_i];
-          if (found) {
-            break;
-          }
-          if (node.nodeType === 3) {
-            if (offset + node.length >= pos) {
-              found = true;
-              range = oDocument.createRange();
-              range.setStart(node, pos - offset);
-              sel.removeAllRanges();
-              sel.addRange(range);
-              break;
-            } else {
-              _results.push(offset += node.length);
-            }
-          } else {
-            _results.push(fn(pos, node));
-          }
-        }
-        return _results;
-      })(pos, this.domInputor);
-    }
-    return this.domInputor;
-  };
-
-  EditableCaret.prototype.getIEPosition = function() {
-    return this.getPosition();
-  };
-
-  EditableCaret.prototype.getPosition = function() {
-    var inputor_offset, offset;
-    offset = this.getOffset();
-    inputor_offset = this.$inputor.offset();
-    offset.left -= inputor_offset.left;
-    offset.top -= inputor_offset.top;
-    return offset;
-  };
-
-  EditableCaret.prototype.getOldIEPos = function() {
-    var preCaretTextRange, textRange;
-    textRange = oDocument.selection.createRange();
-    preCaretTextRange = oDocument.body.createTextRange();
-    preCaretTextRange.moveToElementText(this.domInputor);
-    preCaretTextRange.setEndPoint("EndToEnd", textRange);
-    return preCaretTextRange.text.length;
-  };
-
-  EditableCaret.prototype.getPos = function() {
-    var clonedRange, pos, range;
-    if (range = this.range()) {
-      clonedRange = range.cloneRange();
-      clonedRange.selectNodeContents(this.domInputor);
-      clonedRange.setEnd(range.endContainer, range.endOffset);
-      pos = clonedRange.toString().length;
-      clonedRange.detach();
-      return pos;
-    } else if (oDocument.selection) {
-      return this.getOldIEPos();
-    }
-  };
-
-  EditableCaret.prototype.getOldIEOffset = function() {
-    var range, rect;
-    range = oDocument.selection.createRange().duplicate();
-    range.moveStart("character", -1);
-    rect = range.getBoundingClientRect();
-    return {
-      height: rect.bottom - rect.top,
-      left: rect.left,
-      top: rect.top
-    };
-  };
-
-  EditableCaret.prototype.getOffset = function(pos) {
-    var clonedRange, offset, range, rect, shadowCaret;
-    if (oWindow.getSelection && (range = this.range())) {
-      if (range.endOffset - 1 > 0 && range.endContainer !== this.domInputor) {
-        clonedRange = range.cloneRange();
-        clonedRange.setStart(range.endContainer, range.endOffset - 1);
-        clonedRange.setEnd(range.endContainer, range.endOffset);
-        rect = clonedRange.getBoundingClientRect();
-        offset = {
-          height: rect.height,
-          left: rect.left + rect.width,
-          top: rect.top
-        };
-        clonedRange.detach();
-      }
-      if (!offset || (offset != null ? offset.height : void 0) === 0) {
-        clonedRange = range.cloneRange();
-        shadowCaret = $(oDocument.createTextNode("|"));
-        clonedRange.insertNode(shadowCaret[0]);
-        clonedRange.selectNode(shadowCaret[0]);
-        rect = clonedRange.getBoundingClientRect();
-        offset = {
-          height: rect.height,
-          left: rect.left,
-          top: rect.top
-        };
-        shadowCaret.remove();
-        clonedRange.detach();
-      }
-    } else if (oDocument.selection) {
-      offset = this.getOldIEOffset();
-    }
-    if (offset) {
-      offset.top += $(oWindow).scrollTop();
-      offset.left += $(oWindow).scrollLeft();
-    }
-    return offset;
-  };
-
-  EditableCaret.prototype.range = function() {
-    var sel;
-    if (!oWindow.getSelection) {
-      return;
-    }
-    sel = oWindow.getSelection();
-    if (sel.rangeCount > 0) {
-      return sel.getRangeAt(0);
-    } else {
-      return null;
-    }
-  };
-
-  return EditableCaret;
-
-})();
-
-InputCaret = (function() {
-  function InputCaret($inputor) {
-    this.$inputor = $inputor;
-    this.domInputor = this.$inputor[0];
-  }
-
-  InputCaret.prototype.getIEPos = function() {
-    var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
-    inputor = this.domInputor;
-    range = oDocument.selection.createRange();
-    pos = 0;
-    if (range && range.parentElement() === inputor) {
-      normalizedValue = inputor.value.replace(/\r\n/g, "\n");
-      len = normalizedValue.length;
-      textInputRange = inputor.createTextRange();
-      textInputRange.moveToBookmark(range.getBookmark());
-      endRange = inputor.createTextRange();
-      endRange.collapse(false);
-      if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
-        pos = len;
-      } else {
-        pos = -textInputRange.moveStart("character", -len);
-      }
-    }
-    return pos;
-  };
-
-  InputCaret.prototype.getPos = function() {
-    if (oDocument.selection) {
-      return this.getIEPos();
-    } else {
-      return this.domInputor.selectionStart;
-    }
-  };
-
-  InputCaret.prototype.setPos = function(pos) {
-    var inputor, range;
-    inputor = this.domInputor;
-    if (oDocument.selection) {
-      range = inputor.createTextRange();
-      range.move("character", pos);
-      range.select();
-    } else if (inputor.setSelectionRange) {
-      inputor.setSelectionRange(pos, pos);
-    }
-    return inputor;
-  };
-
-  InputCaret.prototype.getIEOffset = function(pos) {
-    var h, textRange, x, y;
-    textRange = this.domInputor.createTextRange();
-    pos || (pos = this.getPos());
-    textRange.move('character', pos);
-    x = textRange.boundingLeft;
-    y = textRange.boundingTop;
-    h = textRange.boundingHeight;
-    return {
-      left: x,
-      top: y,
-      height: h
-    };
-  };
-
-  InputCaret.prototype.getOffset = function(pos) {
-    var $inputor, offset, position;
-    $inputor = this.$inputor;
-    if (oDocument.selection) {
-      offset = this.getIEOffset(pos);
-      offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
-      offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
-      return offset;
-    } else {
-      offset = $inputor.offset();
-      position = this.getPosition(pos);
-      return offset = {
-        left: offset.left + position.left - $inputor.scrollLeft(),
-        top: offset.top + position.top - $inputor.scrollTop(),
-        height: position.height
-      };
-    }
-  };
-
-  InputCaret.prototype.getPosition = function(pos) {
-    var $inputor, at_rect, end_range, format, html, mirror, start_range;
-    $inputor = this.$inputor;
-    format = function(value) {
-      value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
-      if (/firefox/i.test(navigator.userAgent)) {
-        value = value.replace(/\s/g, '&nbsp;');
-      }
-      return value;
-    };
-    if (pos === void 0) {
-      pos = this.getPos();
-    }
-    start_range = $inputor.val().slice(0, pos);
-    end_range = $inputor.val().slice(pos);
-    html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
-    html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
-    html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
-    mirror = new Mirror($inputor);
-    return at_rect = mirror.create(html).rect();
-  };
-
-  InputCaret.prototype.getIEPosition = function(pos) {
-    var h, inputorOffset, offset, x, y;
-    offset = this.getIEOffset(pos);
-    inputorOffset = this.$inputor.offset();
-    x = offset.left - inputorOffset.left;
-    y = offset.top - inputorOffset.top;
-    h = offset.height;
-    return {
-      left: x,
-      top: y,
-      height: h
-    };
-  };
-
-  return InputCaret;
-
-})();
-
-Mirror = (function() {
-  Mirror.prototype.css_attr = ["borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontWeight", "height", "letterSpacing", "lineHeight", "marginBottom", "marginLeft", "marginRight", "marginTop", "outlineWidth", "overflow", "overflowX", "overflowY", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textAlign", "textOverflow", "textTransform", "whiteSpace", "wordBreak", "wordWrap"];
-
-  function Mirror($inputor) {
-    this.$inputor = $inputor;
-  }
-
-  Mirror.prototype.mirrorCss = function() {
-    var css,
-      _this = this;
-    css = {
-      position: 'absolute',
-      left: -9999,
-      top: 0,
-      zIndex: -20000
-    };
-    if (this.$inputor.prop('tagName') === 'TEXTAREA') {
-      this.css_attr.push('width');
-    }
-    $.each(this.css_attr, function(i, p) {
-      return css[p] = _this.$inputor.css(p);
-    });
-    return css;
-  };
-
-  Mirror.prototype.create = function(html) {
-    this.$mirror = $('<div></div>');
-    this.$mirror.css(this.mirrorCss());
-    this.$mirror.html(html);
-    this.$inputor.after(this.$mirror);
-    return this;
-  };
-
-  Mirror.prototype.rect = function() {
-    var $flag, pos, rect;
-    $flag = this.$mirror.find("#caret");
-    pos = $flag.position();
-    rect = {
-      left: pos.left,
-      top: pos.top,
-      height: $flag.height()
-    };
-    this.$mirror.remove();
-    return rect;
-  };
-
-  return Mirror;
-
-})();
-
-Utils = {
-  contentEditable: function($inputor) {
-    return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
-  }
-};
-
-methods = {
-  pos: function(pos) {
-    if (pos || pos === 0) {
-      return this.setPos(pos);
-    } else {
-      return this.getPos();
-    }
-  },
-  position: function(pos) {
-    if (oDocument.selection) {
-      return this.getIEPosition(pos);
-    } else {
-      return this.getPosition(pos);
-    }
-  },
-  offset: function(pos) {
-    var offset;
-    offset = this.getOffset(pos);
-    return offset;
-  }
-};
-
-oDocument = null;
-
-oWindow = null;
-
-oFrame = null;
-
-setContextBy = function(settings) {
-  var iframe;
-  if (iframe = settings != null ? settings.iframe : void 0) {
-    oFrame = iframe;
-    oWindow = iframe.contentWindow;
-    return oDocument = iframe.contentDocument || oWindow.document;
-  } else {
-    oFrame = void 0;
-    oWindow = window;
-    return oDocument = document;
-  }
-};
-
-discoveryIframeOf = function($dom) {
-  var error;
-  oDocument = $dom[0].ownerDocument;
-  oWindow = oDocument.defaultView || oDocument.parentWindow;
-  try {
-    return oFrame = oWindow.frameElement;
-  } catch (_error) {
-    error = _error;
-  }
-};
-
-$.fn.caret = function(method, value, settings) {
-  var caret;
-  if (methods[method]) {
-    if ($.isPlainObject(value)) {
-      setContextBy(value);
-      value = void 0;
-    } else {
-      setContextBy(settings);
-    }
-    caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
-    return methods[method].apply(caret, [value]);
-  } else {
-    return $.error("Method " + method + " does not exist on jQuery.caret");
-  }
-};
-
-$.fn.caret.EditableCaret = EditableCaret;
-
-$.fn.caret.InputCaret = InputCaret;
-
-$.fn.caret.Utils = Utils;
-
-$.fn.caret.apis = methods;
-
-
-}));
diff --git src/bp-core/js/vendor/jquery.caret.txt src/bp-core/js/vendor/jquery.caret.txt
deleted file mode 100644
index 36cd1c122..000000000
--- src/bp-core/js/vendor/jquery.caret.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright (c) 2013 chord.luo@gmail.com
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
diff --git src/bp-core/js/vendor/tribute.js src/bp-core/js/vendor/tribute.js
new file mode 100644
index 000000000..9c46e6357
--- /dev/null
+++ src/bp-core/js/vendor/tribute.js
@@ -0,0 +1,1898 @@
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define(factory) :
+  (global = global || self, global.Tribute = factory());
+}(this, (function () { 'use strict';
+
+  function _classCallCheck(instance, Constructor) {
+    if (!(instance instanceof Constructor)) {
+      throw new TypeError("Cannot call a class as a function");
+    }
+  }
+
+  function _defineProperties(target, props) {
+    for (var i = 0; i < props.length; i++) {
+      var descriptor = props[i];
+      descriptor.enumerable = descriptor.enumerable || false;
+      descriptor.configurable = true;
+      if ("value" in descriptor) descriptor.writable = true;
+      Object.defineProperty(target, descriptor.key, descriptor);
+    }
+  }
+
+  function _createClass(Constructor, protoProps, staticProps) {
+    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+    if (staticProps) _defineProperties(Constructor, staticProps);
+    return Constructor;
+  }
+
+  function _slicedToArray(arr, i) {
+    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
+  }
+
+  function _arrayWithHoles(arr) {
+    if (Array.isArray(arr)) return arr;
+  }
+
+  function _iterableToArrayLimit(arr, i) {
+    if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
+    var _arr = [];
+    var _n = true;
+    var _d = false;
+    var _e = undefined;
+
+    try {
+      for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
+        _arr.push(_s.value);
+
+        if (i && _arr.length === i) break;
+      }
+    } catch (err) {
+      _d = true;
+      _e = err;
+    } finally {
+      try {
+        if (!_n && _i["return"] != null) _i["return"]();
+      } finally {
+        if (_d) throw _e;
+      }
+    }
+
+    return _arr;
+  }
+
+  function _unsupportedIterableToArray(o, minLen) {
+    if (!o) return;
+    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+    var n = Object.prototype.toString.call(o).slice(8, -1);
+    if (n === "Object" && o.constructor) n = o.constructor.name;
+    if (n === "Map" || n === "Set") return Array.from(n);
+    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+  }
+
+  function _arrayLikeToArray(arr, len) {
+    if (len == null || len > arr.length) len = arr.length;
+
+    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+
+    return arr2;
+  }
+
+  function _nonIterableRest() {
+    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+  }
+
+  if (!Array.prototype.find) {
+    Array.prototype.find = function (predicate) {
+      if (this === null) {
+        throw new TypeError('Array.prototype.find called on null or undefined');
+      }
+
+      if (typeof predicate !== 'function') {
+        throw new TypeError('predicate must be a function');
+      }
+
+      var list = Object(this);
+      var length = list.length >>> 0;
+      var thisArg = arguments[1];
+      var value;
+
+      for (var i = 0; i < length; i++) {
+        value = list[i];
+
+        if (predicate.call(thisArg, value, i, list)) {
+          return value;
+        }
+      }
+
+      return undefined;
+    };
+  }
+
+  if (window && typeof window.CustomEvent !== "function") {
+    var CustomEvent$1 = function CustomEvent(event, params) {
+      params = params || {
+        bubbles: false,
+        cancelable: false,
+        detail: undefined
+      };
+      var evt = document.createEvent('CustomEvent');
+      evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
+      return evt;
+    };
+
+    if (typeof window.Event !== 'undefined') {
+      CustomEvent$1.prototype = window.Event.prototype;
+    }
+
+    window.CustomEvent = CustomEvent$1;
+  }
+
+  var TributeEvents = /*#__PURE__*/function () {
+    function TributeEvents(tribute) {
+      _classCallCheck(this, TributeEvents);
+
+      this.tribute = tribute;
+      this.tribute.events = this;
+    }
+
+    _createClass(TributeEvents, [{
+      key: "bind",
+      value: function bind(element) {
+        element.boundKeydown = this.keydown.bind(element, this);
+        element.boundKeyup = this.keyup.bind(element, this);
+        element.boundInput = this.input.bind(element, this);
+        element.addEventListener("keydown", element.boundKeydown, false);
+        element.addEventListener("keyup", element.boundKeyup, false);
+        element.addEventListener("input", element.boundInput, false);
+      }
+    }, {
+      key: "unbind",
+      value: function unbind(element) {
+        element.removeEventListener("keydown", element.boundKeydown, false);
+        element.removeEventListener("keyup", element.boundKeyup, false);
+        element.removeEventListener("input", element.boundInput, false);
+        delete element.boundKeydown;
+        delete element.boundKeyup;
+        delete element.boundInput;
+      }
+    }, {
+      key: "keydown",
+      value: function keydown(instance, event) {
+        if (instance.shouldDeactivate(event)) {
+          instance.tribute.isActive = false;
+          instance.tribute.hideMenu();
+        }
+
+        var element = this;
+        instance.commandEvent = false;
+        TributeEvents.keys().forEach(function (o) {
+          if (o.key === event.keyCode) {
+            instance.commandEvent = true;
+            instance.callbacks()[o.value.toLowerCase()](event, element);
+          }
+        });
+      }
+    }, {
+      key: "input",
+      value: function input(instance, event) {
+        instance.inputEvent = true;
+        instance.keyup.call(this, instance, event);
+      }
+    }, {
+      key: "click",
+      value: function click(instance, event) {
+        var tribute = instance.tribute;
+
+        if (tribute.menu && tribute.menu.contains(event.target)) {
+          var li = event.target;
+          event.preventDefault();
+          event.stopPropagation();
+
+          while (li.nodeName.toLowerCase() !== "li") {
+            li = li.parentNode;
+
+            if (!li || li === tribute.menu) {
+              throw new Error("cannot find the <li> container for the click");
+            }
+          }
+
+          tribute.selectItemAtIndex(li.getAttribute("data-index"), event);
+          tribute.hideMenu(); // TODO: should fire with externalTrigger and target is outside of menu
+        } else if (tribute.current.element && !tribute.current.externalTrigger) {
+          tribute.current.externalTrigger = false;
+          setTimeout(function () {
+            return tribute.hideMenu();
+          });
+        }
+      }
+    }, {
+      key: "keyup",
+      value: function keyup(instance, event) {
+        if (instance.inputEvent) {
+          instance.inputEvent = false;
+        }
+
+        instance.updateSelection(this);
+        if (event.keyCode === 27) return;
+
+        if (!instance.tribute.allowSpaces && instance.tribute.hasTrailingSpace) {
+          instance.tribute.hasTrailingSpace = false;
+          instance.commandEvent = true;
+          instance.callbacks()["space"](event, this);
+          return;
+        }
+
+        if (!instance.tribute.isActive) {
+          if (instance.tribute.autocompleteMode) {
+            instance.callbacks().triggerChar(event, this, "");
+          } else {
+            var keyCode = instance.getKeyCode(instance, this, event);
+            if (isNaN(keyCode) || !keyCode) return;
+            var trigger = instance.tribute.triggers().find(function (trigger) {
+              return trigger.charCodeAt(0) === keyCode;
+            });
+
+            if (typeof trigger !== "undefined") {
+              instance.callbacks().triggerChar(event, this, trigger);
+            }
+          }
+        }
+
+        if (instance.tribute.current.mentionText.length < instance.tribute.current.collection.menuShowMinLength) {
+          return;
+        }
+
+        if ((instance.tribute.current.trigger || instance.tribute.autocompleteMode) && instance.commandEvent === false || instance.tribute.isActive && event.keyCode === 8) {
+          instance.tribute.showMenuFor(this, true);
+        }
+      }
+    }, {
+      key: "shouldDeactivate",
+      value: function shouldDeactivate(event) {
+        if (!this.tribute.isActive) return false;
+
+        if (this.tribute.current.mentionText.length === 0) {
+          var eventKeyPressed = false;
+          TributeEvents.keys().forEach(function (o) {
+            if (event.keyCode === o.key) eventKeyPressed = true;
+          });
+          return !eventKeyPressed;
+        }
+
+        return false;
+      }
+    }, {
+      key: "getKeyCode",
+      value: function getKeyCode(instance, el, event) {
+
+        var tribute = instance.tribute;
+        var info = tribute.range.getTriggerInfo(false, tribute.hasTrailingSpace, true, tribute.allowSpaces, tribute.autocompleteMode);
+
+        if (info) {
+          return info.mentionTriggerChar.charCodeAt(0);
+        } else {
+          return false;
+        }
+      }
+    }, {
+      key: "updateSelection",
+      value: function updateSelection(el) {
+        this.tribute.current.element = el;
+        var info = this.tribute.range.getTriggerInfo(false, this.tribute.hasTrailingSpace, true, this.tribute.allowSpaces, this.tribute.autocompleteMode);
+
+        if (info) {
+          this.tribute.current.selectedPath = info.mentionSelectedPath;
+          this.tribute.current.mentionText = info.mentionText;
+          this.tribute.current.selectedOffset = info.mentionSelectedOffset;
+        }
+      }
+    }, {
+      key: "callbacks",
+      value: function callbacks() {
+        var _this = this;
+
+        return {
+          triggerChar: function triggerChar(e, el, trigger) {
+            var tribute = _this.tribute;
+            tribute.current.trigger = trigger;
+            var collectionItem = tribute.collection.find(function (item) {
+              return item.trigger === trigger;
+            });
+            tribute.current.collection = collectionItem;
+
+            if (tribute.current.mentionText.length >= tribute.current.collection.menuShowMinLength && tribute.inputEvent) {
+              tribute.showMenuFor(el, true);
+            }
+          },
+          enter: function enter(e, el) {
+            // choose selection
+            if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
+              e.preventDefault();
+              e.stopPropagation();
+              setTimeout(function () {
+                _this.tribute.selectItemAtIndex(_this.tribute.menuSelected, e);
+
+                _this.tribute.hideMenu();
+              }, 0);
+            }
+          },
+          escape: function escape(e, el) {
+            if (_this.tribute.isActive) {
+              e.preventDefault();
+              e.stopPropagation();
+              _this.tribute.isActive = false;
+
+              _this.tribute.hideMenu();
+            }
+          },
+          tab: function tab(e, el) {
+            // choose first match
+            _this.callbacks().enter(e, el);
+          },
+          space: function space(e, el) {
+            if (_this.tribute.isActive) {
+              if (_this.tribute.spaceSelectsMatch) {
+                _this.callbacks().enter(e, el);
+              } else if (!_this.tribute.allowSpaces) {
+                e.stopPropagation();
+                setTimeout(function () {
+                  _this.tribute.hideMenu();
+
+                  _this.tribute.isActive = false;
+                }, 0);
+              }
+            }
+          },
+          up: function up(e, el) {
+            // navigate up ul
+            if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
+              e.preventDefault();
+              e.stopPropagation();
+              var count = _this.tribute.current.filteredItems.length,
+                  selected = _this.tribute.menuSelected;
+
+              if (count > selected && selected > 0) {
+                _this.tribute.menuSelected--;
+
+                _this.setActiveLi();
+              } else if (selected === 0) {
+                _this.tribute.menuSelected = count - 1;
+
+                _this.setActiveLi();
+
+                _this.tribute.menu.scrollTop = _this.tribute.menu.scrollHeight;
+              }
+            }
+          },
+          down: function down(e, el) {
+            // navigate down ul
+            if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
+              e.preventDefault();
+              e.stopPropagation();
+              var count = _this.tribute.current.filteredItems.length - 1,
+                  selected = _this.tribute.menuSelected;
+
+              if (count > selected) {
+                _this.tribute.menuSelected++;
+
+                _this.setActiveLi();
+              } else if (count === selected) {
+                _this.tribute.menuSelected = 0;
+
+                _this.setActiveLi();
+
+                _this.tribute.menu.scrollTop = 0;
+              }
+            }
+          },
+          "delete": function _delete(e, el) {
+            if (_this.tribute.isActive && _this.tribute.current.mentionText.length < 1) {
+              _this.tribute.hideMenu();
+            } else if (_this.tribute.isActive) {
+              _this.tribute.showMenuFor(el);
+            }
+          }
+        };
+      }
+    }, {
+      key: "setActiveLi",
+      value: function setActiveLi(index) {
+        var lis = this.tribute.menu.querySelectorAll("li"),
+            length = lis.length >>> 0;
+        if (index) this.tribute.menuSelected = parseInt(index);
+
+        for (var i = 0; i < length; i++) {
+          var li = lis[i];
+
+          if (i === this.tribute.menuSelected) {
+            li.classList.add(this.tribute.current.collection.selectClass);
+            var liClientRect = li.getBoundingClientRect();
+            var menuClientRect = this.tribute.menu.getBoundingClientRect();
+
+            if (liClientRect.bottom > menuClientRect.bottom) {
+              var scrollDistance = liClientRect.bottom - menuClientRect.bottom;
+              this.tribute.menu.scrollTop += scrollDistance;
+            } else if (liClientRect.top < menuClientRect.top) {
+              var _scrollDistance = menuClientRect.top - liClientRect.top;
+
+              this.tribute.menu.scrollTop -= _scrollDistance;
+            }
+          } else {
+            li.classList.remove(this.tribute.current.collection.selectClass);
+          }
+        }
+      }
+    }, {
+      key: "getFullHeight",
+      value: function getFullHeight(elem, includeMargin) {
+        var height = elem.getBoundingClientRect().height;
+
+        if (includeMargin) {
+          var style = elem.currentStyle || window.getComputedStyle(elem);
+          return height + parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+        }
+
+        return height;
+      }
+    }], [{
+      key: "keys",
+      value: function keys() {
+        return [{
+          key: 9,
+          value: "TAB"
+        }, {
+          key: 8,
+          value: "DELETE"
+        }, {
+          key: 13,
+          value: "ENTER"
+        }, {
+          key: 27,
+          value: "ESCAPE"
+        }, {
+          key: 32,
+          value: "SPACE"
+        }, {
+          key: 38,
+          value: "UP"
+        }, {
+          key: 40,
+          value: "DOWN"
+        }];
+      }
+    }]);
+
+    return TributeEvents;
+  }();
+
+  var TributeMenuEvents = /*#__PURE__*/function () {
+    function TributeMenuEvents(tribute) {
+      _classCallCheck(this, TributeMenuEvents);
+
+      this.tribute = tribute;
+      this.tribute.menuEvents = this;
+      this.menu = this.tribute.menu;
+    }
+
+    _createClass(TributeMenuEvents, [{
+      key: "bind",
+      value: function bind(menu) {
+        var _this = this;
+
+        this.menuClickEvent = this.tribute.events.click.bind(null, this);
+        this.menuContainerScrollEvent = this.debounce(function () {
+          if (_this.tribute.isActive) {
+            _this.tribute.showMenuFor(_this.tribute.current.element, false);
+          }
+        }, 300, false);
+        this.windowResizeEvent = this.debounce(function () {
+          if (_this.tribute.isActive) {
+            _this.tribute.range.positionMenuAtCaret(true);
+          }
+        }, 300, false); // fixes IE11 issues with mousedown
+
+        this.tribute.range.getDocument().addEventListener("MSPointerDown", this.menuClickEvent, false);
+        this.tribute.range.getDocument().addEventListener("mousedown", this.menuClickEvent, false);
+        window.addEventListener("resize", this.windowResizeEvent);
+
+        if (this.menuContainer) {
+          this.menuContainer.addEventListener("scroll", this.menuContainerScrollEvent, false);
+        } else {
+          window.addEventListener("scroll", this.menuContainerScrollEvent);
+        }
+      }
+    }, {
+      key: "unbind",
+      value: function unbind(menu) {
+        this.tribute.range.getDocument().removeEventListener("mousedown", this.menuClickEvent, false);
+        this.tribute.range.getDocument().removeEventListener("MSPointerDown", this.menuClickEvent, false);
+        window.removeEventListener("resize", this.windowResizeEvent);
+
+        if (this.menuContainer) {
+          this.menuContainer.removeEventListener("scroll", this.menuContainerScrollEvent, false);
+        } else {
+          window.removeEventListener("scroll", this.menuContainerScrollEvent);
+        }
+      }
+    }, {
+      key: "debounce",
+      value: function debounce(func, wait, immediate) {
+        var _arguments = arguments,
+            _this2 = this;
+
+        var timeout;
+        return function () {
+          var context = _this2,
+              args = _arguments;
+
+          var later = function later() {
+            timeout = null;
+            if (!immediate) func.apply(context, args);
+          };
+
+          var callNow = immediate && !timeout;
+          clearTimeout(timeout);
+          timeout = setTimeout(later, wait);
+          if (callNow) func.apply(context, args);
+        };
+      }
+    }]);
+
+    return TributeMenuEvents;
+  }();
+
+  var TributeRange = /*#__PURE__*/function () {
+    function TributeRange(tribute) {
+      _classCallCheck(this, TributeRange);
+
+      this.tribute = tribute;
+      this.tribute.range = this;
+    }
+
+    _createClass(TributeRange, [{
+      key: "getDocument",
+      value: function getDocument() {
+        var iframe;
+
+        if (this.tribute.current.collection) {
+          iframe = this.tribute.current.collection.iframe;
+        }
+
+        if (!iframe) {
+          return document;
+        }
+
+        return iframe.contentWindow.document;
+      }
+    }, {
+      key: "positionMenuAtCaret",
+      value: function positionMenuAtCaret(scrollTo) {
+        var _this = this;
+
+        var context = this.tribute.current,
+            coordinates;
+        var info = this.getTriggerInfo(false, this.tribute.hasTrailingSpace, true, this.tribute.allowSpaces, this.tribute.autocompleteMode);
+
+        if (typeof info !== 'undefined') {
+          if (!this.tribute.positionMenu) {
+            this.tribute.menu.style.cssText = "display: block;";
+            return;
+          }
+
+          if (!this.isContentEditable(context.element)) {
+            coordinates = this.getTextAreaOrInputUnderlinePosition(this.tribute.current.element, info.mentionPosition);
+          } else {
+            coordinates = this.getContentEditableCaretPosition(info.mentionPosition);
+          }
+
+          this.tribute.menu.style.cssText = "top: ".concat(coordinates.top, "px;\n                                     left: ").concat(coordinates.left, "px;\n                                     right: ").concat(coordinates.right, "px;\n                                     bottom: ").concat(coordinates.bottom, "px;\n                                     position: absolute;\n                                     display: block;");
+
+          if (coordinates.left === 'auto') {
+            this.tribute.menu.style.left = 'auto';
+          }
+
+          if (coordinates.top === 'auto') {
+            this.tribute.menu.style.top = 'auto';
+          }
+
+          if (scrollTo) this.scrollIntoView();
+          window.setTimeout(function () {
+            var menuDimensions = {
+              width: _this.tribute.menu.offsetWidth,
+              height: _this.tribute.menu.offsetHeight
+            };
+
+            var menuIsOffScreen = _this.isMenuOffScreen(coordinates, menuDimensions);
+
+            var menuIsOffScreenHorizontally = window.innerWidth > menuDimensions.width && (menuIsOffScreen.left || menuIsOffScreen.right);
+            var menuIsOffScreenVertically = window.innerHeight > menuDimensions.height && (menuIsOffScreen.top || menuIsOffScreen.bottom);
+
+            if (menuIsOffScreenHorizontally || menuIsOffScreenVertically) {
+              _this.tribute.menu.style.cssText = 'display: none';
+
+              _this.positionMenuAtCaret(scrollTo);
+            }
+          }, 0);
+        } else {
+          this.tribute.menu.style.cssText = 'display: none';
+        }
+      }
+    }, {
+      key: "selectElement",
+      value: function selectElement(targetElement, path, offset) {
+        var range;
+        var elem = targetElement;
+
+        if (path) {
+          for (var i = 0; i < path.length; i++) {
+            elem = elem.childNodes[path[i]];
+
+            if (elem === undefined) {
+              return;
+            }
+
+            while (elem.length < offset) {
+              offset -= elem.length;
+              elem = elem.nextSibling;
+            }
+
+            if (elem.childNodes.length === 0 && !elem.length) {
+              elem = elem.previousSibling;
+            }
+          }
+        }
+
+        var sel = this.getWindowSelection();
+        range = this.getDocument().createRange();
+        range.setStart(elem, offset);
+        range.setEnd(elem, offset);
+        range.collapse(true);
+
+        try {
+          sel.removeAllRanges();
+        } catch (error) {}
+
+        sel.addRange(range);
+        targetElement.focus();
+      }
+    }, {
+      key: "replaceTriggerText",
+      value: function replaceTriggerText(text, requireLeadingSpace, hasTrailingSpace, originalEvent, item) {
+        var info = this.getTriggerInfo(true, hasTrailingSpace, requireLeadingSpace, this.tribute.allowSpaces, this.tribute.autocompleteMode);
+
+        if (info !== undefined) {
+          var context = this.tribute.current;
+          var replaceEvent = new CustomEvent('tribute-replaced', {
+            detail: {
+              item: item,
+              instance: context,
+              context: info,
+              event: originalEvent
+            }
+          });
+
+          if (!this.isContentEditable(context.element)) {
+            var myField = this.tribute.current.element;
+            var textSuffix = typeof this.tribute.replaceTextSuffix == 'string' ? this.tribute.replaceTextSuffix : ' ';
+            text += textSuffix;
+            var startPos = info.mentionPosition;
+            var endPos = info.mentionPosition + info.mentionText.length + textSuffix.length;
+
+            if (!this.tribute.autocompleteMode) {
+              endPos += info.mentionTriggerChar.length - 1;
+            }
+
+            myField.value = myField.value.substring(0, startPos) + text + myField.value.substring(endPos, myField.value.length);
+            myField.selectionStart = startPos + text.length;
+            myField.selectionEnd = startPos + text.length;
+          } else {
+            // add a space to the end of the pasted text
+            var _textSuffix = typeof this.tribute.replaceTextSuffix == 'string' ? this.tribute.replaceTextSuffix : '\xA0';
+
+            text += _textSuffix;
+
+            var _endPos = info.mentionPosition + info.mentionText.length;
+
+            if (!this.tribute.autocompleteMode) {
+              _endPos += info.mentionTriggerChar.length;
+            }
+
+            this.pasteHtml(text, info.mentionPosition, _endPos);
+          }
+
+          context.element.dispatchEvent(new CustomEvent('input', {
+            bubbles: true
+          }));
+          context.element.dispatchEvent(replaceEvent);
+        }
+      }
+    }, {
+      key: "pasteHtml",
+      value: function pasteHtml(html, startPos, endPos) {
+        var range, sel;
+        sel = this.getWindowSelection();
+        range = this.getDocument().createRange();
+        range.setStart(sel.anchorNode, startPos);
+        range.setEnd(sel.anchorNode, endPos);
+        range.deleteContents();
+        var el = this.getDocument().createElement('div');
+        el.innerHTML = html;
+        var frag = this.getDocument().createDocumentFragment(),
+            node,
+            lastNode;
+
+        while (node = el.firstChild) {
+          lastNode = frag.appendChild(node);
+        }
+
+        range.insertNode(frag); // Preserve the selection
+
+        if (lastNode) {
+          range = range.cloneRange();
+          range.setStartAfter(lastNode);
+          range.collapse(true);
+          sel.removeAllRanges();
+          sel.addRange(range);
+        }
+      }
+    }, {
+      key: "getWindowSelection",
+      value: function getWindowSelection() {
+        if (this.tribute.collection.iframe) {
+          return this.tribute.collection.iframe.contentWindow.getSelection();
+        }
+
+        return window.getSelection();
+      }
+    }, {
+      key: "getNodePositionInParent",
+      value: function getNodePositionInParent(element) {
+        if (element.parentNode === null) {
+          return 0;
+        }
+
+        for (var i = 0; i < element.parentNode.childNodes.length; i++) {
+          var node = element.parentNode.childNodes[i];
+
+          if (node === element) {
+            return i;
+          }
+        }
+      }
+    }, {
+      key: "getContentEditableSelectedPath",
+      value: function getContentEditableSelectedPath(ctx) {
+        var sel = this.getWindowSelection();
+        var selected = sel.anchorNode;
+        var path = [];
+        var offset;
+
+        if (selected != null) {
+          var i;
+          var ce = selected.contentEditable;
+
+          while (selected !== null && ce !== 'true') {
+            i = this.getNodePositionInParent(selected);
+            path.push(i);
+            selected = selected.parentNode;
+
+            if (selected !== null) {
+              ce = selected.contentEditable;
+            }
+          }
+
+          path.reverse(); // getRangeAt may not exist, need alternative
+
+          offset = sel.getRangeAt(0).startOffset;
+          return {
+            selected: selected,
+            path: path,
+            offset: offset
+          };
+        }
+      }
+    }, {
+      key: "getTextPrecedingCurrentSelection",
+      value: function getTextPrecedingCurrentSelection() {
+        var context = this.tribute.current,
+            text = '';
+
+        if (!this.isContentEditable(context.element)) {
+          var textComponent = this.tribute.current.element;
+
+          if (textComponent) {
+            var startPos = textComponent.selectionStart;
+
+            if (textComponent.value && startPos >= 0) {
+              text = textComponent.value.substring(0, startPos);
+            }
+          }
+        } else {
+          var selectedElem = this.getWindowSelection().anchorNode;
+
+          if (selectedElem != null) {
+            var workingNodeContent = selectedElem.textContent;
+            var selectStartOffset = this.getWindowSelection().getRangeAt(0).startOffset;
+
+            if (workingNodeContent && selectStartOffset >= 0) {
+              text = workingNodeContent.substring(0, selectStartOffset);
+            }
+          }
+        }
+
+        return text;
+      }
+    }, {
+      key: "getLastWordInText",
+      value: function getLastWordInText(text) {
+        text = text.replace(/\u00A0/g, ' '); // https://stackoverflow.com/questions/29850407/how-do-i-replace-unicode-character-u00a0-with-a-space-in-javascript
+
+        var wordsArray;
+
+        if (this.tribute.autocompleteSeparator) {
+          wordsArray = text.split(this.tribute.autocompleteSeparator);
+        } else {
+          wordsArray = text.split(/\s+/);
+        }
+
+        var worldsCount = wordsArray.length - 1;
+        return wordsArray[worldsCount].trim();
+      }
+    }, {
+      key: "getTriggerInfo",
+      value: function getTriggerInfo(menuAlreadyActive, hasTrailingSpace, requireLeadingSpace, allowSpaces, isAutocomplete) {
+        var _this2 = this;
+
+        var ctx = this.tribute.current;
+        var selected, path, offset;
+
+        if (!this.isContentEditable(ctx.element)) {
+          selected = this.tribute.current.element;
+        } else {
+          var selectionInfo = this.getContentEditableSelectedPath(ctx);
+
+          if (selectionInfo) {
+            selected = selectionInfo.selected;
+            path = selectionInfo.path;
+            offset = selectionInfo.offset;
+          }
+        }
+
+        var effectiveRange = this.getTextPrecedingCurrentSelection();
+        var lastWordOfEffectiveRange = this.getLastWordInText(effectiveRange);
+
+        if (isAutocomplete) {
+          return {
+            mentionPosition: effectiveRange.length - lastWordOfEffectiveRange.length,
+            mentionText: lastWordOfEffectiveRange,
+            mentionSelectedElement: selected,
+            mentionSelectedPath: path,
+            mentionSelectedOffset: offset
+          };
+        }
+
+        if (effectiveRange !== undefined && effectiveRange !== null) {
+          var mostRecentTriggerCharPos = -1;
+          var triggerChar;
+          this.tribute.collection.forEach(function (config) {
+            var c = config.trigger;
+            var idx = config.requireLeadingSpace ? _this2.lastIndexWithLeadingSpace(effectiveRange, c) : effectiveRange.lastIndexOf(c);
+
+            if (idx > mostRecentTriggerCharPos) {
+              mostRecentTriggerCharPos = idx;
+              triggerChar = c;
+              requireLeadingSpace = config.requireLeadingSpace;
+            }
+          });
+
+          if (mostRecentTriggerCharPos >= 0 && (mostRecentTriggerCharPos === 0 || !requireLeadingSpace || /[\xA0\s]/g.test(effectiveRange.substring(mostRecentTriggerCharPos - 1, mostRecentTriggerCharPos)))) {
+            var currentTriggerSnippet = effectiveRange.substring(mostRecentTriggerCharPos + triggerChar.length, effectiveRange.length);
+            triggerChar = effectiveRange.substring(mostRecentTriggerCharPos, mostRecentTriggerCharPos + triggerChar.length);
+            var firstSnippetChar = currentTriggerSnippet.substring(0, 1);
+            var leadingSpace = currentTriggerSnippet.length > 0 && (firstSnippetChar === ' ' || firstSnippetChar === '\xA0');
+
+            if (hasTrailingSpace) {
+              currentTriggerSnippet = currentTriggerSnippet.trim();
+            }
+
+            var regex = allowSpaces ? /[^\S ]/g : /[\xA0\s]/g;
+            this.tribute.hasTrailingSpace = regex.test(currentTriggerSnippet);
+
+            if (!leadingSpace && (menuAlreadyActive || !regex.test(currentTriggerSnippet))) {
+              return {
+                mentionPosition: mostRecentTriggerCharPos,
+                mentionText: currentTriggerSnippet,
+                mentionSelectedElement: selected,
+                mentionSelectedPath: path,
+                mentionSelectedOffset: offset,
+                mentionTriggerChar: triggerChar
+              };
+            }
+          }
+        }
+      }
+    }, {
+      key: "lastIndexWithLeadingSpace",
+      value: function lastIndexWithLeadingSpace(str, trigger) {
+        var reversedStr = str.split('').reverse().join('');
+        var index = -1;
+
+        for (var cidx = 0, len = str.length; cidx < len; cidx++) {
+          var firstChar = cidx === str.length - 1;
+          var leadingSpace = /\s/.test(reversedStr[cidx + 1]);
+          var match = true;
+
+          for (var triggerIdx = trigger.length - 1; triggerIdx >= 0; triggerIdx--) {
+            if (trigger[triggerIdx] !== reversedStr[cidx - triggerIdx]) {
+              match = false;
+              break;
+            }
+          }
+
+          if (match && (firstChar || leadingSpace)) {
+            index = str.length - 1 - cidx;
+            break;
+          }
+        }
+
+        return index;
+      }
+    }, {
+      key: "isContentEditable",
+      value: function isContentEditable(element) {
+        return element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA';
+      }
+    }, {
+      key: "isMenuOffScreen",
+      value: function isMenuOffScreen(coordinates, menuDimensions) {
+        var windowWidth = window.innerWidth;
+        var windowHeight = window.innerHeight;
+        var doc = document.documentElement;
+        var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+        var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+        var menuTop = typeof coordinates.top === 'number' ? coordinates.top : windowTop + windowHeight - coordinates.bottom - menuDimensions.height;
+        var menuRight = typeof coordinates.right === 'number' ? coordinates.right : coordinates.left + menuDimensions.width;
+        var menuBottom = typeof coordinates.bottom === 'number' ? coordinates.bottom : coordinates.top + menuDimensions.height;
+        var menuLeft = typeof coordinates.left === 'number' ? coordinates.left : windowLeft + windowWidth - coordinates.right - menuDimensions.width;
+        return {
+          top: menuTop < Math.floor(windowTop),
+          right: menuRight > Math.ceil(windowLeft + windowWidth),
+          bottom: menuBottom > Math.ceil(windowTop + windowHeight),
+          left: menuLeft < Math.floor(windowLeft)
+        };
+      }
+    }, {
+      key: "getMenuDimensions",
+      value: function getMenuDimensions() {
+        // Width of the menu depends of its contents and position
+        // We must check what its width would be without any obstruction
+        // This way, we can achieve good positioning for flipping the menu
+        var dimensions = {
+          width: null,
+          height: null
+        };
+        this.tribute.menu.style.cssText = "top: 0px;\n                                 left: 0px;\n                                 position: fixed;\n                                 display: block;\n                                 visibility; hidden;";
+        dimensions.width = this.tribute.menu.offsetWidth;
+        dimensions.height = this.tribute.menu.offsetHeight;
+        this.tribute.menu.style.cssText = "display: none;";
+        return dimensions;
+      }
+    }, {
+      key: "getTextAreaOrInputUnderlinePosition",
+      value: function getTextAreaOrInputUnderlinePosition(element, position, flipped) {
+        var properties = ['direction', 'boxSizing', 'width', 'height', 'overflowX', 'overflowY', 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust', 'lineHeight', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', 'letterSpacing', 'wordSpacing'];
+        var isFirefox = window.mozInnerScreenX !== null;
+        var div = this.getDocument().createElement('div');
+        div.id = 'input-textarea-caret-position-mirror-div';
+        this.getDocument().body.appendChild(div);
+        var style = div.style;
+        var computed = window.getComputedStyle ? getComputedStyle(element) : element.currentStyle;
+        style.whiteSpace = 'pre-wrap';
+
+        if (element.nodeName !== 'INPUT') {
+          style.wordWrap = 'break-word';
+        } // position off-screen
+
+
+        style.position = 'absolute';
+        style.visibility = 'hidden'; // transfer the element's properties to the div
+
+        properties.forEach(function (prop) {
+          style[prop] = computed[prop];
+        });
+
+        if (isFirefox) {
+          style.width = "".concat(parseInt(computed.width) - 2, "px");
+          if (element.scrollHeight > parseInt(computed.height)) style.overflowY = 'scroll';
+        } else {
+          style.overflow = 'hidden';
+        }
+
+        div.textContent = element.value.substring(0, position);
+
+        if (element.nodeName === 'INPUT') {
+          div.textContent = div.textContent.replace(/\s/g, ' ');
+        }
+
+        var span = this.getDocument().createElement('span');
+        span.textContent = element.value.substring(position) || '.';
+        div.appendChild(span);
+        var rect = element.getBoundingClientRect();
+        var doc = document.documentElement;
+        var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+        var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+        var top = 0;
+        var left = 0;
+
+        if (this.menuContainerIsBody) {
+          top = rect.top;
+          left = rect.left;
+        }
+
+        var coordinates = {
+          top: top + windowTop + span.offsetTop + parseInt(computed.borderTopWidth) + parseInt(computed.fontSize) - element.scrollTop,
+          left: left + windowLeft + span.offsetLeft + parseInt(computed.borderLeftWidth)
+        };
+        var windowWidth = window.innerWidth;
+        var windowHeight = window.innerHeight;
+        var menuDimensions = this.getMenuDimensions();
+        var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+        if (menuIsOffScreen.right) {
+          coordinates.right = windowWidth - coordinates.left;
+          coordinates.left = 'auto';
+        }
+
+        var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
+
+        if (menuIsOffScreen.bottom) {
+          var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
+          var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
+          coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top - span.offsetTop);
+          coordinates.top = 'auto';
+        }
+
+        menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+        if (menuIsOffScreen.left) {
+          coordinates.left = windowWidth > menuDimensions.width ? windowLeft + windowWidth - menuDimensions.width : windowLeft;
+          delete coordinates.right;
+        }
+
+        if (menuIsOffScreen.top) {
+          coordinates.top = windowHeight > menuDimensions.height ? windowTop + windowHeight - menuDimensions.height : windowTop;
+          delete coordinates.bottom;
+        }
+
+        this.getDocument().body.removeChild(div);
+        return coordinates;
+      }
+    }, {
+      key: "getContentEditableCaretPosition",
+      value: function getContentEditableCaretPosition(selectedNodePosition) {
+        var range;
+        var sel = this.getWindowSelection();
+        range = this.getDocument().createRange();
+        range.setStart(sel.anchorNode, selectedNodePosition);
+        range.setEnd(sel.anchorNode, selectedNodePosition);
+        range.collapse(false);
+        var rect = range.getBoundingClientRect();
+        var doc = document.documentElement;
+        var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+        var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+        var left = rect.left;
+        var top = rect.top;
+        var coordinates = {
+          left: left + windowLeft,
+          top: top + rect.height + windowTop
+        };
+        var windowWidth = window.innerWidth;
+        var windowHeight = window.innerHeight;
+        var menuDimensions = this.getMenuDimensions();
+        var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+        if (menuIsOffScreen.right) {
+          coordinates.left = 'auto';
+          coordinates.right = windowWidth - rect.left - windowLeft;
+        }
+
+        var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
+
+        if (menuIsOffScreen.bottom) {
+          var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
+          var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
+          coordinates.top = 'auto';
+          coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top);
+        }
+
+        menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+        if (menuIsOffScreen.left) {
+          coordinates.left = windowWidth > menuDimensions.width ? windowLeft + windowWidth - menuDimensions.width : windowLeft;
+          delete coordinates.right;
+        }
+
+        if (menuIsOffScreen.top) {
+          coordinates.top = windowHeight > menuDimensions.height ? windowTop + windowHeight - menuDimensions.height : windowTop;
+          delete coordinates.bottom;
+        }
+
+        if (!this.menuContainerIsBody) {
+          coordinates.left = coordinates.left ? coordinates.left - this.tribute.menuContainer.offsetLeft : coordinates.left;
+          coordinates.top = coordinates.top ? coordinates.top - this.tribute.menuContainer.offsetTop : coordinates.top;
+        }
+
+        return coordinates;
+      }
+    }, {
+      key: "scrollIntoView",
+      value: function scrollIntoView(elem) {
+        var reasonableBuffer = 20,
+            clientRect;
+        var maxScrollDisplacement = 100;
+        var e = this.menu;
+        if (typeof e === 'undefined') return;
+
+        while (clientRect === undefined || clientRect.height === 0) {
+          clientRect = e.getBoundingClientRect();
+
+          if (clientRect.height === 0) {
+            e = e.childNodes[0];
+
+            if (e === undefined || !e.getBoundingClientRect) {
+              return;
+            }
+          }
+        }
+
+        var elemTop = clientRect.top;
+        var elemBottom = elemTop + clientRect.height;
+
+        if (elemTop < 0) {
+          window.scrollTo(0, window.pageYOffset + clientRect.top - reasonableBuffer);
+        } else if (elemBottom > window.innerHeight) {
+          var maxY = window.pageYOffset + clientRect.top - reasonableBuffer;
+
+          if (maxY - window.pageYOffset > maxScrollDisplacement) {
+            maxY = window.pageYOffset + maxScrollDisplacement;
+          }
+
+          var targetY = window.pageYOffset - (window.innerHeight - elemBottom);
+
+          if (targetY > maxY) {
+            targetY = maxY;
+          }
+
+          window.scrollTo(0, targetY);
+        }
+      }
+    }, {
+      key: "menuContainerIsBody",
+      get: function get() {
+        return this.tribute.menuContainer === document.body || !this.tribute.menuContainer;
+      }
+    }]);
+
+    return TributeRange;
+  }();
+
+  // Thanks to https://github.com/mattyork/fuzzy
+  var TributeSearch = /*#__PURE__*/function () {
+    function TributeSearch(tribute) {
+      _classCallCheck(this, TributeSearch);
+
+      this.tribute = tribute;
+      this.tribute.search = this;
+    }
+
+    _createClass(TributeSearch, [{
+      key: "simpleFilter",
+      value: function simpleFilter(pattern, array) {
+        var _this = this;
+
+        return array.filter(function (string) {
+          return _this.test(pattern, string);
+        });
+      }
+    }, {
+      key: "test",
+      value: function test(pattern, string) {
+        return this.match(pattern, string) !== null;
+      }
+    }, {
+      key: "match",
+      value: function match(pattern, string, opts) {
+        opts = opts || {};
+        var len = string.length,
+            pre = opts.pre || '',
+            post = opts.post || '',
+            compareString = opts.caseSensitive && string || string.toLowerCase();
+
+        if (opts.skip) {
+          return {
+            rendered: string,
+            score: 0
+          };
+        }
+
+        pattern = opts.caseSensitive && pattern || pattern.toLowerCase();
+        var patternCache = this.traverse(compareString, pattern, 0, 0, []);
+
+        if (!patternCache) {
+          return null;
+        }
+
+        return {
+          rendered: this.render(string, patternCache.cache, pre, post),
+          score: patternCache.score
+        };
+      }
+    }, {
+      key: "traverse",
+      value: function traverse(string, pattern, stringIndex, patternIndex, patternCache) {
+        if (this.tribute.autocompleteSeparator) {
+          // if the pattern search at end
+          pattern = pattern.split(this.tribute.autocompleteSeparator).splice(-1)[0];
+        }
+
+        if (pattern.length === patternIndex) {
+          // calculate score and copy the cache containing the indices where it's found
+          return {
+            score: this.calculateScore(patternCache),
+            cache: patternCache.slice()
+          };
+        } // if string at end or remaining pattern > remaining string
+
+
+        if (string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) {
+          return undefined;
+        }
+
+        var c = pattern[patternIndex];
+        var index = string.indexOf(c, stringIndex);
+        var best, temp;
+
+        while (index > -1) {
+          patternCache.push(index);
+          temp = this.traverse(string, pattern, index + 1, patternIndex + 1, patternCache);
+          patternCache.pop(); // if downstream traversal failed, return best answer so far
+
+          if (!temp) {
+            return best;
+          }
+
+          if (!best || best.score < temp.score) {
+            best = temp;
+          }
+
+          index = string.indexOf(c, index + 1);
+        }
+
+        return best;
+      }
+    }, {
+      key: "calculateScore",
+      value: function calculateScore(patternCache) {
+        var score = 0;
+        var temp = 1;
+        patternCache.forEach(function (index, i) {
+          if (i > 0) {
+            if (patternCache[i - 1] + 1 === index) {
+              temp += temp + 1;
+            } else {
+              temp = 1;
+            }
+          }
+
+          score += temp;
+        });
+        return score;
+      }
+    }, {
+      key: "render",
+      value: function render(string, indices, pre, post) {
+        var rendered = string.substring(0, indices[0]);
+        indices.forEach(function (index, i) {
+          rendered += pre + string[index] + post + string.substring(index + 1, indices[i + 1] ? indices[i + 1] : string.length);
+        });
+        return rendered;
+      }
+    }, {
+      key: "filter",
+      value: function filter(pattern, arr, opts) {
+        var _this2 = this;
+
+        opts = opts || {};
+        return arr.reduce(function (prev, element, idx, arr) {
+          var str = element;
+
+          if (opts.extract) {
+            str = opts.extract(element);
+
+            if (!str) {
+              // take care of undefineds / nulls / etc.
+              str = '';
+            }
+          }
+
+          var rendered = _this2.match(pattern, str, opts);
+
+          if (rendered != null) {
+            prev[prev.length] = {
+              string: rendered.rendered,
+              score: rendered.score,
+              index: idx,
+              original: element
+            };
+          }
+
+          return prev;
+        }, []).sort(function (a, b) {
+          var compare = b.score - a.score;
+          if (compare) return compare;
+          return a.index - b.index;
+        });
+      }
+    }]);
+
+    return TributeSearch;
+  }();
+
+  var Tribute = /*#__PURE__*/function () {
+    function Tribute(_ref) {
+      var _this = this;
+
+      var _ref$values = _ref.values,
+          values = _ref$values === void 0 ? null : _ref$values,
+          _ref$loadingItemTempl = _ref.loadingItemTemplate,
+          loadingItemTemplate = _ref$loadingItemTempl === void 0 ? null : _ref$loadingItemTempl,
+          _ref$iframe = _ref.iframe,
+          iframe = _ref$iframe === void 0 ? null : _ref$iframe,
+          _ref$selectClass = _ref.selectClass,
+          selectClass = _ref$selectClass === void 0 ? "highlight" : _ref$selectClass,
+          _ref$containerClass = _ref.containerClass,
+          containerClass = _ref$containerClass === void 0 ? "tribute-container" : _ref$containerClass,
+          _ref$itemClass = _ref.itemClass,
+          itemClass = _ref$itemClass === void 0 ? "" : _ref$itemClass,
+          _ref$trigger = _ref.trigger,
+          trigger = _ref$trigger === void 0 ? "@" : _ref$trigger,
+          _ref$autocompleteMode = _ref.autocompleteMode,
+          autocompleteMode = _ref$autocompleteMode === void 0 ? false : _ref$autocompleteMode,
+          _ref$autocompleteSepa = _ref.autocompleteSeparator,
+          autocompleteSeparator = _ref$autocompleteSepa === void 0 ? null : _ref$autocompleteSepa,
+          _ref$selectTemplate = _ref.selectTemplate,
+          selectTemplate = _ref$selectTemplate === void 0 ? null : _ref$selectTemplate,
+          _ref$menuItemTemplate = _ref.menuItemTemplate,
+          menuItemTemplate = _ref$menuItemTemplate === void 0 ? null : _ref$menuItemTemplate,
+          _ref$lookup = _ref.lookup,
+          lookup = _ref$lookup === void 0 ? "key" : _ref$lookup,
+          _ref$fillAttr = _ref.fillAttr,
+          fillAttr = _ref$fillAttr === void 0 ? "value" : _ref$fillAttr,
+          _ref$collection = _ref.collection,
+          collection = _ref$collection === void 0 ? null : _ref$collection,
+          _ref$menuContainer = _ref.menuContainer,
+          menuContainer = _ref$menuContainer === void 0 ? null : _ref$menuContainer,
+          _ref$noMatchTemplate = _ref.noMatchTemplate,
+          noMatchTemplate = _ref$noMatchTemplate === void 0 ? null : _ref$noMatchTemplate,
+          _ref$requireLeadingSp = _ref.requireLeadingSpace,
+          requireLeadingSpace = _ref$requireLeadingSp === void 0 ? true : _ref$requireLeadingSp,
+          _ref$allowSpaces = _ref.allowSpaces,
+          allowSpaces = _ref$allowSpaces === void 0 ? false : _ref$allowSpaces,
+          _ref$replaceTextSuffi = _ref.replaceTextSuffix,
+          replaceTextSuffix = _ref$replaceTextSuffi === void 0 ? null : _ref$replaceTextSuffi,
+          _ref$positionMenu = _ref.positionMenu,
+          positionMenu = _ref$positionMenu === void 0 ? true : _ref$positionMenu,
+          _ref$spaceSelectsMatc = _ref.spaceSelectsMatch,
+          spaceSelectsMatch = _ref$spaceSelectsMatc === void 0 ? false : _ref$spaceSelectsMatc,
+          _ref$searchOpts = _ref.searchOpts,
+          searchOpts = _ref$searchOpts === void 0 ? {} : _ref$searchOpts,
+          _ref$menuItemLimit = _ref.menuItemLimit,
+          menuItemLimit = _ref$menuItemLimit === void 0 ? null : _ref$menuItemLimit,
+          _ref$menuShowMinLengt = _ref.menuShowMinLength,
+          menuShowMinLength = _ref$menuShowMinLengt === void 0 ? 0 : _ref$menuShowMinLengt;
+
+      _classCallCheck(this, Tribute);
+
+      this.autocompleteMode = autocompleteMode;
+      this.autocompleteSeparator = autocompleteSeparator;
+      this.menuSelected = 0;
+      this.current = {};
+      this.inputEvent = false;
+      this.isActive = false;
+      this.menuContainer = menuContainer;
+      this.allowSpaces = allowSpaces;
+      this.replaceTextSuffix = replaceTextSuffix;
+      this.positionMenu = positionMenu;
+      this.hasTrailingSpace = false;
+      this.spaceSelectsMatch = spaceSelectsMatch;
+
+      if (this.autocompleteMode) {
+        trigger = "";
+        allowSpaces = false;
+      }
+
+      if (values) {
+        this.collection = [{
+          // symbol that starts the lookup
+          trigger: trigger,
+          // is it wrapped in an iframe
+          iframe: iframe,
+          // class applied to selected item
+          selectClass: selectClass,
+          // class applied to the Container
+          containerClass: containerClass,
+          // class applied to each item
+          itemClass: itemClass,
+          // function called on select that retuns the content to insert
+          selectTemplate: (selectTemplate || Tribute.defaultSelectTemplate).bind(this),
+          // function called that returns content for an item
+          menuItemTemplate: (menuItemTemplate || Tribute.defaultMenuItemTemplate).bind(this),
+          // function called when menu is empty, disables hiding of menu.
+          noMatchTemplate: function (t) {
+            if (typeof t === "string") {
+              if (t.trim() === "") return null;
+              return t;
+            }
+
+            if (typeof t === "function") {
+              return t.bind(_this);
+            }
+
+            return noMatchTemplate || function () {
+              return "<li>No Match Found!</li>";
+            }.bind(_this);
+          }(noMatchTemplate),
+          // column to search against in the object
+          lookup: lookup,
+          // column that contains the content to insert by default
+          fillAttr: fillAttr,
+          // array of objects or a function returning an array of objects
+          values: values,
+          // useful for when values is an async function
+          loadingItemTemplate: loadingItemTemplate,
+          requireLeadingSpace: requireLeadingSpace,
+          searchOpts: searchOpts,
+          menuItemLimit: menuItemLimit,
+          menuShowMinLength: menuShowMinLength
+        }];
+      } else if (collection) {
+        if (this.autocompleteMode) console.warn("Tribute in autocomplete mode does not work for collections");
+        this.collection = collection.map(function (item) {
+          return {
+            trigger: item.trigger || trigger,
+            iframe: item.iframe || iframe,
+            selectClass: item.selectClass || selectClass,
+            containerClass: item.containerClass || containerClass,
+            itemClass: item.itemClass || itemClass,
+            selectTemplate: (item.selectTemplate || Tribute.defaultSelectTemplate).bind(_this),
+            menuItemTemplate: (item.menuItemTemplate || Tribute.defaultMenuItemTemplate).bind(_this),
+            // function called when menu is empty, disables hiding of menu.
+            noMatchTemplate: function (t) {
+              if (typeof t === "string") {
+                if (t.trim() === "") return null;
+                return t;
+              }
+
+              if (typeof t === "function") {
+                return t.bind(_this);
+              }
+
+              return noMatchTemplate || function () {
+                return "<li>No Match Found!</li>";
+              }.bind(_this);
+            }(noMatchTemplate),
+            lookup: item.lookup || lookup,
+            fillAttr: item.fillAttr || fillAttr,
+            values: item.values,
+            loadingItemTemplate: item.loadingItemTemplate,
+            requireLeadingSpace: item.requireLeadingSpace,
+            searchOpts: item.searchOpts || searchOpts,
+            menuItemLimit: item.menuItemLimit || menuItemLimit,
+            menuShowMinLength: item.menuShowMinLength || menuShowMinLength
+          };
+        });
+      } else {
+        throw new Error("[Tribute] No collection specified.");
+      }
+
+      new TributeRange(this);
+      new TributeEvents(this);
+      new TributeMenuEvents(this);
+      new TributeSearch(this);
+    }
+
+    _createClass(Tribute, [{
+      key: "triggers",
+      value: function triggers() {
+        return this.collection.map(function (config) {
+          return config.trigger;
+        });
+      }
+    }, {
+      key: "attach",
+      value: function attach(el) {
+        if (!el) {
+          throw new Error("[Tribute] Must pass in a DOM node or NodeList.");
+        } // Check if it is a jQuery collection
+
+
+        if (typeof jQuery !== "undefined" && el instanceof jQuery) {
+          el = el.get();
+        } // Is el an Array/Array-like object?
+
+
+        if (el.constructor === NodeList || el.constructor === HTMLCollection || el.constructor === Array) {
+          var length = el.length;
+
+          for (var i = 0; i < length; ++i) {
+            this._attach(el[i]);
+          }
+        } else {
+          this._attach(el);
+        }
+      }
+    }, {
+      key: "_attach",
+      value: function _attach(el) {
+        if (el.hasAttribute("data-tribute")) {
+          console.warn("Tribute was already bound to " + el.nodeName);
+        }
+
+        this.ensureEditable(el);
+        this.events.bind(el);
+        el.setAttribute("data-tribute", true);
+      }
+    }, {
+      key: "ensureEditable",
+      value: function ensureEditable(element) {
+        if (Tribute.inputTypes().indexOf(element.nodeName) === -1) {
+          if (element.contentEditable) {
+            element.contentEditable = true;
+          } else {
+            throw new Error("[Tribute] Cannot bind to " + element.nodeName);
+          }
+        }
+      }
+    }, {
+      key: "createMenu",
+      value: function createMenu(containerClass) {
+        var wrapper = this.range.getDocument().createElement("div"),
+            ul = this.range.getDocument().createElement("ul");
+        wrapper.className = containerClass;
+        wrapper.appendChild(ul);
+
+        if (this.menuContainer) {
+          return this.menuContainer.appendChild(wrapper);
+        }
+
+        return this.range.getDocument().body.appendChild(wrapper);
+      }
+    }, {
+      key: "showMenuFor",
+      value: function showMenuFor(element, scrollTo) {
+        var _this2 = this;
+
+        // Only proceed if menu isn't already shown for the current element & mentionText
+        if (this.isActive && this.current.element === element && this.current.mentionText === this.currentMentionTextSnapshot) {
+          return;
+        }
+
+        this.currentMentionTextSnapshot = this.current.mentionText; // create the menu if it doesn't exist.
+
+        if (!this.menu) {
+          this.menu = this.createMenu(this.current.collection.containerClass);
+          element.tributeMenu = this.menu;
+          this.menuEvents.bind(this.menu);
+        }
+
+        this.isActive = true;
+        this.menuSelected = 0;
+
+        if (!this.current.mentionText) {
+          this.current.mentionText = "";
+        }
+
+        var processValues = function processValues(values) {
+          // Tribute may not be active any more by the time the value callback returns
+          if (!_this2.isActive) {
+            return;
+          }
+
+          var items = _this2.search.filter(_this2.current.mentionText, values, {
+            pre: _this2.current.collection.searchOpts.pre || "<span>",
+            post: _this2.current.collection.searchOpts.post || "</span>",
+            skip: _this2.current.collection.searchOpts.skip,
+            extract: function extract(el) {
+              if (typeof _this2.current.collection.lookup === "string") {
+                return el[_this2.current.collection.lookup];
+              } else if (typeof _this2.current.collection.lookup === "function") {
+                return _this2.current.collection.lookup(el, _this2.current.mentionText);
+              } else {
+                throw new Error("Invalid lookup attribute, lookup must be string or function.");
+              }
+            }
+          });
+
+          if (_this2.current.collection.menuItemLimit) {
+            items = items.slice(0, _this2.current.collection.menuItemLimit);
+          }
+
+          _this2.current.filteredItems = items;
+
+          var ul = _this2.menu.querySelector("ul");
+
+          _this2.range.positionMenuAtCaret(scrollTo);
+
+          if (!items.length) {
+            var noMatchEvent = new CustomEvent("tribute-no-match", {
+              detail: _this2.menu
+            });
+
+            _this2.current.element.dispatchEvent(noMatchEvent);
+
+            if (typeof _this2.current.collection.noMatchTemplate === "function" && !_this2.current.collection.noMatchTemplate() || !_this2.current.collection.noMatchTemplate) {
+              _this2.hideMenu();
+            } else {
+              typeof _this2.current.collection.noMatchTemplate === "function" ? ul.innerHTML = _this2.current.collection.noMatchTemplate() : ul.innerHTML = _this2.current.collection.noMatchTemplate;
+            }
+
+            return;
+          }
+
+          ul.innerHTML = "";
+
+          var fragment = _this2.range.getDocument().createDocumentFragment();
+
+          items.forEach(function (item, index) {
+            var li = _this2.range.getDocument().createElement("li");
+
+            li.setAttribute("data-index", index);
+            li.className = _this2.current.collection.itemClass;
+            li.addEventListener("mousemove", function (e) {
+              var _this2$_findLiTarget = _this2._findLiTarget(e.target),
+                  _this2$_findLiTarget2 = _slicedToArray(_this2$_findLiTarget, 2),
+                  li = _this2$_findLiTarget2[0],
+                  index = _this2$_findLiTarget2[1];
+
+              if (e.movementY !== 0) {
+                _this2.events.setActiveLi(index);
+              }
+            });
+
+            if (_this2.menuSelected === index) {
+              li.classList.add(_this2.current.collection.selectClass);
+            }
+
+            li.innerHTML = _this2.current.collection.menuItemTemplate(item);
+            fragment.appendChild(li);
+          });
+          ul.appendChild(fragment);
+        };
+
+        if (typeof this.current.collection.values === "function") {
+          if (this.current.collection.loadingItemTemplate) {
+            this.menu.querySelector("ul").innerHTML = this.current.collection.loadingItemTemplate;
+            this.range.positionMenuAtCaret(scrollTo);
+          }
+
+          this.current.collection.values(this.current.mentionText, processValues);
+        } else {
+          processValues(this.current.collection.values);
+        }
+      }
+    }, {
+      key: "_findLiTarget",
+      value: function _findLiTarget(el) {
+        if (!el) return [];
+        var index = el.getAttribute("data-index");
+        return !index ? this._findLiTarget(el.parentNode) : [el, index];
+      }
+    }, {
+      key: "showMenuForCollection",
+      value: function showMenuForCollection(element, collectionIndex) {
+        if (element !== document.activeElement) {
+          this.placeCaretAtEnd(element);
+        }
+
+        this.current.collection = this.collection[collectionIndex || 0];
+        this.current.externalTrigger = true;
+        this.current.element = element;
+        if (element.isContentEditable) this.insertTextAtCursor(this.current.collection.trigger);else this.insertAtCaret(element, this.current.collection.trigger);
+        this.showMenuFor(element);
+      } // TODO: make sure this works for inputs/textareas
+
+    }, {
+      key: "placeCaretAtEnd",
+      value: function placeCaretAtEnd(el) {
+        el.focus();
+
+        if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
+          var range = document.createRange();
+          range.selectNodeContents(el);
+          range.collapse(false);
+          var sel = window.getSelection();
+          sel.removeAllRanges();
+          sel.addRange(range);
+        } else if (typeof document.body.createTextRange != "undefined") {
+          var textRange = document.body.createTextRange();
+          textRange.moveToElementText(el);
+          textRange.collapse(false);
+          textRange.select();
+        }
+      } // for contenteditable
+
+    }, {
+      key: "insertTextAtCursor",
+      value: function insertTextAtCursor(text) {
+        var sel, range;
+        sel = window.getSelection();
+        range = sel.getRangeAt(0);
+        range.deleteContents();
+        var textNode = document.createTextNode(text);
+        range.insertNode(textNode);
+        range.selectNodeContents(textNode);
+        range.collapse(false);
+        sel.removeAllRanges();
+        sel.addRange(range);
+      } // for regular inputs
+
+    }, {
+      key: "insertAtCaret",
+      value: function insertAtCaret(textarea, text) {
+        var scrollPos = textarea.scrollTop;
+        var caretPos = textarea.selectionStart;
+        var front = textarea.value.substring(0, caretPos);
+        var back = textarea.value.substring(textarea.selectionEnd, textarea.value.length);
+        textarea.value = front + text + back;
+        caretPos = caretPos + text.length;
+        textarea.selectionStart = caretPos;
+        textarea.selectionEnd = caretPos;
+        textarea.focus();
+        textarea.scrollTop = scrollPos;
+      }
+    }, {
+      key: "hideMenu",
+      value: function hideMenu() {
+        if (this.menu) {
+          this.menu.style.cssText = "display: none;";
+          this.isActive = false;
+          this.menuSelected = 0;
+          this.current = {};
+        }
+      }
+    }, {
+      key: "selectItemAtIndex",
+      value: function selectItemAtIndex(index, originalEvent) {
+        index = parseInt(index);
+        if (typeof index !== "number" || isNaN(index)) return;
+        var item = this.current.filteredItems[index];
+        var content = this.current.collection.selectTemplate(item);
+        if (content !== null) this.replaceText(content, originalEvent, item);
+      }
+    }, {
+      key: "replaceText",
+      value: function replaceText(content, originalEvent, item) {
+        this.range.replaceTriggerText(content, true, true, originalEvent, item);
+      }
+    }, {
+      key: "_append",
+      value: function _append(collection, newValues, replace) {
+        if (typeof collection.values === "function") {
+          throw new Error("Unable to append to values, as it is a function.");
+        } else if (!replace) {
+          collection.values = collection.values.concat(newValues);
+        } else {
+          collection.values = newValues;
+        }
+      }
+    }, {
+      key: "append",
+      value: function append(collectionIndex, newValues, replace) {
+        var index = parseInt(collectionIndex);
+        if (typeof index !== "number") throw new Error("please provide an index for the collection to update.");
+        var collection = this.collection[index];
+
+        this._append(collection, newValues, replace);
+      }
+    }, {
+      key: "appendCurrent",
+      value: function appendCurrent(newValues, replace) {
+        if (this.isActive) {
+          this._append(this.current.collection, newValues, replace);
+        } else {
+          throw new Error("No active state. Please use append instead and pass an index.");
+        }
+      }
+    }, {
+      key: "detach",
+      value: function detach(el) {
+        if (!el) {
+          throw new Error("[Tribute] Must pass in a DOM node or NodeList.");
+        } // Check if it is a jQuery collection
+
+
+        if (typeof jQuery !== "undefined" && el instanceof jQuery) {
+          el = el.get();
+        } // Is el an Array/Array-like object?
+
+
+        if (el.constructor === NodeList || el.constructor === HTMLCollection || el.constructor === Array) {
+          var length = el.length;
+
+          for (var i = 0; i < length; ++i) {
+            this._detach(el[i]);
+          }
+        } else {
+          this._detach(el);
+        }
+      }
+    }, {
+      key: "_detach",
+      value: function _detach(el) {
+        var _this3 = this;
+
+        this.events.unbind(el);
+
+        if (el.tributeMenu) {
+          this.menuEvents.unbind(el.tributeMenu);
+        }
+
+        setTimeout(function () {
+          el.removeAttribute("data-tribute");
+          _this3.isActive = false;
+
+          if (el.tributeMenu) {
+            el.tributeMenu.remove();
+          }
+        });
+      }
+    }, {
+      key: "isActive",
+      get: function get() {
+        return this._isActive;
+      },
+      set: function set(val) {
+        if (this._isActive != val) {
+          this._isActive = val;
+
+          if (this.current.element) {
+            var noMatchEvent = new CustomEvent("tribute-active-".concat(val));
+            this.current.element.dispatchEvent(noMatchEvent);
+          }
+        }
+      }
+    }], [{
+      key: "defaultSelectTemplate",
+      value: function defaultSelectTemplate(item) {
+        if (typeof item === "undefined") return "".concat(this.current.collection.trigger).concat(this.current.mentionText);
+
+        if (this.range.isContentEditable(this.current.element)) {
+          return '<span class="tribute-mention">' + (this.current.collection.trigger + item.original[this.current.collection.fillAttr]) + "</span>";
+        }
+
+        return this.current.collection.trigger + item.original[this.current.collection.fillAttr];
+      }
+    }, {
+      key: "defaultMenuItemTemplate",
+      value: function defaultMenuItemTemplate(matchItem) {
+        return matchItem.string;
+      }
+    }, {
+      key: "inputTypes",
+      value: function inputTypes() {
+        return ["TEXTAREA", "INPUT"];
+      }
+    }]);
+
+    return Tribute;
+  }();
+
+  /**
+   * Tribute.js
+   * Native ES6 JavaScript @mention Plugin
+   **/
+
+  return Tribute;
+
+})));
diff --git src/bp-friends/bp-friends-functions.php src/bp-friends/bp-friends-functions.php
index a2a906b5a..8aae0eac6 100644
--- src/bp-friends/bp-friends-functions.php
+++ src/bp-friends/bp-friends-functions.php
@@ -819,12 +819,14 @@ function bp_friends_prime_mentions_results() {
 		$result        = new stdClass();
 		$result->ID    = $user->user_nicename;
 		$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
+		$result->user_id = $user->ID;
 
 		if ( ! empty( $user->display_name ) && ! bp_disable_profile_sync() ) {
 			$result->name = $user->display_name;
 		} else {
 			$result->name = bp_core_get_user_displayname( $user->ID );
 		}
+		$result->search = "{$result->ID} {$result->name}";
 
 		$results[] = $result;
 	}
diff --git src/bp-templates/bp-nouveau/includes/messages/functions.php src/bp-templates/bp-nouveau/includes/messages/functions.php
index c10e9280a..2774185ae 100644
--- src/bp-templates/bp-nouveau/includes/messages/functions.php
+++ src/bp-templates/bp-nouveau/includes/messages/functions.php
@@ -49,7 +49,7 @@ function bp_nouveau_messages_register_scripts( $scripts = array() ) {
 	return array_merge( $scripts, array(
 		'bp-nouveau-messages-at' => array(
 			'file'         => buddypress()->plugin_url . 'bp-activity/js/mentions%s.js',
-			'dependencies' => array( 'bp-nouveau', 'jquery', 'jquery-atwho' ),
+			'dependencies' => array( 'bp-nouveau', 'jquery', 'bp-api-request', 'zurb-tribute' ),
 			'version'      => bp_get_version(),
 			'footer'       => true,
 		),
@@ -72,9 +72,6 @@ function bp_nouveau_messages_enqueue_scripts() {
 	}
 
 	wp_enqueue_script( 'bp-nouveau-messages' );
-
-	// Add The tiny MCE init specific function.
-	add_filter( 'tiny_mce_before_init', 'bp_nouveau_messages_at_on_tinymce_init', 10, 2 );
 }
 
 /**
@@ -314,18 +311,6 @@ function bp_nouveau_messages_mce_buttons( $buttons = array() ) {
 	return $buttons;
 }
 
-/**
- * @since 3.0.0
- */
-function bp_nouveau_messages_at_on_tinymce_init( $settings, $editor_id ) {
-	// We only apply the mentions init to the visual post editor in the WP dashboard.
-	if ( 'message_content' === $editor_id ) {
-		$settings['init_instance_callback'] = 'window.bp.Nouveau.Messages.tinyMCEinit';
-	}
-
-	return $settings;
-}
-
 /**
  * @since 3.0.0
  */
diff --git src/bp-templates/bp-nouveau/js/buddypress-messages.js src/bp-templates/bp-nouveau/js/buddypress-messages.js
index 44732ceaa..cfd0ee9af 100644
--- src/bp-templates/bp-nouveau/js/buddypress-messages.js
+++ src/bp-templates/bp-nouveau/js/buddypress-messages.js
@@ -101,24 +101,6 @@ window.bp = window.bp || {};
 			}
 		},
 
-		tinyMCEinit: function() {
-			if ( typeof window.tinyMCE === 'undefined' || window.tinyMCE.activeEditor === null || typeof window.tinyMCE.activeEditor === 'undefined' ) {
-				return;
-			} else {
-				// Mentions isn't available, so bail.
-				if ( _.isEmpty( exports.mentions ) ) {
-					return;
-				}
-
-				$( window.tinyMCE.activeEditor.contentDocument.activeElement )
-					.atwho( 'setIframe', $( '#message_content_ifr' )[0] )
-					.bp_mentions( {
-						data: [],
-						suffix: ' '
-					} );
-			}
-		},
-
 		removeFeedback: function() {
 			var feedback;
 
