/* global bp */ window.bp = window.bp || {}; ( function( bp, $, undefined ) { var mentionsQueryCache = [], mentionsItem; bp.mentions = bp.mentions || {}; bp.mentions.users = window.bp.mentions.users || []; if ( typeof window.BP_Suggestions === 'object' ) { bp.mentions.users = window.BP_Suggestions.friends || bp.mentions.users; } /** * Adds BuddyPress @mentions to form inputs. * * @param {array|object} options If array, becomes the suggestions' data source. If object, passed as config to $.atwho(). * @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 + '' + $2 + '' + $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; } } }, /** * 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' }; var groupId = this.$inputor.data( 'suggestions-group-id' ); if ( ( 'number' === typeof groupId || 'string' === typeof groupId ) && ! isNaN( groupId - parseFloat( groupId ) ) ) { params['group-id'] = parseInt( this.$inputor.data( 'suggestions-group-id' ), 10 ); } 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 ); }); } }, 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; } ), at: '@', searchKey: 'search', displayTpl: '
  • @${ID}${name}
  • ' }, opts = $.extend( true, {}, suggestionsDefaults, mentionsDefaults, options ); return $.fn.atwho.call( this, opts ); }; $( document ).ready(function() { // Activity/reply, post comments, dashboard post 'text' editor. $( '.bp-suggestions, #comments form textarea, .wp-editor-area' ).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 );