Skip to:
Content

BuddyPress.org

Ticket #8001: 8001.02.diff

File 8001.02.diff, 139.3 KB (added by dcavins, 4 years ago)

Update original patch; add support for BP Nouveau.

  • .gitignore

    diff --git a/.gitignore b/.gitignore
    index cc3f92c79..c5008ece8 100644
    a b logs 
    1717results
    1818src/vendor
    1919vendor
     20!src/bp-core/js/vendor
    2021
    2122node_modules
    2223npm-debug.log
  • src/bp-activity/bp-activity-cssjs.php

    diff --git a/src/bp-activity/bp-activity-cssjs.php b/src/bp-activity/bp-activity-cssjs.php
    index 26748b440..2495272f7 100644
    a b function bp_activity_mentions_script() { 
    3333
    3434        $min = bp_core_get_minified_asset_suffix();
    3535
    36         wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'jquery', 'jquery-atwho' ), bp_get_version(), true );
     36        wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'bp-api-request', 'zurb-tribute' ), bp_get_version(), true );
    3737        wp_enqueue_style( 'bp-mentions-css', buddypress()->plugin_url . "bp-activity/css/mentions{$min}.css", array(), bp_get_version() );
    3838
    3939        wp_style_add_data( 'bp-mentions-css', 'rtl', 'replace' );
    function bp_activity_mentions_script() { 
    4141                wp_style_add_data( 'bp-mentions-css', 'suffix', $min );
    4242        }
    4343
    44         // If the script has been enqueued, let's attach our mentions TinyMCE init callback.
    45         add_filter( 'tiny_mce_before_init', 'bp_add_mentions_on_tinymce_init', 10, 2 );
    46 
    4744        /**
    4845         * Fires at the end of the Activity Mentions script.
    4946         *
    function bp_activity_mentions_script() { 
    5754add_action( 'bp_enqueue_scripts', 'bp_activity_mentions_script' );
    5855add_action( 'bp_admin_enqueue_scripts', 'bp_activity_mentions_script' );
    5956
    60 /**
    61  * Bind the mentions listener to a wp_editor instance when TinyMCE initializes.
    62  *
    63  * @since 2.3.3
    64  *
    65  * @param array  $settings   An array with TinyMCE config.
    66  * @param string $editor_id Unique editor identifier, e.g. 'content'.
    67  * @return array  $mceInit   An array with TinyMCE config.
    68  */
    69 function bp_add_mentions_on_tinymce_init( $settings, $editor_id ) {
    70         // We only apply the mentions init to the visual post editor in the WP dashboard.
    71         if ( 'content' === $editor_id ) {
    72                 $settings['init_instance_callback'] = 'window.bp.mentions.tinyMCEinit';
    73         }
    74 
    75         return $settings;
    76 }
  • src/bp-activity/css/mentions.css

    diff --git a/src/bp-activity/css/mentions.css b/src/bp-activity/css/mentions.css
    index 7773c75ec..f7bc47d12 100644
    a b  
    1 .atwho-view {
    2         background: rgba(204, 204, 204, 0.8);
    3         border-radius: 2px;
    4         border: 1px solid rgb(204, 204, 204);
    5         box-shadow: 0 0 5px rgba(204, 204, 204, 0.25), 0 0 1px #fff;
    6         color: #d84800;
    7         display: none;
    8         font-family: sans-serif;
    9         margin-top: 18px;
     1.tribute-container {
    102        position: absolute;
    113        top: 0;
    12         z-index: 1000; /* >999 for wp-admin */
    13 }
    14 
    15 /* rtl:ignore */
    16 .atwho-view {
    174        left: 0;
     5        height: auto;
     6        max-height: 300px;
     7        max-width: 500px;
     8        overflow: auto;
     9        display: block;
     10        z-index: 999999;
    1811}
    19 
    20 .atwho-view ul {
    21         background: #fff;
    22         list-style: none;
    23         margin: auto;
     12.tribute-container ul {
     13        margin: 0;
     14        margin-top: 2px;
    2415        padding: 0;
     16        list-style: none;
     17        background: #efefef;
    2518}
    26 
    27 .atwho-view ul li {
    28         border-bottom: 1px solid #efefef;
    29         box-sizing: content-box;
     19.tribute-container li {
     20        padding: 5px 5px;
    3021        cursor: pointer;
    31         display: block;
    32         font-size: 14px;
    33         height: 20px;
    34         line-height: 20px;
    35         margin: 0;
    36         overflow: hidden;
    37         padding: 5px 10px;
    38 }
    39 
    40 .atwho-view img {
    41         border-radius: 2px;
    42         float: right;
    43         height: 20px;
    44         margin-top: 0;
    45         width: 20px;
    4622}
    47 
    48 .atwho-view strong {
    49         background: #efefef;
    50         font-weight: 700;
     23.tribute-container li.highlight, .tribute-container li:hover {
     24        background: #ddd;
    5125}
    52 
    53 .atwho-view .username strong {
    54         color: #d54e21;
     26.tribute-container li span {
     27        font-weight: bold;
    5528}
    56 
    57 .atwho-view small {
    58         color: #aaa;
    59         float: right;
    60         font-size: smaller;
    61         font-weight: 400;
    62         margin: 0 10px 0 40px;
     29.tribute-container li.no-match {
     30        cursor: default;
    6331}
    64 
    65 .atwho-view .cur {
    66         background: rgba(239, 239, 239, 0.5);
     32.tribute-container .menu-highlighted {
     33        font-weight: bold;
    6734}
    68 
    69 @media (max-width: 900px) {
    70 
    71         .atwho-view img {
    72                 float: left;
    73                 margin: 0 10px 0 0;
    74         }
    75 }
    76 
    77 @media (max-width: 400px) {
    78 
    79         .atwho-view ul li {
    80                 font-size: 16px;
    81                 line-height: 23px;
    82                 padding: 13px;
    83         }
    84 
    85         .atwho-view ul li img {
    86                 height: 30px;
    87                 margin-top: -5px;
    88                 width: 30px;
    89         }
    90 
    91         .atwho-view {
    92                 border-radius: 0;
    93                 left: 0 !important;
    94                 width: 100%;
    95         }
    96 
    97         .atwho-view ul li .username {
    98                 display: inline-block;
    99                 margin: -10px 0 0 0;
    100                 padding: 10px 0;
    101         }
    102 
    103         .atwho-view ul li small {
    104                 display: inline-block;
    105                 margin-left: 20px;
    106         }
     35.tribute-container li img {
     36        border-radius: 2px;
     37        float: right;
     38        height: 20px;
     39        /*margin-top: 0;*/
     40        width: 20px;
    10741}
  • src/bp-activity/js/mentions.js

    diff --git a/src/bp-activity/js/mentions.js b/src/bp-activity/js/mentions.js
    index 3fbdd9d0c..2c9a94025 100644
    a b window.bp = window.bp || {}; 
    1616        /**
    1717         * Adds BuddyPress @mentions to form inputs.
    1818         *
    19          * @param {array|object} options If array, becomes the suggestions' data source. If object, passed as config to $.atwho().
     19         * @param {array} defaultList If array, becomes the suggestions' default data source.
    2020         * @since 2.1.0
    2121         */
    22         $.fn.bp_mentions = function( options ) {
    23                 if ( Array.isArray( options ) ) {
    24                         options = { data: options };
    25                 }
    26 
    27                 /**
    28                  * Default options for at.js; see https://github.com/ichord/At.js/.
    29                  */
    30                 var suggestionsDefaults = {
    31                         delay:             200,
    32                         hideWithoutSuffix: true,
    33                         insertTpl:         '@${ID}',
    34                         limit:             10,
    35                         startWithSpace:    false,
    36                         suffix:            '',
    37 
    38                         callbacks: {
    39                                 /**
    40                                  * Custom filter to only match the start of spaced words.
    41                                  * Based on the core/default one.
    42                                  *
    43                                  * @param {string} query
    44                                  * @param {array} data
    45                                  * @param {string} search_key
    46                                  * @return {array}
    47                                  * @since 2.1.0
    48                                  */
    49                                 filter: function( query, data, search_key ) {
    50                                         var item, _i, _len, _results = [],
    51                                         regxp = new RegExp( '^' + query + '| ' + query, 'ig' ); // start of string, or preceded by a space.
    52 
    53                                         for ( _i = 0, _len = data.length; _i < _len; _i++ ) {
    54                                                 item = data[ _i ];
    55                                                 if ( item[ search_key ].toLowerCase().match( regxp ) ) {
    56                                                         _results.push( item );
    57                                                 }
    58                                         }
    59 
    60                                         return _results;
    61                                 },
    62 
    63                                 /**
    64                                  * Removes some spaces around highlighted string and tweaks regex to allow spaces
    65                                  * (to match display_name). Based on the core default.
    66                                  *
    67                                  * @param {unknown} li
    68                                  * @param {string} query
    69                                  * @return {string}
    70                                  * @since 2.1.0
    71                                  */
    72                                 highlighter: function( li, query ) {
    73                                         if ( ! query ) {
    74                                                 return li;
    75                                         }
    76 
    77                                         var regexp = new RegExp( '>(\\s*|[\\w\\s]*)(' + this.at.replace( '+', '\\+') + '?' + query.replace( '+', '\\+' ) + ')([\\w ]*)\\s*<', 'ig' );
    78                                         return li.replace( regexp, function( str, $1, $2, $3 ) {
    79                                                 return '>' + $1 + '<strong>' + $2 + '</strong>' + $3 + '<';
    80                                         });
    81                                 },
    82 
    83                                 /**
    84                                  * Reposition the suggestion list dynamically.
    85                                  *
    86                                  * @param {unknown} offset
    87                                  * @since 2.1.0
    88                                  */
    89                                 before_reposition: function( offset ) {
    90                                         // get the iframe, if any, already applied with atwho.js library.
    91                                         var caret,
    92                                                         line,
    93                                                         iframeOffset,
    94                                                         move,
    95                                                         $view = $( '#atwho-ground-' + this.id + ' .atwho-view' ),
    96                                                         $body = $( 'body' ),
    97                                                         atwhoDataValue = this.$inputor.data( 'atwho' );
    98 
    99                                         if ( 'undefined' !== atwhoDataValue && 'undefined' !== atwhoDataValue.iframe && null !== atwhoDataValue.iframe ) {
    100                                                 caret = this.$inputor.caret( 'offset', { iframe: atwhoDataValue.iframe } );
    101                                                 // Caret.js no longer calculates iframe caret position from the window (it's now just within the iframe).
    102                                                 // We need to get the iframe offset from the window and merge that into our object.
    103                                                 iframeOffset = $( atwhoDataValue.iframe ).offset();
    104                                                 if ( 'undefined' !== iframeOffset ) {
    105                                                         caret.left += iframeOffset.left;
    106                                                         caret.top  += iframeOffset.top;
    107                                                 }
    108                                         } else {
    109                                                 caret = this.$inputor.caret( 'offset' );
    110                                         }
    111 
    112                                         // If the caret is past horizontal half, then flip it, yo.
    113                                         if ( caret.left > ( $body.width() / 2 ) ) {
    114                                                 $view.addClass( 'right' );
    115                                                 move = caret.left - offset.left - this.view.$el.width();
    116                                         } else {
    117                                                 $view.removeClass( 'right' );
    118                                                 move = caret.left - offset.left + 1;
    119                                         }
    120 
    121                                         // If we're on a small screen, scroll to caret.
    122                                         if ( $body.width() <= 400 ) {
    123                                                 $( document ).scrollTop( caret.top - 6 );
    124                                         }
    125 
    126                                         // New position is under the caret (never above) and positioned to follow.
    127                                         // Dynamic sizing based on the input area (remove 'px' from end).
    128                                         line = parseInt( this.$inputor.css( 'line-height' ).substr( 0, this.$inputor.css( 'line-height' ).length - 2 ), 10 );
    129                                         if ( !line || line < 5 ) { // Sanity check, and catch no line-height.
    130                                                 line = 19;
    131                                         }
    132 
    133                                         offset.top   = caret.top + line;
    134                                         offset.left += move;
    135                                 },
    136 
    137                                 /**
    138                                  * Override default behavior which inserts junk tags in the WordPress Visual editor.
    139                                  *
    140                                  * @param {unknown} $inputor Element which we're inserting content into.
    141                                  * @param {string} content The content that will be inserted.
    142                                  * @param {string} suffix Applied to the end of the content string.
    143                                  * @return {string}
    144                                  * @since 2.1.0
    145                                  */
    146                                 inserting_wrapper: function( $inputor, content, suffix ) {
    147                                         return '' + content + suffix;
    148                                 }
     22        $.fn.bp_mentions = function( defaultList ) {
     23                var debouncer = function(func, wait) {
     24                        var timeout;
     25                        return function() {
     26                                var context = this;
     27                                var args = arguments;
     28
     29                                var callFunction = function() {
     30                                   func.apply(context, args)
     31                                };
     32
     33                                clearTimeout(timeout);
     34                                timeout = setTimeout(callFunction, wait);
     35                        };
     36                };
     37
     38                var remoteSearch = function( text, cb ) {
     39                        /**
     40                        * Immediately show the pre-created friends list, if it's populated,
     41                        * and the user has hesitated after hitting @ (no search text provided).
     42                        */
     43                        if ( text.length === 0 && $.isArray( defaultList ) && defaultList.length > 0 ) {
     44                                cb(defaultList);
     45                                return;
    14946                        }
    150                 },
    15147
    152                 /**
    153                  * Default options for our @mentions; see https://github.com/ichord/At.js/.
    154                  */
    155                 mentionsDefaults = {
    156                         callbacks: {
    157                                 /**
    158                                  * If there are no matches for the query in this.data, then query BuddyPress.
    159                                  *
    160                                  * @param {string} query Partial @mention to search for.
    161                                  * @param {function} render_view Render page callback function.
    162                                  * @since 2.1.0
    163                                  * @since 3.0.0. Renamed from "remote_filter" for at.js v1.5.4 support.
    164                                  */
    165                                 remoteFilter: function( query, render_view ) {
    166                                         var self = $( this ),
    167                                                 params = {};
    168 
    169                                         mentionsItem = mentionsQueryCache[ query ];
    170                                         if ( typeof mentionsItem === 'object' ) {
    171                                                 render_view( mentionsItem );
    172                                                 return;
    173                                         }
    174 
    175                                         if ( self.xhr ) {
    176                                                 self.xhr.abort();
    177                                         }
    178 
    179                                         params = { 'action': 'bp_get_suggestions', 'term': query, 'type': 'members' };
     48                        mentionsItem = mentionsQueryCache[ text ];
     49                        if ( typeof mentionsItem === 'object' ) {
     50                                cb( mentionsItem );
     51                                return;
     52                        }
    18053
    181                                         if ( $.isNumeric( this.$inputor.data( 'suggestions-group-id' ) ) ) {
    182                                                 params['group-id'] = parseInt( this.$inputor.data( 'suggestions-group-id' ), 10 );
     54                        return bp.apiRequest( {
     55                                path: 'buddypress/v1/members/?search=' + text,
     56                                type: 'GET'
     57                        } ).done( function( data ) {
     58                                var retval = $.map( data,
     59                                        /**
     60                                         * Create a composite index to determine ordering of results;
     61                                         * nicename matches will appear on top.
     62                                         *
     63                                         * @param {array} suggestion A suggestion's original data.
     64                                         * @return {array} A suggestion's new data.
     65                                         * @since 2.1.0
     66                                         */
     67                                        function( suggestion ) {
     68                                                suggestion.search = suggestion.user_login + ' ' + suggestion.name;
     69                                                return suggestion;
    18370                                        }
    184 
    185                                         self.xhr = $.getJSON( ajaxurl, params )
    186                                                 /**
    187                                                  * Success callback for the @suggestions lookup.
    188                                                  *
    189                                                  * @param {object} response Details of users matching the query.
    190                                                  * @since 2.1.0
    191                                                  */
    192                                                 .done(function( response ) {
    193                                                         if ( ! response.success ) {
    194                                                                 return;
    195                                                         }
    196 
    197                                                         var data = $.map( response.data,
    198                                                                 /**
    199                                                                  * Create a composite index to determine ordering of results;
    200                                                                  * nicename matches will appear on top.
    201                                                                  *
    202                                                                  * @param {array} suggestion A suggestion's original data.
    203                                                                  * @return {array} A suggestion's new data.
    204                                                                  * @since 2.1.0
    205                                                                  */
    206                                                                 function( suggestion ) {
    207                                                                         suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name;
    208                                                                         return suggestion;
    209                                                                 }
    210                                                         );
    211 
    212                                                         mentionsQueryCache[ query ] = data;
    213                                                         render_view( data );
    214                                                 });
    215                                 }
     71                                );
     72
     73                                mentionsQueryCache[ text ] = retval;
     74                                cb(retval);
     75                        } ).fail( function( error ) {
     76                                return error;
     77                        } );
     78                };
     79
     80                var tributeParams = {
     81                        values: debouncer( function (text, cb) {
     82                                remoteSearch(text, users => cb(users));
     83                        }, 250),
     84                        lookup: 'search',
     85                        fillAttr: 'user_login',
     86                        menuItemTemplate: function (item) {
     87                                return '<img src="' + item.original.avatar_urls.thumb + '" alt="Profile picture of ' + item.original.name + '"> @' + item.string;
    21688                        },
     89                };
    21790
    218                         data: $.map( options.data,
    219                                 /**
    220                                  * Create a composite index to search against of nicename + display name.
    221                                  * This will also determine ordering of results, so nicename matches will appear on top.
    222                                  *
    223                                  * @param {array} suggestion A suggestion's original data.
    224                                  * @return {array} A suggestion's new data.
    225                                  * @since 2.1.0
    226                                  */
    227                                 function( suggestion ) {
    228                                         suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name;
    229                                         return suggestion;
    230                                 }
    231                         ),
    232 
    233                         at:         '@',
    234                         searchKey:  'search',
    235                         displayTpl: '<li data-value="@${ID}"><img src="${image}" alt="" /><span class="username">@${ID}</span><small>${name}</small></li>'
    236                 },
     91                var tribute = new Tribute( tributeParams );
    23792
    238                 opts = $.extend( true, {}, suggestionsDefaults, mentionsDefaults, options );
    239                 return $.fn.atwho.call( this, opts );
     93                $( this ).each( function() {
     94                        tribute.attach( document.getElementById( $( this ).attr( "id" ) ) );
     95                });
    24096        };
    24197
    242         $( document ).ready(function() {
    243                 // Activity/reply, post comments, dashboard post 'text' editor.
    244                 $( '.bp-suggestions, #comments form textarea, .wp-editor-area' ).bp_mentions( bp.mentions.users );
     98        $( document ).ready( function() {
     99                // Activity/reply, post comments, bp-nouveau messages composer.
     100                $( '.bp-suggestions, #comments form textarea, .bp-messages-content .send-to-input' ).bp_mentions( bp.mentions.users );
    245101        });
    246102
    247         bp.mentions.tinyMCEinit = function() {
    248                 if ( typeof window.tinyMCE === 'undefined' || window.tinyMCE.activeEditor === null || typeof window.tinyMCE.activeEditor === 'undefined' ) {
    249                         return;
    250                 } else {
    251                         $( window.tinyMCE.activeEditor.contentDocument.activeElement )
    252                                 .atwho( 'setIframe', $( '.wp-editor-wrap iframe' )[0] )
    253                                 .bp_mentions( bp.mentions.users );
    254                 }
    255         };
    256103})( bp, jQuery );
  • src/bp-core/bp-core-cssjs.php

    diff --git a/src/bp-core/bp-core-cssjs.php b/src/bp-core/bp-core-cssjs.php
    index 5e5691f51..3d9f06022 100644
    a b function bp_core_register_common_scripts() { 
    7070                'bp-jquery-cookie'  => array( 'file' => "{$url}vendor/jquery-cookie{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => false ),
    7171                'bp-jquery-scroll-to' => array( 'file' => "{$url}vendor/jquery-scroll-to{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => false ),
    7272
    73                 // Version 2.1.
    74                 'jquery-caret' => array( 'file' => "{$url}vendor/jquery.caret{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => true ),
    75                 'jquery-atwho' => array( 'file' => "{$url}vendor/jquery.atwho{$min}.js", 'dependencies' => array( 'jquery', 'jquery-caret' ), 'footer' => true ),
    76 
    7773                // Version 2.3.
    7874                'bp-plupload' => array( 'file' => "{$url}bp-plupload{$min}.js", 'dependencies' => array( 'plupload', 'jquery', 'json2', 'wp-backbone' ), 'footer' => true ),
    7975                'bp-avatar'   => array( 'file' => "{$url}avatar{$min}.js", 'dependencies' => array( 'jcrop' ), 'footer' => true ),
    function bp_core_register_common_scripts() { 
    8581                // Version 2.7.
    8682                'bp-moment'    => array( 'file' => "{$url}vendor/moment-js/moment{$min}.js", 'dependencies' => array(), 'footer' => true ),
    8783                'bp-livestamp' => array( 'file' => "{$url}vendor/livestamp{$min}.js", 'dependencies' => array( 'jquery', 'bp-moment' ), 'footer' => true ),
     84
     85                // Version 8
     86                'zurb-tribute' => array( 'file' => "{$url}vendor/tribute{$min}.js", 'dependencies' => array(), 'footer' => true ),
    8887        );
    8988
    9089        // Version 2.7 - Add Moment.js locale to our $scripts array if we found one.
  • deleted file src/bp-core/js/vendor/jquery.atwho.js

    diff --git a/src/bp-core/js/vendor/jquery.atwho.js b/src/bp-core/js/vendor/jquery.atwho.js
    deleted file mode 100755
    index b0685d2b1..000000000
    + -  
    1 /**
    2  * at.js - 1.5.4
    3  * Copyright (c) 2017 chord.luo <chord.luo@gmail.com>;
    4  * Homepage: http://ichord.github.com/At.js
    5  * License: MIT
    6  */
    7 (function (root, factory) {
    8   if (typeof define === 'function' && define.amd) {
    9     // AMD. Register as an anonymous module unless amdModuleId is set.
    10     define(["jquery"], function (a0) {
    11       return (factory(a0));
    12     });
    13   } else if (typeof exports === 'object') {
    14     // Node. Does not work with strict CommonJS, but
    15     // only CommonJS-like environments that support module.exports,
    16     // like Node.
    17     module.exports = factory(require("jquery"));
    18   } else {
    19     factory(jQuery);
    20   }
    21 }(this, function ($) {
    22 var DEFAULT_CALLBACKS, KEY_CODE;
    23 
    24 KEY_CODE = {
    25   ESC: 27,
    26   TAB: 9,
    27   ENTER: 13,
    28   CTRL: 17,
    29   A: 65,
    30   P: 80,
    31   N: 78,
    32   LEFT: 37,
    33   UP: 38,
    34   RIGHT: 39,
    35   DOWN: 40,
    36   BACKSPACE: 8,
    37   SPACE: 32
    38 };
    39 
    40 DEFAULT_CALLBACKS = {
    41   beforeSave: function(data) {
    42     return Controller.arrayToDefaultHash(data);
    43   },
    44   matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
    45     var _a, _y, match, regexp, space;
    46     flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    47     if (should_startWithSpace) {
    48       flag = '(?:^|\\s)' + flag;
    49     }
    50     _a = decodeURI("%C3%80");
    51     _y = decodeURI("%C3%BF");
    52     space = acceptSpaceBar ? "\ " : "";
    53     regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
    54     match = regexp.exec(subtext);
    55     if (match) {
    56       return match[2] || match[1];
    57     } else {
    58       return null;
    59     }
    60   },
    61   filter: function(query, data, searchKey) {
    62     var _results, i, item, len;
    63     _results = [];
    64     for (i = 0, len = data.length; i < len; i++) {
    65       item = data[i];
    66       if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) {
    67         _results.push(item);
    68       }
    69     }
    70     return _results;
    71   },
    72   remoteFilter: null,
    73   sorter: function(query, items, searchKey) {
    74     var _results, i, item, len;
    75     if (!query) {
    76       return items;
    77     }
    78     _results = [];
    79     for (i = 0, len = items.length; i < len; i++) {
    80       item = items[i];
    81       item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase());
    82       if (item.atwho_order > -1) {
    83         _results.push(item);
    84       }
    85     }
    86     return _results.sort(function(a, b) {
    87       return a.atwho_order - b.atwho_order;
    88     });
    89   },
    90   tplEval: function(tpl, map) {
    91     var error, error1, template;
    92     template = tpl;
    93     try {
    94       if (typeof tpl !== 'string') {
    95         template = tpl(map);
    96       }
    97       return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
    98         return map[key];
    99       });
    100     } catch (error1) {
    101       error = error1;
    102       return "";
    103     }
    104   },
    105   highlighter: function(li, query) {
    106     var regexp;
    107     if (!query) {
    108       return li;
    109     }
    110     regexp = new RegExp(">\\s*([^\<]*?)(" + query.replace("+", "\\+") + ")([^\<]*)\\s*<", 'ig');
    111     return li.replace(regexp, function(str, $1, $2, $3) {
    112       return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
    113     });
    114   },
    115   beforeInsert: function(value, $li, e) {
    116     return value;
    117   },
    118   beforeReposition: function(offset) {
    119     return offset;
    120   },
    121   afterMatchFailed: function(at, el) {}
    122 };
    123 
    124 var App;
    125 
    126 App = (function() {
    127   function App(inputor) {
    128     this.currentFlag = null;
    129     this.controllers = {};
    130     this.aliasMaps = {};
    131     this.$inputor = $(inputor);
    132     this.setupRootElement();
    133     this.listen();
    134   }
    135 
    136   App.prototype.createContainer = function(doc) {
    137     var ref;
    138     if ((ref = this.$el) != null) {
    139       ref.remove();
    140     }
    141     return $(doc.body).append(this.$el = $("<div class='atwho-container'></div>"));
    142   };
    143 
    144   App.prototype.setupRootElement = function(iframe, asRoot) {
    145     var error, error1;
    146     if (asRoot == null) {
    147       asRoot = false;
    148     }
    149     if (iframe) {
    150       this.window = iframe.contentWindow;
    151       this.document = iframe.contentDocument || this.window.document;
    152       this.iframe = iframe;
    153     } else {
    154       this.document = this.$inputor[0].ownerDocument;
    155       this.window = this.document.defaultView || this.document.parentWindow;
    156       try {
    157         this.iframe = this.window.frameElement;
    158       } catch (error1) {
    159         error = error1;
    160         this.iframe = null;
    161         if ($.fn.atwho.debug) {
    162           throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
    163         }
    164       }
    165     }
    166     return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
    167   };
    168 
    169   App.prototype.controller = function(at) {
    170     var c, current, currentFlag, ref;
    171     if (this.aliasMaps[at]) {
    172       current = this.controllers[this.aliasMaps[at]];
    173     } else {
    174       ref = this.controllers;
    175       for (currentFlag in ref) {
    176         c = ref[currentFlag];
    177         if (currentFlag === at) {
    178           current = c;
    179           break;
    180         }
    181       }
    182     }
    183     if (current) {
    184       return current;
    185     } else {
    186       return this.controllers[this.currentFlag];
    187     }
    188   };
    189 
    190   App.prototype.setContextFor = function(at) {
    191     this.currentFlag = at;
    192     return this;
    193   };
    194 
    195   App.prototype.reg = function(flag, setting) {
    196     var base, controller;
    197     controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
    198     if (setting.alias) {
    199       this.aliasMaps[setting.alias] = flag;
    200     }
    201     controller.init(setting);
    202     return this;
    203   };
    204 
    205   App.prototype.listen = function() {
    206     return this.$inputor.on('compositionstart', (function(_this) {
    207       return function(e) {
    208         var ref;
    209         if ((ref = _this.controller()) != null) {
    210           ref.view.hide();
    211         }
    212         _this.isComposing = true;
    213         return null;
    214       };
    215     })(this)).on('compositionend', (function(_this) {
    216       return function(e) {
    217         _this.isComposing = false;
    218         setTimeout(function(e) {
    219           return _this.dispatch(e);
    220         });
    221         return null;
    222       };
    223     })(this)).on('keyup.atwhoInner', (function(_this) {
    224       return function(e) {
    225         return _this.onKeyup(e);
    226       };
    227     })(this)).on('keydown.atwhoInner', (function(_this) {
    228       return function(e) {
    229         return _this.onKeydown(e);
    230       };
    231     })(this)).on('blur.atwhoInner', (function(_this) {
    232       return function(e) {
    233         var c;
    234         if (c = _this.controller()) {
    235           c.expectedQueryCBId = null;
    236           return c.view.hide(e, c.getOpt("displayTimeout"));
    237         }
    238       };
    239     })(this)).on('click.atwhoInner', (function(_this) {
    240       return function(e) {
    241         return _this.dispatch(e);
    242       };
    243     })(this)).on('scroll.atwhoInner', (function(_this) {
    244       return function() {
    245         var lastScrollTop;
    246         lastScrollTop = _this.$inputor.scrollTop();
    247         return function(e) {
    248           var currentScrollTop, ref;
    249           currentScrollTop = e.target.scrollTop;
    250           if (lastScrollTop !== currentScrollTop) {
    251             if ((ref = _this.controller()) != null) {
    252               ref.view.hide(e);
    253             }
    254           }
    255           lastScrollTop = currentScrollTop;
    256           return true;
    257         };
    258       };
    259     })(this)());
    260   };
    261 
    262   App.prototype.shutdown = function() {
    263     var _, c, ref;
    264     ref = this.controllers;
    265     for (_ in ref) {
    266       c = ref[_];
    267       c.destroy();
    268       delete this.controllers[_];
    269     }
    270     this.$inputor.off('.atwhoInner');
    271     return this.$el.remove();
    272   };
    273 
    274   App.prototype.dispatch = function(e) {
    275     var _, c, ref, results;
    276     ref = this.controllers;
    277     results = [];
    278     for (_ in ref) {
    279       c = ref[_];
    280       results.push(c.lookUp(e));
    281     }
    282     return results;
    283   };
    284 
    285   App.prototype.onKeyup = function(e) {
    286     var ref;
    287     switch (e.keyCode) {
    288       case KEY_CODE.ESC:
    289         e.preventDefault();
    290         if ((ref = this.controller()) != null) {
    291           ref.view.hide();
    292         }
    293         break;
    294       case KEY_CODE.DOWN:
    295       case KEY_CODE.UP:
    296       case KEY_CODE.CTRL:
    297       case KEY_CODE.ENTER:
    298         $.noop();
    299         break;
    300       case KEY_CODE.P:
    301       case KEY_CODE.N:
    302         if (!e.ctrlKey) {
    303           this.dispatch(e);
    304         }
    305         break;
    306       default:
    307         this.dispatch(e);
    308     }
    309   };
    310 
    311   App.prototype.onKeydown = function(e) {
    312     var ref, view;
    313     view = (ref = this.controller()) != null ? ref.view : void 0;
    314     if (!(view && view.visible())) {
    315       return;
    316     }
    317     switch (e.keyCode) {
    318       case KEY_CODE.ESC:
    319         e.preventDefault();
    320         view.hide(e);
    321         break;
    322       case KEY_CODE.UP:
    323         e.preventDefault();
    324         view.prev();
    325         break;
    326       case KEY_CODE.DOWN:
    327         e.preventDefault();
    328         view.next();
    329         break;
    330       case KEY_CODE.P:
    331         if (!e.ctrlKey) {
    332           return;
    333         }
    334         e.preventDefault();
    335         view.prev();
    336         break;
    337       case KEY_CODE.N:
    338         if (!e.ctrlKey) {
    339           return;
    340         }
    341         e.preventDefault();
    342         view.next();
    343         break;
    344       case KEY_CODE.TAB:
    345       case KEY_CODE.ENTER:
    346       case KEY_CODE.SPACE:
    347         if (!view.visible()) {
    348           return;
    349         }
    350         if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) {
    351           return;
    352         }
    353         if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) {
    354           return;
    355         }
    356         if (view.highlighted()) {
    357           e.preventDefault();
    358           view.choose(e);
    359         } else {
    360           view.hide(e);
    361         }
    362         break;
    363       default:
    364         $.noop();
    365     }
    366   };
    367 
    368   return App;
    369 
    370 })();
    371 
    372 var Controller,
    373   slice = [].slice;
    374 
    375 Controller = (function() {
    376   Controller.prototype.uid = function() {
    377     return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
    378   };
    379 
    380   function Controller(app, at1) {
    381     this.app = app;
    382     this.at = at1;
    383     this.$inputor = this.app.$inputor;
    384     this.id = this.$inputor[0].id || this.uid();
    385     this.expectedQueryCBId = null;
    386     this.setting = null;
    387     this.query = null;
    388     this.pos = 0;
    389     this.range = null;
    390     if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
    391       this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
    392     }
    393     this.model = new Model(this);
    394     this.view = new View(this);
    395   }
    396 
    397   Controller.prototype.init = function(setting) {
    398     this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
    399     this.view.init();
    400     return this.model.reload(this.setting.data);
    401   };
    402 
    403   Controller.prototype.destroy = function() {
    404     this.trigger('beforeDestroy');
    405     this.model.destroy();
    406     this.view.destroy();
    407     return this.$el.remove();
    408   };
    409 
    410   Controller.prototype.callDefault = function() {
    411     var args, error, error1, funcName;
    412     funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
    413     try {
    414       return DEFAULT_CALLBACKS[funcName].apply(this, args);
    415     } catch (error1) {
    416       error = error1;
    417       return $.error(error + " Or maybe At.js doesn't have function " + funcName);
    418     }
    419   };
    420 
    421   Controller.prototype.trigger = function(name, data) {
    422     var alias, eventName;
    423     if (data == null) {
    424       data = [];
    425     }
    426     data.push(this);
    427     alias = this.getOpt('alias');
    428     eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
    429     return this.$inputor.trigger(eventName, data);
    430   };
    431 
    432   Controller.prototype.callbacks = function(funcName) {
    433     return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
    434   };
    435 
    436   Controller.prototype.getOpt = function(at, default_value) {
    437     var e, error1;
    438     try {
    439       return this.setting[at];
    440     } catch (error1) {
    441       e = error1;
    442       return null;
    443     }
    444   };
    445 
    446   Controller.prototype.insertContentFor = function($li) {
    447     var data, tpl;
    448     tpl = this.getOpt('insertTpl');
    449     data = $.extend({}, $li.data('item-data'), {
    450       'atwho-at': this.at
    451     });
    452     return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
    453   };
    454 
    455   Controller.prototype.renderView = function(data) {
    456     var searchKey;
    457     searchKey = this.getOpt("searchKey");
    458     data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey);
    459     return this.view.render(data.slice(0, this.getOpt('limit')));
    460   };
    461 
    462   Controller.arrayToDefaultHash = function(data) {
    463     var i, item, len, results;
    464     if (!$.isArray(data)) {
    465       return data;
    466     }
    467     results = [];
    468     for (i = 0, len = data.length; i < len; i++) {
    469       item = data[i];
    470       if ($.isPlainObject(item)) {
    471         results.push(item);
    472       } else {
    473         results.push({
    474           name: item
    475         });
    476       }
    477     }
    478     return results;
    479   };
    480 
    481   Controller.prototype.lookUp = function(e) {
    482     var query, wait;
    483     if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
    484       return;
    485     }
    486     if (this.getOpt('suspendOnComposing') && this.app.isComposing) {
    487       return;
    488     }
    489     query = this.catchQuery(e);
    490     if (!query) {
    491       this.expectedQueryCBId = null;
    492       return query;
    493     }
    494     this.app.setContextFor(this.at);
    495     if (wait = this.getOpt('delay')) {
    496       this._delayLookUp(query, wait);
    497     } else {
    498       this._lookUp(query);
    499     }
    500     return query;
    501   };
    502 
    503   Controller.prototype._delayLookUp = function(query, wait) {
    504     var now, remaining;
    505     now = Date.now ? Date.now() : new Date().getTime();
    506     this.previousCallTime || (this.previousCallTime = now);
    507     remaining = wait - (now - this.previousCallTime);
    508     if ((0 < remaining && remaining < wait)) {
    509       this.previousCallTime = now;
    510       this._stopDelayedCall();
    511       return this.delayedCallTimeout = setTimeout((function(_this) {
    512         return function() {
    513           _this.previousCallTime = 0;
    514           _this.delayedCallTimeout = null;
    515           return _this._lookUp(query);
    516         };
    517       })(this), wait);
    518     } else {
    519       this._stopDelayedCall();
    520       if (this.previousCallTime !== now) {
    521         this.previousCallTime = 0;
    522       }
    523       return this._lookUp(query);
    524     }
    525   };
    526 
    527   Controller.prototype._stopDelayedCall = function() {
    528     if (this.delayedCallTimeout) {
    529       clearTimeout(this.delayedCallTimeout);
    530       return this.delayedCallTimeout = null;
    531     }
    532   };
    533 
    534   Controller.prototype._generateQueryCBId = function() {
    535     return {};
    536   };
    537 
    538   Controller.prototype._lookUp = function(query) {
    539     var _callback;
    540     _callback = function(queryCBId, data) {
    541       if (queryCBId !== this.expectedQueryCBId) {
    542         return;
    543       }
    544       if (data && data.length > 0) {
    545         return this.renderView(this.constructor.arrayToDefaultHash(data));
    546       } else {
    547         return this.view.hide();
    548       }
    549     };
    550     this.expectedQueryCBId = this._generateQueryCBId();
    551     return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
    552   };
    553 
    554   return Controller;
    555 
    556 })();
    557 
    558 var TextareaController,
    559   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; },
    560   hasProp = {}.hasOwnProperty;
    561 
    562 TextareaController = (function(superClass) {
    563   extend(TextareaController, superClass);
    564 
    565   function TextareaController() {
    566     return TextareaController.__super__.constructor.apply(this, arguments);
    567   }
    568 
    569   TextareaController.prototype.catchQuery = function() {
    570     var caretPos, content, end, isString, query, start, subtext;
    571     content = this.$inputor.val();
    572     caretPos = this.$inputor.caret('pos', {
    573       iframe: this.app.iframe
    574     });
    575     subtext = content.slice(0, caretPos);
    576     query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
    577     isString = typeof query === 'string';
    578     if (isString && query.length < this.getOpt('minLen', 0)) {
    579       return;
    580     }
    581     if (isString && query.length <= this.getOpt('maxLen', 20)) {
    582       start = caretPos - query.length;
    583       end = start + query.length;
    584       this.pos = start;
    585       query = {
    586         'text': query,
    587         'headPos': start,
    588         'endPos': end
    589       };
    590       this.trigger("matched", [this.at, query.text]);
    591     } else {
    592       query = null;
    593       this.view.hide();
    594     }
    595     return this.query = query;
    596   };
    597 
    598   TextareaController.prototype.rect = function() {
    599     var c, iframeOffset, scaleBottom;
    600     if (!(c = this.$inputor.caret('offset', this.pos - 1, {
    601       iframe: this.app.iframe
    602     }))) {
    603       return;
    604     }
    605     if (this.app.iframe && !this.app.iframeAsRoot) {
    606       iframeOffset = $(this.app.iframe).offset();
    607       c.left += iframeOffset.left;
    608       c.top += iframeOffset.top;
    609     }
    610     scaleBottom = this.app.document.selection ? 0 : 2;
    611     return {
    612       left: c.left,
    613       top: c.top,
    614       bottom: c.top + c.height + scaleBottom
    615     };
    616   };
    617 
    618   TextareaController.prototype.insert = function(content, $li) {
    619     var $inputor, source, startStr, suffix, text;
    620     $inputor = this.$inputor;
    621     source = $inputor.val();
    622     startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
    623     suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
    624     content += suffix;
    625     text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
    626     $inputor.val(text);
    627     $inputor.caret('pos', startStr.length + content.length, {
    628       iframe: this.app.iframe
    629     });
    630     if (!$inputor.is(':focus')) {
    631       $inputor.focus();
    632     }
    633     return $inputor.change();
    634   };
    635 
    636   return TextareaController;
    637 
    638 })(Controller);
    639 
    640 var EditableController,
    641   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; },
    642   hasProp = {}.hasOwnProperty;
    643 
    644 EditableController = (function(superClass) {
    645   extend(EditableController, superClass);
    646 
    647   function EditableController() {
    648     return EditableController.__super__.constructor.apply(this, arguments);
    649   }
    650 
    651   EditableController.prototype._getRange = function() {
    652     var sel;
    653     sel = this.app.window.getSelection();
    654     if (sel.rangeCount > 0) {
    655       return sel.getRangeAt(0);
    656     }
    657   };
    658 
    659   EditableController.prototype._setRange = function(position, node, range) {
    660     if (range == null) {
    661       range = this._getRange();
    662     }
    663     if (!(range && node)) {
    664       return;
    665     }
    666     node = $(node)[0];
    667     if (position === 'after') {
    668       range.setEndAfter(node);
    669       range.setStartAfter(node);
    670     } else {
    671       range.setEndBefore(node);
    672       range.setStartBefore(node);
    673     }
    674     range.collapse(false);
    675     return this._clearRange(range);
    676   };
    677 
    678   EditableController.prototype._clearRange = function(range) {
    679     var sel;
    680     if (range == null) {
    681       range = this._getRange();
    682     }
    683     sel = this.app.window.getSelection();
    684     if (this.ctrl_a_pressed == null) {
    685       sel.removeAllRanges();
    686       return sel.addRange(range);
    687     }
    688   };
    689 
    690   EditableController.prototype._movingEvent = function(e) {
    691     var ref;
    692     return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
    693   };
    694 
    695   EditableController.prototype._unwrap = function(node) {
    696     var next;
    697     node = $(node).unwrap().get(0);
    698     if ((next = node.nextSibling) && next.nodeValue) {
    699       node.nodeValue += next.nodeValue;
    700       $(next).remove();
    701     }
    702     return node;
    703   };
    704 
    705   EditableController.prototype.catchQuery = function(e) {
    706     var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
    707     if (!(range = this._getRange())) {
    708       return;
    709     }
    710     if (!range.collapsed) {
    711       return;
    712     }
    713     if (e.which === KEY_CODE.ENTER) {
    714       ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap();
    715       if ($query.is(':empty')) {
    716         $query.remove();
    717       }
    718       ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap();
    719       this._clearRange();
    720       return;
    721     }
    722     if (/firefox/i.test(navigator.userAgent)) {
    723       if ($(range.startContainer).is(this.$inputor)) {
    724         this._clearRange();
    725         return;
    726       }
    727       if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) {
    728         _range = range.cloneRange();
    729         _range.setStart(range.startContainer, offset);
    730         if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) {
    731           inserted = $(range.startContainer).contents().get(offset);
    732           this._setRange('after', $(inserted).contents().last());
    733         }
    734       } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) {
    735         $inserted = $(range.startContainer.previousSibling);
    736         if ($inserted.is('.atwho-inserted') && range.startOffset === 0) {
    737           this._setRange('after', $inserted.contents().last());
    738         }
    739       }
    740     }
    741     $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
    742     if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
    743       $query.remove();
    744     }
    745     if (!this._movingEvent(e)) {
    746       $query.removeClass('atwho-inserted');
    747     }
    748     if ($query.length > 0) {
    749       switch (e.which) {
    750         case KEY_CODE.LEFT:
    751           this._setRange('before', $query.get(0), range);
    752           $query.removeClass('atwho-query');
    753           return;
    754         case KEY_CODE.RIGHT:
    755           this._setRange('after', $query.get(0).nextSibling, range);
    756           $query.removeClass('atwho-query');
    757           return;
    758       }
    759     }
    760     if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
    761       $query.empty().html(query_content).attr('data-atwho-at-query', null);
    762       this._setRange('after', $query.get(0), range);
    763     }
    764     _range = range.cloneRange();
    765     _range.setStart(range.startContainer, 0);
    766     matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
    767     isString = typeof matched === 'string';
    768     if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
    769       range.setStart(range.startContainer, index);
    770       $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
    771       range.surroundContents($query.get(0));
    772       lastNode = $query.contents().last().get(0);
    773       if (lastNode) {
    774         if (/firefox/i.test(navigator.userAgent)) {
    775           range.setStart(lastNode, lastNode.length);
    776           range.setEnd(lastNode, lastNode.length);
    777           this._clearRange(range);
    778         } else {
    779           this._setRange('after', lastNode, range);
    780         }
    781       }
    782     }
    783     if (isString && matched.length < this.getOpt('minLen', 0)) {
    784       return;
    785     }
    786     if (isString && matched.length <= this.getOpt('maxLen', 20)) {
    787       query = {
    788         text: matched,
    789         el: $query
    790       };
    791       this.trigger("matched", [this.at, query.text]);
    792       return this.query = query;
    793     } else {
    794       this.view.hide();
    795       this.query = {
    796         el: $query
    797       };
    798       if ($query.text().indexOf(this.at) >= 0) {
    799         if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) {
    800           $query.removeClass('atwho-query');
    801         } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) {
    802           this._setRange("after", this._unwrap($query.text($query.text()).contents().first()));
    803         }
    804       }
    805       return null;
    806     }
    807   };
    808 
    809   EditableController.prototype.rect = function() {
    810     var $iframe, iframeOffset, rect;
    811     rect = this.query.el.offset();
    812     if (!(rect && this.query.el[0].getClientRects().length)) {
    813       return;
    814     }
    815     if (this.app.iframe && !this.app.iframeAsRoot) {
    816       iframeOffset = ($iframe = $(this.app.iframe)).offset();
    817       rect.left += iframeOffset.left - this.$inputor.scrollLeft();
    818       rect.top += iframeOffset.top - this.$inputor.scrollTop();
    819     }
    820     rect.bottom = rect.top + this.query.el.height();
    821     return rect;
    822   };
    823 
    824   EditableController.prototype.insert = function(content, $li) {
    825     var data, overrides, range, suffix, suffixNode;
    826     if (!this.$inputor.is(':focus')) {
    827       this.$inputor.focus();
    828     }
    829     overrides = this.getOpt('functionOverrides');
    830     if (overrides.insert) {
    831       return overrides.insert.call(this, content, $li);
    832     }
    833     suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0";
    834     data = $li.data('item-data');
    835     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");
    836     if (range = this._getRange()) {
    837       if (this.query.el.length) {
    838         range.setEndAfter(this.query.el[0]);
    839       }
    840       range.collapse(false);
    841       range.insertNode(suffixNode = this.app.document.createTextNode("" + suffix));
    842       this._setRange('after', suffixNode, range);
    843     }
    844     if (!this.$inputor.is(':focus')) {
    845       this.$inputor.focus();
    846     }
    847     return this.$inputor.change();
    848   };
    849 
    850   return EditableController;
    851 
    852 })(Controller);
    853 
    854 var Model;
    855 
    856 Model = (function() {
    857   function Model(context) {
    858     this.context = context;
    859     this.at = this.context.at;
    860     this.storage = this.context.$inputor;
    861   }
    862 
    863   Model.prototype.destroy = function() {
    864     return this.storage.data(this.at, null);
    865   };
    866 
    867   Model.prototype.saved = function() {
    868     return this.fetch() > 0;
    869   };
    870 
    871   Model.prototype.query = function(query, callback) {
    872     var _remoteFilter, data, searchKey;
    873     data = this.fetch();
    874     searchKey = this.context.getOpt("searchKey");
    875     data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || [];
    876     _remoteFilter = this.context.callbacks('remoteFilter');
    877     if (data.length > 0 || (!_remoteFilter && data.length === 0)) {
    878       return callback(data);
    879     } else {
    880       return _remoteFilter.call(this.context, query, callback);
    881     }
    882   };
    883 
    884   Model.prototype.fetch = function() {
    885     return this.storage.data(this.at) || [];
    886   };
    887 
    888   Model.prototype.save = function(data) {
    889     return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
    890   };
    891 
    892   Model.prototype.load = function(data) {
    893     if (!(this.saved() || !data)) {
    894       return this._load(data);
    895     }
    896   };
    897 
    898   Model.prototype.reload = function(data) {
    899     return this._load(data);
    900   };
    901 
    902   Model.prototype._load = function(data) {
    903     if (typeof data === "string") {
    904       return $.ajax(data, {
    905         dataType: "json"
    906       }).done((function(_this) {
    907         return function(data) {
    908           return _this.save(data);
    909         };
    910       })(this));
    911     } else {
    912       return this.save(data);
    913     }
    914   };
    915 
    916   return Model;
    917 
    918 })();
    919 
    920 var View;
    921 
    922 View = (function() {
    923   function View(context) {
    924     this.context = context;
    925     this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
    926     this.$elUl = this.$el.children();
    927     this.timeoutID = null;
    928     this.context.$el.append(this.$el);
    929     this.bindEvent();
    930   }
    931 
    932   View.prototype.init = function() {
    933     var header_tpl, id;
    934     id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
    935     header_tpl = this.context.getOpt("headerTpl");
    936     if (header_tpl && this.$el.children().length === 1) {
    937       this.$el.prepend(header_tpl);
    938     }
    939     return this.$el.attr({
    940       'id': "at-view-" + id
    941     });
    942   };
    943 
    944   View.prototype.destroy = function() {
    945     return this.$el.remove();
    946   };
    947 
    948   View.prototype.bindEvent = function() {
    949     var $menu, lastCoordX, lastCoordY;
    950     $menu = this.$el.find('ul');
    951     lastCoordX = 0;
    952     lastCoordY = 0;
    953     return $menu.on('mousemove.atwho-view', 'li', (function(_this) {
    954       return function(e) {
    955         var $cur;
    956         if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
    957           return;
    958         }
    959         lastCoordX = e.clientX;
    960         lastCoordY = e.clientY;
    961         $cur = $(e.currentTarget);
    962         if ($cur.hasClass('cur')) {
    963           return;
    964         }
    965         $menu.find('.cur').removeClass('cur');
    966         return $cur.addClass('cur');
    967       };
    968     })(this)).on('click.atwho-view', 'li', (function(_this) {
    969       return function(e) {
    970         $menu.find('.cur').removeClass('cur');
    971         $(e.currentTarget).addClass('cur');
    972         _this.choose(e);
    973         return e.preventDefault();
    974       };
    975     })(this));
    976   };
    977 
    978   View.prototype.visible = function() {
    979     return $.expr.filters.visible(this.$el[0]);
    980   };
    981 
    982   View.prototype.highlighted = function() {
    983     return this.$el.find(".cur").length > 0;
    984   };
    985 
    986   View.prototype.choose = function(e) {
    987     var $li, content;
    988     if (($li = this.$el.find(".cur")).length) {
    989       content = this.context.insertContentFor($li);
    990       this.context._stopDelayedCall();
    991       this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li, e), $li);
    992       this.context.trigger("inserted", [$li, e]);
    993       this.hide(e);
    994     }
    995     if (this.context.getOpt("hideWithoutSuffix")) {
    996       return this.stopShowing = true;
    997     }
    998   };
    999 
    1000   View.prototype.reposition = function(rect) {
    1001     var _window, offset, overflowOffset, ref;
    1002     _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
    1003     if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
    1004       rect.bottom = rect.top - this.$el.height();
    1005     }
    1006     if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
    1007       rect.left = overflowOffset;
    1008     }
    1009     offset = {
    1010       left: rect.left,
    1011       top: rect.bottom
    1012     };
    1013     if ((ref = this.context.callbacks("beforeReposition")) != null) {
    1014       ref.call(this.context, offset);
    1015     }
    1016     this.$el.offset(offset);
    1017     return this.context.trigger("reposition", [offset]);
    1018   };
    1019 
    1020   View.prototype.next = function() {
    1021     var cur, next, nextEl, offset;
    1022     cur = this.$el.find('.cur').removeClass('cur');
    1023     next = cur.next();
    1024     if (!next.length) {
    1025       next = this.$el.find('li:first');
    1026     }
    1027     next.addClass('cur');
    1028     nextEl = next[0];
    1029     offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
    1030     return this.scrollTop(Math.max(0, offset - this.$el.height()));
    1031   };
    1032 
    1033   View.prototype.prev = function() {
    1034     var cur, offset, prev, prevEl;
    1035     cur = this.$el.find('.cur').removeClass('cur');
    1036     prev = cur.prev();
    1037     if (!prev.length) {
    1038       prev = this.$el.find('li:last');
    1039     }
    1040     prev.addClass('cur');
    1041     prevEl = prev[0];
    1042     offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
    1043     return this.scrollTop(Math.max(0, offset - this.$el.height()));
    1044   };
    1045 
    1046   View.prototype.scrollTop = function(scrollTop) {
    1047     var scrollDuration;
    1048     scrollDuration = this.context.getOpt('scrollDuration');
    1049     if (scrollDuration) {
    1050       return this.$elUl.animate({
    1051         scrollTop: scrollTop
    1052       }, scrollDuration);
    1053     } else {
    1054       return this.$elUl.scrollTop(scrollTop);
    1055     }
    1056   };
    1057 
    1058   View.prototype.show = function() {
    1059     var rect;
    1060     if (this.stopShowing) {
    1061       this.stopShowing = false;
    1062       return;
    1063     }
    1064     if (!this.visible()) {
    1065       this.$el.show();
    1066       this.$el.scrollTop(0);
    1067       this.context.trigger('shown');
    1068     }
    1069     if (rect = this.context.rect()) {
    1070       return this.reposition(rect);
    1071     }
    1072   };
    1073 
    1074   View.prototype.hide = function(e, time) {
    1075     var callback;
    1076     if (!this.visible()) {
    1077       return;
    1078     }
    1079     if (isNaN(time)) {
    1080       this.$el.hide();
    1081       return this.context.trigger('hidden', [e]);
    1082     } else {
    1083       callback = (function(_this) {
    1084         return function() {
    1085           return _this.hide();
    1086         };
    1087       })(this);
    1088       clearTimeout(this.timeoutID);
    1089       return this.timeoutID = setTimeout(callback, time);
    1090     }
    1091   };
    1092 
    1093   View.prototype.render = function(list) {
    1094     var $li, $ul, i, item, len, li, tpl;
    1095     if (!($.isArray(list) && list.length > 0)) {
    1096       this.hide();
    1097       return;
    1098     }
    1099     this.$el.find('ul').empty();
    1100     $ul = this.$el.find('ul');
    1101     tpl = this.context.getOpt('displayTpl');
    1102     for (i = 0, len = list.length; i < len; i++) {
    1103       item = list[i];
    1104       item = $.extend({}, item, {
    1105         'atwho-at': this.context.at
    1106       });
    1107       li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay");
    1108       $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
    1109       $li.data("item-data", item);
    1110       $ul.append($li);
    1111     }
    1112     this.show();
    1113     if (this.context.getOpt('highlightFirst')) {
    1114       return $ul.find("li:first").addClass("cur");
    1115     }
    1116   };
    1117 
    1118   return View;
    1119 
    1120 })();
    1121 
    1122 var Api;
    1123 
    1124 Api = {
    1125   load: function(at, data) {
    1126     var c;
    1127     if (c = this.controller(at)) {
    1128       return c.model.load(data);
    1129     }
    1130   },
    1131   isSelecting: function() {
    1132     var ref;
    1133     return !!((ref = this.controller()) != null ? ref.view.visible() : void 0);
    1134   },
    1135   hide: function() {
    1136     var ref;
    1137     return (ref = this.controller()) != null ? ref.view.hide() : void 0;
    1138   },
    1139   reposition: function() {
    1140     var c;
    1141     if (c = this.controller()) {
    1142       return c.view.reposition(c.rect());
    1143     }
    1144   },
    1145   setIframe: function(iframe, asRoot) {
    1146     this.setupRootElement(iframe, asRoot);
    1147     return null;
    1148   },
    1149   run: function() {
    1150     return this.dispatch();
    1151   },
    1152   destroy: function() {
    1153     this.shutdown();
    1154     return this.$inputor.data('atwho', null);
    1155   }
    1156 };
    1157 
    1158 $.fn.atwho = function(method) {
    1159   var _args, result;
    1160   _args = arguments;
    1161   result = null;
    1162   this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
    1163     var $this, app;
    1164     if (!(app = ($this = $(this)).data("atwho"))) {
    1165       $this.data('atwho', (app = new App(this)));
    1166     }
    1167     if (typeof method === 'object' || !method) {
    1168       return app.reg(method.at, method);
    1169     } else if (Api[method] && app) {
    1170       return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
    1171     } else {
    1172       return $.error("Method " + method + " does not exist on jQuery.atwho");
    1173     }
    1174   });
    1175   if (result != null) {
    1176     return result;
    1177   } else {
    1178     return this;
    1179   }
    1180 };
    1181 
    1182 $.fn.atwho["default"] = {
    1183   at: void 0,
    1184   alias: void 0,
    1185   data: null,
    1186   displayTpl: "<li>${name}</li>",
    1187   insertTpl: "${atwho-at}${name}",
    1188   headerTpl: null,
    1189   callbacks: DEFAULT_CALLBACKS,
    1190   functionOverrides: {},
    1191   searchKey: "name",
    1192   suffix: void 0,
    1193   hideWithoutSuffix: false,
    1194   startWithSpace: true,
    1195   acceptSpaceBar: false,
    1196   highlightFirst: true,
    1197   limit: 5,
    1198   maxLen: 20,
    1199   minLen: 0,
    1200   displayTimeout: 300,
    1201   delay: null,
    1202   spaceSelectsMatch: false,
    1203   tabSelectsMatch: true,
    1204   editableAtwhoQueryAttrs: {},
    1205   scrollDuration: 150,
    1206   suspendOnComposing: true,
    1207   lookUpOnClick: true
    1208 };
    1209 
    1210 $.fn.atwho.debug = false;
    1211 
    1212 }));
  • deleted file src/bp-core/js/vendor/jquery.atwho.txt

    diff --git a/src/bp-core/js/vendor/jquery.atwho.txt b/src/bp-core/js/vendor/jquery.atwho.txt
    deleted file mode 100644
    index 36cd1c122..000000000
    + -  
    1 Copyright (c) 2013 chord.luo@gmail.com
    2 
    3 Permission is hereby granted, free of charge, to any person
    4 obtaining a copy of this software and associated documentation
    5 files (the "Software"), to deal in the Software without
    6 restriction, including without limitation the rights to use,
    7 copy, modify, merge, publish, distribute, sublicense, and/or sell
    8 copies of the Software, and to permit persons to whom the
    9 Software is furnished to do so, subject to the following
    10 conditions:
    11 
    12 The above copyright notice and this permission notice shall be
    13 included in all copies or substantial portions of the Software.
    14 
    15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    17 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    18 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    19 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    20 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    21 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    22 OTHER DEALINGS IN THE SOFTWARE.
  • deleted file src/bp-core/js/vendor/jquery.caret.js

    diff --git a/src/bp-core/js/vendor/jquery.caret.js b/src/bp-core/js/vendor/jquery.caret.js
    deleted file mode 100755
    index a63ceda93..000000000
    + -  
    1 (function (root, factory) {
    2   if (typeof define === 'function' && define.amd) {
    3     // AMD. Register as an anonymous module.
    4     define(["jquery"], function ($) {
    5       return (root.returnExportsGlobal = factory($));
    6     });
    7   } else if (typeof exports === 'object') {
    8     // Node. Does not work with strict CommonJS, but
    9     // only CommonJS-like environments that support module.exports,
    10     // like Node.
    11     module.exports = factory(require("jquery"));
    12   } else {
    13     factory(jQuery);
    14   }
    15 }(this, function ($) {
    16 
    17 /*
    18   Implement Github like autocomplete mentions
    19   http://ichord.github.com/At.js
    20 
    21   Copyright (c) 2013 chord.luo@gmail.com
    22   Licensed under the MIT license.
    23 */
    24 
    25 /*
    26 本插件操作 textarea 或者 input 内的插入符
    27 只实现了获得插入符在文本框中的位置,我设置
    28 插入符的位置.
    29 */
    30 
    31 "use strict";
    32 var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
    33 
    34 pluginName = 'caret';
    35 
    36 EditableCaret = (function() {
    37   function EditableCaret($inputor) {
    38     this.$inputor = $inputor;
    39     this.domInputor = this.$inputor[0];
    40   }
    41 
    42   EditableCaret.prototype.setPos = function(pos) {
    43     var fn, found, offset, sel;
    44     if (sel = oWindow.getSelection()) {
    45       offset = 0;
    46       found = false;
    47       (fn = function(pos, parent) {
    48         var node, range, _i, _len, _ref, _results;
    49         _ref = parent.childNodes;
    50         _results = [];
    51         for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    52           node = _ref[_i];
    53           if (found) {
    54             break;
    55           }
    56           if (node.nodeType === 3) {
    57             if (offset + node.length >= pos) {
    58               found = true;
    59               range = oDocument.createRange();
    60               range.setStart(node, pos - offset);
    61               sel.removeAllRanges();
    62               sel.addRange(range);
    63               break;
    64             } else {
    65               _results.push(offset += node.length);
    66             }
    67           } else {
    68             _results.push(fn(pos, node));
    69           }
    70         }
    71         return _results;
    72       })(pos, this.domInputor);
    73     }
    74     return this.domInputor;
    75   };
    76 
    77   EditableCaret.prototype.getIEPosition = function() {
    78     return this.getPosition();
    79   };
    80 
    81   EditableCaret.prototype.getPosition = function() {
    82     var inputor_offset, offset;
    83     offset = this.getOffset();
    84     inputor_offset = this.$inputor.offset();
    85     offset.left -= inputor_offset.left;
    86     offset.top -= inputor_offset.top;
    87     return offset;
    88   };
    89 
    90   EditableCaret.prototype.getOldIEPos = function() {
    91     var preCaretTextRange, textRange;
    92     textRange = oDocument.selection.createRange();
    93     preCaretTextRange = oDocument.body.createTextRange();
    94     preCaretTextRange.moveToElementText(this.domInputor);
    95     preCaretTextRange.setEndPoint("EndToEnd", textRange);
    96     return preCaretTextRange.text.length;
    97   };
    98 
    99   EditableCaret.prototype.getPos = function() {
    100     var clonedRange, pos, range;
    101     if (range = this.range()) {
    102       clonedRange = range.cloneRange();
    103       clonedRange.selectNodeContents(this.domInputor);
    104       clonedRange.setEnd(range.endContainer, range.endOffset);
    105       pos = clonedRange.toString().length;
    106       clonedRange.detach();
    107       return pos;
    108     } else if (oDocument.selection) {
    109       return this.getOldIEPos();
    110     }
    111   };
    112 
    113   EditableCaret.prototype.getOldIEOffset = function() {
    114     var range, rect;
    115     range = oDocument.selection.createRange().duplicate();
    116     range.moveStart("character", -1);
    117     rect = range.getBoundingClientRect();
    118     return {
    119       height: rect.bottom - rect.top,
    120       left: rect.left,
    121       top: rect.top
    122     };
    123   };
    124 
    125   EditableCaret.prototype.getOffset = function(pos) {
    126     var clonedRange, offset, range, rect, shadowCaret;
    127     if (oWindow.getSelection && (range = this.range())) {
    128       if (range.endOffset - 1 > 0 && range.endContainer !== this.domInputor) {
    129         clonedRange = range.cloneRange();
    130         clonedRange.setStart(range.endContainer, range.endOffset - 1);
    131         clonedRange.setEnd(range.endContainer, range.endOffset);
    132         rect = clonedRange.getBoundingClientRect();
    133         offset = {
    134           height: rect.height,
    135           left: rect.left + rect.width,
    136           top: rect.top
    137         };
    138         clonedRange.detach();
    139       }
    140       if (!offset || (offset != null ? offset.height : void 0) === 0) {
    141         clonedRange = range.cloneRange();
    142         shadowCaret = $(oDocument.createTextNode("|"));
    143         clonedRange.insertNode(shadowCaret[0]);
    144         clonedRange.selectNode(shadowCaret[0]);
    145         rect = clonedRange.getBoundingClientRect();
    146         offset = {
    147           height: rect.height,
    148           left: rect.left,
    149           top: rect.top
    150         };
    151         shadowCaret.remove();
    152         clonedRange.detach();
    153       }
    154     } else if (oDocument.selection) {
    155       offset = this.getOldIEOffset();
    156     }
    157     if (offset) {
    158       offset.top += $(oWindow).scrollTop();
    159       offset.left += $(oWindow).scrollLeft();
    160     }
    161     return offset;
    162   };
    163 
    164   EditableCaret.prototype.range = function() {
    165     var sel;
    166     if (!oWindow.getSelection) {
    167       return;
    168     }
    169     sel = oWindow.getSelection();
    170     if (sel.rangeCount > 0) {
    171       return sel.getRangeAt(0);
    172     } else {
    173       return null;
    174     }
    175   };
    176 
    177   return EditableCaret;
    178 
    179 })();
    180 
    181 InputCaret = (function() {
    182   function InputCaret($inputor) {
    183     this.$inputor = $inputor;
    184     this.domInputor = this.$inputor[0];
    185   }
    186 
    187   InputCaret.prototype.getIEPos = function() {
    188     var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
    189     inputor = this.domInputor;
    190     range = oDocument.selection.createRange();
    191     pos = 0;
    192     if (range && range.parentElement() === inputor) {
    193       normalizedValue = inputor.value.replace(/\r\n/g, "\n");
    194       len = normalizedValue.length;
    195       textInputRange = inputor.createTextRange();
    196       textInputRange.moveToBookmark(range.getBookmark());
    197       endRange = inputor.createTextRange();
    198       endRange.collapse(false);
    199       if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
    200         pos = len;
    201       } else {
    202         pos = -textInputRange.moveStart("character", -len);
    203       }
    204     }
    205     return pos;
    206   };
    207 
    208   InputCaret.prototype.getPos = function() {
    209     if (oDocument.selection) {
    210       return this.getIEPos();
    211     } else {
    212       return this.domInputor.selectionStart;
    213     }
    214   };
    215 
    216   InputCaret.prototype.setPos = function(pos) {
    217     var inputor, range;
    218     inputor = this.domInputor;
    219     if (oDocument.selection) {
    220       range = inputor.createTextRange();
    221       range.move("character", pos);
    222       range.select();
    223     } else if (inputor.setSelectionRange) {
    224       inputor.setSelectionRange(pos, pos);
    225     }
    226     return inputor;
    227   };
    228 
    229   InputCaret.prototype.getIEOffset = function(pos) {
    230     var h, textRange, x, y;
    231     textRange = this.domInputor.createTextRange();
    232     pos || (pos = this.getPos());
    233     textRange.move('character', pos);
    234     x = textRange.boundingLeft;
    235     y = textRange.boundingTop;
    236     h = textRange.boundingHeight;
    237     return {
    238       left: x,
    239       top: y,
    240       height: h
    241     };
    242   };
    243 
    244   InputCaret.prototype.getOffset = function(pos) {
    245     var $inputor, offset, position;
    246     $inputor = this.$inputor;
    247     if (oDocument.selection) {
    248       offset = this.getIEOffset(pos);
    249       offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
    250       offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
    251       return offset;
    252     } else {
    253       offset = $inputor.offset();
    254       position = this.getPosition(pos);
    255       return offset = {
    256         left: offset.left + position.left - $inputor.scrollLeft(),
    257         top: offset.top + position.top - $inputor.scrollTop(),
    258         height: position.height
    259       };
    260     }
    261   };
    262 
    263   InputCaret.prototype.getPosition = function(pos) {
    264     var $inputor, at_rect, end_range, format, html, mirror, start_range;
    265     $inputor = this.$inputor;
    266     format = function(value) {
    267       value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
    268       if (/firefox/i.test(navigator.userAgent)) {
    269         value = value.replace(/\s/g, '&nbsp;');
    270       }
    271       return value;
    272     };
    273     if (pos === void 0) {
    274       pos = this.getPos();
    275     }
    276     start_range = $inputor.val().slice(0, pos);
    277     end_range = $inputor.val().slice(pos);
    278     html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
    279     html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
    280     html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
    281     mirror = new Mirror($inputor);
    282     return at_rect = mirror.create(html).rect();
    283   };
    284 
    285   InputCaret.prototype.getIEPosition = function(pos) {
    286     var h, inputorOffset, offset, x, y;
    287     offset = this.getIEOffset(pos);
    288     inputorOffset = this.$inputor.offset();
    289     x = offset.left - inputorOffset.left;
    290     y = offset.top - inputorOffset.top;
    291     h = offset.height;
    292     return {
    293       left: x,
    294       top: y,
    295       height: h
    296     };
    297   };
    298 
    299   return InputCaret;
    300 
    301 })();
    302 
    303 Mirror = (function() {
    304   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"];
    305 
    306   function Mirror($inputor) {
    307     this.$inputor = $inputor;
    308   }
    309 
    310   Mirror.prototype.mirrorCss = function() {
    311     var css,
    312       _this = this;
    313     css = {
    314       position: 'absolute',
    315       left: -9999,
    316       top: 0,
    317       zIndex: -20000
    318     };
    319     if (this.$inputor.prop('tagName') === 'TEXTAREA') {
    320       this.css_attr.push('width');
    321     }
    322     $.each(this.css_attr, function(i, p) {
    323       return css[p] = _this.$inputor.css(p);
    324     });
    325     return css;
    326   };
    327 
    328   Mirror.prototype.create = function(html) {
    329     this.$mirror = $('<div></div>');
    330     this.$mirror.css(this.mirrorCss());
    331     this.$mirror.html(html);
    332     this.$inputor.after(this.$mirror);
    333     return this;
    334   };
    335 
    336   Mirror.prototype.rect = function() {
    337     var $flag, pos, rect;
    338     $flag = this.$mirror.find("#caret");
    339     pos = $flag.position();
    340     rect = {
    341       left: pos.left,
    342       top: pos.top,
    343       height: $flag.height()
    344     };
    345     this.$mirror.remove();
    346     return rect;
    347   };
    348 
    349   return Mirror;
    350 
    351 })();
    352 
    353 Utils = {
    354   contentEditable: function($inputor) {
    355     return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
    356   }
    357 };
    358 
    359 methods = {
    360   pos: function(pos) {
    361     if (pos || pos === 0) {
    362       return this.setPos(pos);
    363     } else {
    364       return this.getPos();
    365     }
    366   },
    367   position: function(pos) {
    368     if (oDocument.selection) {
    369       return this.getIEPosition(pos);
    370     } else {
    371       return this.getPosition(pos);
    372     }
    373   },
    374   offset: function(pos) {
    375     var offset;
    376     offset = this.getOffset(pos);
    377     return offset;
    378   }
    379 };
    380 
    381 oDocument = null;
    382 
    383 oWindow = null;
    384 
    385 oFrame = null;
    386 
    387 setContextBy = function(settings) {
    388   var iframe;
    389   if (iframe = settings != null ? settings.iframe : void 0) {
    390     oFrame = iframe;
    391     oWindow = iframe.contentWindow;
    392     return oDocument = iframe.contentDocument || oWindow.document;
    393   } else {
    394     oFrame = void 0;
    395     oWindow = window;
    396     return oDocument = document;
    397   }
    398 };
    399 
    400 discoveryIframeOf = function($dom) {
    401   var error;
    402   oDocument = $dom[0].ownerDocument;
    403   oWindow = oDocument.defaultView || oDocument.parentWindow;
    404   try {
    405     return oFrame = oWindow.frameElement;
    406   } catch (_error) {
    407     error = _error;
    408   }
    409 };
    410 
    411 $.fn.caret = function(method, value, settings) {
    412   var caret;
    413   if (methods[method]) {
    414     if ($.isPlainObject(value)) {
    415       setContextBy(value);
    416       value = void 0;
    417     } else {
    418       setContextBy(settings);
    419     }
    420     caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
    421     return methods[method].apply(caret, [value]);
    422   } else {
    423     return $.error("Method " + method + " does not exist on jQuery.caret");
    424   }
    425 };
    426 
    427 $.fn.caret.EditableCaret = EditableCaret;
    428 
    429 $.fn.caret.InputCaret = InputCaret;
    430 
    431 $.fn.caret.Utils = Utils;
    432 
    433 $.fn.caret.apis = methods;
    434 
    435 
    436 }));
  • deleted file src/bp-core/js/vendor/jquery.caret.txt

    diff --git a/src/bp-core/js/vendor/jquery.caret.txt b/src/bp-core/js/vendor/jquery.caret.txt
    deleted file mode 100644
    index 36cd1c122..000000000
    + -  
    1 Copyright (c) 2013 chord.luo@gmail.com
    2 
    3 Permission is hereby granted, free of charge, to any person
    4 obtaining a copy of this software and associated documentation
    5 files (the "Software"), to deal in the Software without
    6 restriction, including without limitation the rights to use,
    7 copy, modify, merge, publish, distribute, sublicense, and/or sell
    8 copies of the Software, and to permit persons to whom the
    9 Software is furnished to do so, subject to the following
    10 conditions:
    11 
    12 The above copyright notice and this permission notice shall be
    13 included in all copies or substantial portions of the Software.
    14 
    15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    17 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    18 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    19 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    20 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    21 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    22 OTHER DEALINGS IN THE SOFTWARE.
  • new file src/bp-core/js/vendor/tribute.js

    diff --git a/src/bp-core/js/vendor/tribute.js b/src/bp-core/js/vendor/tribute.js
    new file mode 100644
    index 000000000..9c46e6357
    - +  
     1(function (global, factory) {
     2  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
     3  typeof define === 'function' && define.amd ? define(factory) :
     4  (global = global || self, global.Tribute = factory());
     5}(this, (function () { 'use strict';
     6
     7  function _classCallCheck(instance, Constructor) {
     8    if (!(instance instanceof Constructor)) {
     9      throw new TypeError("Cannot call a class as a function");
     10    }
     11  }
     12
     13  function _defineProperties(target, props) {
     14    for (var i = 0; i < props.length; i++) {
     15      var descriptor = props[i];
     16      descriptor.enumerable = descriptor.enumerable || false;
     17      descriptor.configurable = true;
     18      if ("value" in descriptor) descriptor.writable = true;
     19      Object.defineProperty(target, descriptor.key, descriptor);
     20    }
     21  }
     22
     23  function _createClass(Constructor, protoProps, staticProps) {
     24    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
     25    if (staticProps) _defineProperties(Constructor, staticProps);
     26    return Constructor;
     27  }
     28
     29  function _slicedToArray(arr, i) {
     30    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
     31  }
     32
     33  function _arrayWithHoles(arr) {
     34    if (Array.isArray(arr)) return arr;
     35  }
     36
     37  function _iterableToArrayLimit(arr, i) {
     38    if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
     39    var _arr = [];
     40    var _n = true;
     41    var _d = false;
     42    var _e = undefined;
     43
     44    try {
     45      for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
     46        _arr.push(_s.value);
     47
     48        if (i && _arr.length === i) break;
     49      }
     50    } catch (err) {
     51      _d = true;
     52      _e = err;
     53    } finally {
     54      try {
     55        if (!_n && _i["return"] != null) _i["return"]();
     56      } finally {
     57        if (_d) throw _e;
     58      }
     59    }
     60
     61    return _arr;
     62  }
     63
     64  function _unsupportedIterableToArray(o, minLen) {
     65    if (!o) return;
     66    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
     67    var n = Object.prototype.toString.call(o).slice(8, -1);
     68    if (n === "Object" && o.constructor) n = o.constructor.name;
     69    if (n === "Map" || n === "Set") return Array.from(n);
     70    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
     71  }
     72
     73  function _arrayLikeToArray(arr, len) {
     74    if (len == null || len > arr.length) len = arr.length;
     75
     76    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
     77
     78    return arr2;
     79  }
     80
     81  function _nonIterableRest() {
     82    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
     83  }
     84
     85  if (!Array.prototype.find) {
     86    Array.prototype.find = function (predicate) {
     87      if (this === null) {
     88        throw new TypeError('Array.prototype.find called on null or undefined');
     89      }
     90
     91      if (typeof predicate !== 'function') {
     92        throw new TypeError('predicate must be a function');
     93      }
     94
     95      var list = Object(this);
     96      var length = list.length >>> 0;
     97      var thisArg = arguments[1];
     98      var value;
     99
     100      for (var i = 0; i < length; i++) {
     101        value = list[i];
     102
     103        if (predicate.call(thisArg, value, i, list)) {
     104          return value;
     105        }
     106      }
     107
     108      return undefined;
     109    };
     110  }
     111
     112  if (window && typeof window.CustomEvent !== "function") {
     113    var CustomEvent$1 = function CustomEvent(event, params) {
     114      params = params || {
     115        bubbles: false,
     116        cancelable: false,
     117        detail: undefined
     118      };
     119      var evt = document.createEvent('CustomEvent');
     120      evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
     121      return evt;
     122    };
     123
     124    if (typeof window.Event !== 'undefined') {
     125      CustomEvent$1.prototype = window.Event.prototype;
     126    }
     127
     128    window.CustomEvent = CustomEvent$1;
     129  }
     130
     131  var TributeEvents = /*#__PURE__*/function () {
     132    function TributeEvents(tribute) {
     133      _classCallCheck(this, TributeEvents);
     134
     135      this.tribute = tribute;
     136      this.tribute.events = this;
     137    }
     138
     139    _createClass(TributeEvents, [{
     140      key: "bind",
     141      value: function bind(element) {
     142        element.boundKeydown = this.keydown.bind(element, this);
     143        element.boundKeyup = this.keyup.bind(element, this);
     144        element.boundInput = this.input.bind(element, this);
     145        element.addEventListener("keydown", element.boundKeydown, false);
     146        element.addEventListener("keyup", element.boundKeyup, false);
     147        element.addEventListener("input", element.boundInput, false);
     148      }
     149    }, {
     150      key: "unbind",
     151      value: function unbind(element) {
     152        element.removeEventListener("keydown", element.boundKeydown, false);
     153        element.removeEventListener("keyup", element.boundKeyup, false);
     154        element.removeEventListener("input", element.boundInput, false);
     155        delete element.boundKeydown;
     156        delete element.boundKeyup;
     157        delete element.boundInput;
     158      }
     159    }, {
     160      key: "keydown",
     161      value: function keydown(instance, event) {
     162        if (instance.shouldDeactivate(event)) {
     163          instance.tribute.isActive = false;
     164          instance.tribute.hideMenu();
     165        }
     166
     167        var element = this;
     168        instance.commandEvent = false;
     169        TributeEvents.keys().forEach(function (o) {
     170          if (o.key === event.keyCode) {
     171            instance.commandEvent = true;
     172            instance.callbacks()[o.value.toLowerCase()](event, element);
     173          }
     174        });
     175      }
     176    }, {
     177      key: "input",
     178      value: function input(instance, event) {
     179        instance.inputEvent = true;
     180        instance.keyup.call(this, instance, event);
     181      }
     182    }, {
     183      key: "click",
     184      value: function click(instance, event) {
     185        var tribute = instance.tribute;
     186
     187        if (tribute.menu && tribute.menu.contains(event.target)) {
     188          var li = event.target;
     189          event.preventDefault();
     190          event.stopPropagation();
     191
     192          while (li.nodeName.toLowerCase() !== "li") {
     193            li = li.parentNode;
     194
     195            if (!li || li === tribute.menu) {
     196              throw new Error("cannot find the <li> container for the click");
     197            }
     198          }
     199
     200          tribute.selectItemAtIndex(li.getAttribute("data-index"), event);
     201          tribute.hideMenu(); // TODO: should fire with externalTrigger and target is outside of menu
     202        } else if (tribute.current.element && !tribute.current.externalTrigger) {
     203          tribute.current.externalTrigger = false;
     204          setTimeout(function () {
     205            return tribute.hideMenu();
     206          });
     207        }
     208      }
     209    }, {
     210      key: "keyup",
     211      value: function keyup(instance, event) {
     212        if (instance.inputEvent) {
     213          instance.inputEvent = false;
     214        }
     215
     216        instance.updateSelection(this);
     217        if (event.keyCode === 27) return;
     218
     219        if (!instance.tribute.allowSpaces && instance.tribute.hasTrailingSpace) {
     220          instance.tribute.hasTrailingSpace = false;
     221          instance.commandEvent = true;
     222          instance.callbacks()["space"](event, this);
     223          return;
     224        }
     225
     226        if (!instance.tribute.isActive) {
     227          if (instance.tribute.autocompleteMode) {
     228            instance.callbacks().triggerChar(event, this, "");
     229          } else {
     230            var keyCode = instance.getKeyCode(instance, this, event);
     231            if (isNaN(keyCode) || !keyCode) return;
     232            var trigger = instance.tribute.triggers().find(function (trigger) {
     233              return trigger.charCodeAt(0) === keyCode;
     234            });
     235
     236            if (typeof trigger !== "undefined") {
     237              instance.callbacks().triggerChar(event, this, trigger);
     238            }
     239          }
     240        }
     241
     242        if (instance.tribute.current.mentionText.length < instance.tribute.current.collection.menuShowMinLength) {
     243          return;
     244        }
     245
     246        if ((instance.tribute.current.trigger || instance.tribute.autocompleteMode) && instance.commandEvent === false || instance.tribute.isActive && event.keyCode === 8) {
     247          instance.tribute.showMenuFor(this, true);
     248        }
     249      }
     250    }, {
     251      key: "shouldDeactivate",
     252      value: function shouldDeactivate(event) {
     253        if (!this.tribute.isActive) return false;
     254
     255        if (this.tribute.current.mentionText.length === 0) {
     256          var eventKeyPressed = false;
     257          TributeEvents.keys().forEach(function (o) {
     258            if (event.keyCode === o.key) eventKeyPressed = true;
     259          });
     260          return !eventKeyPressed;
     261        }
     262
     263        return false;
     264      }
     265    }, {
     266      key: "getKeyCode",
     267      value: function getKeyCode(instance, el, event) {
     268
     269        var tribute = instance.tribute;
     270        var info = tribute.range.getTriggerInfo(false, tribute.hasTrailingSpace, true, tribute.allowSpaces, tribute.autocompleteMode);
     271
     272        if (info) {
     273          return info.mentionTriggerChar.charCodeAt(0);
     274        } else {
     275          return false;
     276        }
     277      }
     278    }, {
     279      key: "updateSelection",
     280      value: function updateSelection(el) {
     281        this.tribute.current.element = el;
     282        var info = this.tribute.range.getTriggerInfo(false, this.tribute.hasTrailingSpace, true, this.tribute.allowSpaces, this.tribute.autocompleteMode);
     283
     284        if (info) {
     285          this.tribute.current.selectedPath = info.mentionSelectedPath;
     286          this.tribute.current.mentionText = info.mentionText;
     287          this.tribute.current.selectedOffset = info.mentionSelectedOffset;
     288        }
     289      }
     290    }, {
     291      key: "callbacks",
     292      value: function callbacks() {
     293        var _this = this;
     294
     295        return {
     296          triggerChar: function triggerChar(e, el, trigger) {
     297            var tribute = _this.tribute;
     298            tribute.current.trigger = trigger;
     299            var collectionItem = tribute.collection.find(function (item) {
     300              return item.trigger === trigger;
     301            });
     302            tribute.current.collection = collectionItem;
     303
     304            if (tribute.current.mentionText.length >= tribute.current.collection.menuShowMinLength && tribute.inputEvent) {
     305              tribute.showMenuFor(el, true);
     306            }
     307          },
     308          enter: function enter(e, el) {
     309            // choose selection
     310            if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
     311              e.preventDefault();
     312              e.stopPropagation();
     313              setTimeout(function () {
     314                _this.tribute.selectItemAtIndex(_this.tribute.menuSelected, e);
     315
     316                _this.tribute.hideMenu();
     317              }, 0);
     318            }
     319          },
     320          escape: function escape(e, el) {
     321            if (_this.tribute.isActive) {
     322              e.preventDefault();
     323              e.stopPropagation();
     324              _this.tribute.isActive = false;
     325
     326              _this.tribute.hideMenu();
     327            }
     328          },
     329          tab: function tab(e, el) {
     330            // choose first match
     331            _this.callbacks().enter(e, el);
     332          },
     333          space: function space(e, el) {
     334            if (_this.tribute.isActive) {
     335              if (_this.tribute.spaceSelectsMatch) {
     336                _this.callbacks().enter(e, el);
     337              } else if (!_this.tribute.allowSpaces) {
     338                e.stopPropagation();
     339                setTimeout(function () {
     340                  _this.tribute.hideMenu();
     341
     342                  _this.tribute.isActive = false;
     343                }, 0);
     344              }
     345            }
     346          },
     347          up: function up(e, el) {
     348            // navigate up ul
     349            if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
     350              e.preventDefault();
     351              e.stopPropagation();
     352              var count = _this.tribute.current.filteredItems.length,
     353                  selected = _this.tribute.menuSelected;
     354
     355              if (count > selected && selected > 0) {
     356                _this.tribute.menuSelected--;
     357
     358                _this.setActiveLi();
     359              } else if (selected === 0) {
     360                _this.tribute.menuSelected = count - 1;
     361
     362                _this.setActiveLi();
     363
     364                _this.tribute.menu.scrollTop = _this.tribute.menu.scrollHeight;
     365              }
     366            }
     367          },
     368          down: function down(e, el) {
     369            // navigate down ul
     370            if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
     371              e.preventDefault();
     372              e.stopPropagation();
     373              var count = _this.tribute.current.filteredItems.length - 1,
     374                  selected = _this.tribute.menuSelected;
     375
     376              if (count > selected) {
     377                _this.tribute.menuSelected++;
     378
     379                _this.setActiveLi();
     380              } else if (count === selected) {
     381                _this.tribute.menuSelected = 0;
     382
     383                _this.setActiveLi();
     384
     385                _this.tribute.menu.scrollTop = 0;
     386              }
     387            }
     388          },
     389          "delete": function _delete(e, el) {
     390            if (_this.tribute.isActive && _this.tribute.current.mentionText.length < 1) {
     391              _this.tribute.hideMenu();
     392            } else if (_this.tribute.isActive) {
     393              _this.tribute.showMenuFor(el);
     394            }
     395          }
     396        };
     397      }
     398    }, {
     399      key: "setActiveLi",
     400      value: function setActiveLi(index) {
     401        var lis = this.tribute.menu.querySelectorAll("li"),
     402            length = lis.length >>> 0;
     403        if (index) this.tribute.menuSelected = parseInt(index);
     404
     405        for (var i = 0; i < length; i++) {
     406          var li = lis[i];
     407
     408          if (i === this.tribute.menuSelected) {
     409            li.classList.add(this.tribute.current.collection.selectClass);
     410            var liClientRect = li.getBoundingClientRect();
     411            var menuClientRect = this.tribute.menu.getBoundingClientRect();
     412
     413            if (liClientRect.bottom > menuClientRect.bottom) {
     414              var scrollDistance = liClientRect.bottom - menuClientRect.bottom;
     415              this.tribute.menu.scrollTop += scrollDistance;
     416            } else if (liClientRect.top < menuClientRect.top) {
     417              var _scrollDistance = menuClientRect.top - liClientRect.top;
     418
     419              this.tribute.menu.scrollTop -= _scrollDistance;
     420            }
     421          } else {
     422            li.classList.remove(this.tribute.current.collection.selectClass);
     423          }
     424        }
     425      }
     426    }, {
     427      key: "getFullHeight",
     428      value: function getFullHeight(elem, includeMargin) {
     429        var height = elem.getBoundingClientRect().height;
     430
     431        if (includeMargin) {
     432          var style = elem.currentStyle || window.getComputedStyle(elem);
     433          return height + parseFloat(style.marginTop) + parseFloat(style.marginBottom);
     434        }
     435
     436        return height;
     437      }
     438    }], [{
     439      key: "keys",
     440      value: function keys() {
     441        return [{
     442          key: 9,
     443          value: "TAB"
     444        }, {
     445          key: 8,
     446          value: "DELETE"
     447        }, {
     448          key: 13,
     449          value: "ENTER"
     450        }, {
     451          key: 27,
     452          value: "ESCAPE"
     453        }, {
     454          key: 32,
     455          value: "SPACE"
     456        }, {
     457          key: 38,
     458          value: "UP"
     459        }, {
     460          key: 40,
     461          value: "DOWN"
     462        }];
     463      }
     464    }]);
     465
     466    return TributeEvents;
     467  }();
     468
     469  var TributeMenuEvents = /*#__PURE__*/function () {
     470    function TributeMenuEvents(tribute) {
     471      _classCallCheck(this, TributeMenuEvents);
     472
     473      this.tribute = tribute;
     474      this.tribute.menuEvents = this;
     475      this.menu = this.tribute.menu;
     476    }
     477
     478    _createClass(TributeMenuEvents, [{
     479      key: "bind",
     480      value: function bind(menu) {
     481        var _this = this;
     482
     483        this.menuClickEvent = this.tribute.events.click.bind(null, this);
     484        this.menuContainerScrollEvent = this.debounce(function () {
     485          if (_this.tribute.isActive) {
     486            _this.tribute.showMenuFor(_this.tribute.current.element, false);
     487          }
     488        }, 300, false);
     489        this.windowResizeEvent = this.debounce(function () {
     490          if (_this.tribute.isActive) {
     491            _this.tribute.range.positionMenuAtCaret(true);
     492          }
     493        }, 300, false); // fixes IE11 issues with mousedown
     494
     495        this.tribute.range.getDocument().addEventListener("MSPointerDown", this.menuClickEvent, false);
     496        this.tribute.range.getDocument().addEventListener("mousedown", this.menuClickEvent, false);
     497        window.addEventListener("resize", this.windowResizeEvent);
     498
     499        if (this.menuContainer) {
     500          this.menuContainer.addEventListener("scroll", this.menuContainerScrollEvent, false);
     501        } else {
     502          window.addEventListener("scroll", this.menuContainerScrollEvent);
     503        }
     504      }
     505    }, {
     506      key: "unbind",
     507      value: function unbind(menu) {
     508        this.tribute.range.getDocument().removeEventListener("mousedown", this.menuClickEvent, false);
     509        this.tribute.range.getDocument().removeEventListener("MSPointerDown", this.menuClickEvent, false);
     510        window.removeEventListener("resize", this.windowResizeEvent);
     511
     512        if (this.menuContainer) {
     513          this.menuContainer.removeEventListener("scroll", this.menuContainerScrollEvent, false);
     514        } else {
     515          window.removeEventListener("scroll", this.menuContainerScrollEvent);
     516        }
     517      }
     518    }, {
     519      key: "debounce",
     520      value: function debounce(func, wait, immediate) {
     521        var _arguments = arguments,
     522            _this2 = this;
     523
     524        var timeout;
     525        return function () {
     526          var context = _this2,
     527              args = _arguments;
     528
     529          var later = function later() {
     530            timeout = null;
     531            if (!immediate) func.apply(context, args);
     532          };
     533
     534          var callNow = immediate && !timeout;
     535          clearTimeout(timeout);
     536          timeout = setTimeout(later, wait);
     537          if (callNow) func.apply(context, args);
     538        };
     539      }
     540    }]);
     541
     542    return TributeMenuEvents;
     543  }();
     544
     545  var TributeRange = /*#__PURE__*/function () {
     546    function TributeRange(tribute) {
     547      _classCallCheck(this, TributeRange);
     548
     549      this.tribute = tribute;
     550      this.tribute.range = this;
     551    }
     552
     553    _createClass(TributeRange, [{
     554      key: "getDocument",
     555      value: function getDocument() {
     556        var iframe;
     557
     558        if (this.tribute.current.collection) {
     559          iframe = this.tribute.current.collection.iframe;
     560        }
     561
     562        if (!iframe) {
     563          return document;
     564        }
     565
     566        return iframe.contentWindow.document;
     567      }
     568    }, {
     569      key: "positionMenuAtCaret",
     570      value: function positionMenuAtCaret(scrollTo) {
     571        var _this = this;
     572
     573        var context = this.tribute.current,
     574            coordinates;
     575        var info = this.getTriggerInfo(false, this.tribute.hasTrailingSpace, true, this.tribute.allowSpaces, this.tribute.autocompleteMode);
     576
     577        if (typeof info !== 'undefined') {
     578          if (!this.tribute.positionMenu) {
     579            this.tribute.menu.style.cssText = "display: block;";
     580            return;
     581          }
     582
     583          if (!this.isContentEditable(context.element)) {
     584            coordinates = this.getTextAreaOrInputUnderlinePosition(this.tribute.current.element, info.mentionPosition);
     585          } else {
     586            coordinates = this.getContentEditableCaretPosition(info.mentionPosition);
     587          }
     588
     589          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;");
     590
     591          if (coordinates.left === 'auto') {
     592            this.tribute.menu.style.left = 'auto';
     593          }
     594
     595          if (coordinates.top === 'auto') {
     596            this.tribute.menu.style.top = 'auto';
     597          }
     598
     599          if (scrollTo) this.scrollIntoView();
     600          window.setTimeout(function () {
     601            var menuDimensions = {
     602              width: _this.tribute.menu.offsetWidth,
     603              height: _this.tribute.menu.offsetHeight
     604            };
     605
     606            var menuIsOffScreen = _this.isMenuOffScreen(coordinates, menuDimensions);
     607
     608            var menuIsOffScreenHorizontally = window.innerWidth > menuDimensions.width && (menuIsOffScreen.left || menuIsOffScreen.right);
     609            var menuIsOffScreenVertically = window.innerHeight > menuDimensions.height && (menuIsOffScreen.top || menuIsOffScreen.bottom);
     610
     611            if (menuIsOffScreenHorizontally || menuIsOffScreenVertically) {
     612              _this.tribute.menu.style.cssText = 'display: none';
     613
     614              _this.positionMenuAtCaret(scrollTo);
     615            }
     616          }, 0);
     617        } else {
     618          this.tribute.menu.style.cssText = 'display: none';
     619        }
     620      }
     621    }, {
     622      key: "selectElement",
     623      value: function selectElement(targetElement, path, offset) {
     624        var range;
     625        var elem = targetElement;
     626
     627        if (path) {
     628          for (var i = 0; i < path.length; i++) {
     629            elem = elem.childNodes[path[i]];
     630
     631            if (elem === undefined) {
     632              return;
     633            }
     634
     635            while (elem.length < offset) {
     636              offset -= elem.length;
     637              elem = elem.nextSibling;
     638            }
     639
     640            if (elem.childNodes.length === 0 && !elem.length) {
     641              elem = elem.previousSibling;
     642            }
     643          }
     644        }
     645
     646        var sel = this.getWindowSelection();
     647        range = this.getDocument().createRange();
     648        range.setStart(elem, offset);
     649        range.setEnd(elem, offset);
     650        range.collapse(true);
     651
     652        try {
     653          sel.removeAllRanges();
     654        } catch (error) {}
     655
     656        sel.addRange(range);
     657        targetElement.focus();
     658      }
     659    }, {
     660      key: "replaceTriggerText",
     661      value: function replaceTriggerText(text, requireLeadingSpace, hasTrailingSpace, originalEvent, item) {
     662        var info = this.getTriggerInfo(true, hasTrailingSpace, requireLeadingSpace, this.tribute.allowSpaces, this.tribute.autocompleteMode);
     663
     664        if (info !== undefined) {
     665          var context = this.tribute.current;
     666          var replaceEvent = new CustomEvent('tribute-replaced', {
     667            detail: {
     668              item: item,
     669              instance: context,
     670              context: info,
     671              event: originalEvent
     672            }
     673          });
     674
     675          if (!this.isContentEditable(context.element)) {
     676            var myField = this.tribute.current.element;
     677            var textSuffix = typeof this.tribute.replaceTextSuffix == 'string' ? this.tribute.replaceTextSuffix : ' ';
     678            text += textSuffix;
     679            var startPos = info.mentionPosition;
     680            var endPos = info.mentionPosition + info.mentionText.length + textSuffix.length;
     681
     682            if (!this.tribute.autocompleteMode) {
     683              endPos += info.mentionTriggerChar.length - 1;
     684            }
     685
     686            myField.value = myField.value.substring(0, startPos) + text + myField.value.substring(endPos, myField.value.length);
     687            myField.selectionStart = startPos + text.length;
     688            myField.selectionEnd = startPos + text.length;
     689          } else {
     690            // add a space to the end of the pasted text
     691            var _textSuffix = typeof this.tribute.replaceTextSuffix == 'string' ? this.tribute.replaceTextSuffix : '\xA0';
     692
     693            text += _textSuffix;
     694
     695            var _endPos = info.mentionPosition + info.mentionText.length;
     696
     697            if (!this.tribute.autocompleteMode) {
     698              _endPos += info.mentionTriggerChar.length;
     699            }
     700
     701            this.pasteHtml(text, info.mentionPosition, _endPos);
     702          }
     703
     704          context.element.dispatchEvent(new CustomEvent('input', {
     705            bubbles: true
     706          }));
     707          context.element.dispatchEvent(replaceEvent);
     708        }
     709      }
     710    }, {
     711      key: "pasteHtml",
     712      value: function pasteHtml(html, startPos, endPos) {
     713        var range, sel;
     714        sel = this.getWindowSelection();
     715        range = this.getDocument().createRange();
     716        range.setStart(sel.anchorNode, startPos);
     717        range.setEnd(sel.anchorNode, endPos);
     718        range.deleteContents();
     719        var el = this.getDocument().createElement('div');
     720        el.innerHTML = html;
     721        var frag = this.getDocument().createDocumentFragment(),
     722            node,
     723            lastNode;
     724
     725        while (node = el.firstChild) {
     726          lastNode = frag.appendChild(node);
     727        }
     728
     729        range.insertNode(frag); // Preserve the selection
     730
     731        if (lastNode) {
     732          range = range.cloneRange();
     733          range.setStartAfter(lastNode);
     734          range.collapse(true);
     735          sel.removeAllRanges();
     736          sel.addRange(range);
     737        }
     738      }
     739    }, {
     740      key: "getWindowSelection",
     741      value: function getWindowSelection() {
     742        if (this.tribute.collection.iframe) {
     743          return this.tribute.collection.iframe.contentWindow.getSelection();
     744        }
     745
     746        return window.getSelection();
     747      }
     748    }, {
     749      key: "getNodePositionInParent",
     750      value: function getNodePositionInParent(element) {
     751        if (element.parentNode === null) {
     752          return 0;
     753        }
     754
     755        for (var i = 0; i < element.parentNode.childNodes.length; i++) {
     756          var node = element.parentNode.childNodes[i];
     757
     758          if (node === element) {
     759            return i;
     760          }
     761        }
     762      }
     763    }, {
     764      key: "getContentEditableSelectedPath",
     765      value: function getContentEditableSelectedPath(ctx) {
     766        var sel = this.getWindowSelection();
     767        var selected = sel.anchorNode;
     768        var path = [];
     769        var offset;
     770
     771        if (selected != null) {
     772          var i;
     773          var ce = selected.contentEditable;
     774
     775          while (selected !== null && ce !== 'true') {
     776            i = this.getNodePositionInParent(selected);
     777            path.push(i);
     778            selected = selected.parentNode;
     779
     780            if (selected !== null) {
     781              ce = selected.contentEditable;
     782            }
     783          }
     784
     785          path.reverse(); // getRangeAt may not exist, need alternative
     786
     787          offset = sel.getRangeAt(0).startOffset;
     788          return {
     789            selected: selected,
     790            path: path,
     791            offset: offset
     792          };
     793        }
     794      }
     795    }, {
     796      key: "getTextPrecedingCurrentSelection",
     797      value: function getTextPrecedingCurrentSelection() {
     798        var context = this.tribute.current,
     799            text = '';
     800
     801        if (!this.isContentEditable(context.element)) {
     802          var textComponent = this.tribute.current.element;
     803
     804          if (textComponent) {
     805            var startPos = textComponent.selectionStart;
     806
     807            if (textComponent.value && startPos >= 0) {
     808              text = textComponent.value.substring(0, startPos);
     809            }
     810          }
     811        } else {
     812          var selectedElem = this.getWindowSelection().anchorNode;
     813
     814          if (selectedElem != null) {
     815            var workingNodeContent = selectedElem.textContent;
     816            var selectStartOffset = this.getWindowSelection().getRangeAt(0).startOffset;
     817
     818            if (workingNodeContent && selectStartOffset >= 0) {
     819              text = workingNodeContent.substring(0, selectStartOffset);
     820            }
     821          }
     822        }
     823
     824        return text;
     825      }
     826    }, {
     827      key: "getLastWordInText",
     828      value: function getLastWordInText(text) {
     829        text = text.replace(/\u00A0/g, ' '); // https://stackoverflow.com/questions/29850407/how-do-i-replace-unicode-character-u00a0-with-a-space-in-javascript
     830
     831        var wordsArray;
     832
     833        if (this.tribute.autocompleteSeparator) {
     834          wordsArray = text.split(this.tribute.autocompleteSeparator);
     835        } else {
     836          wordsArray = text.split(/\s+/);
     837        }
     838
     839        var worldsCount = wordsArray.length - 1;
     840        return wordsArray[worldsCount].trim();
     841      }
     842    }, {
     843      key: "getTriggerInfo",
     844      value: function getTriggerInfo(menuAlreadyActive, hasTrailingSpace, requireLeadingSpace, allowSpaces, isAutocomplete) {
     845        var _this2 = this;
     846
     847        var ctx = this.tribute.current;
     848        var selected, path, offset;
     849
     850        if (!this.isContentEditable(ctx.element)) {
     851          selected = this.tribute.current.element;
     852        } else {
     853          var selectionInfo = this.getContentEditableSelectedPath(ctx);
     854
     855          if (selectionInfo) {
     856            selected = selectionInfo.selected;
     857            path = selectionInfo.path;
     858            offset = selectionInfo.offset;
     859          }
     860        }
     861
     862        var effectiveRange = this.getTextPrecedingCurrentSelection();
     863        var lastWordOfEffectiveRange = this.getLastWordInText(effectiveRange);
     864
     865        if (isAutocomplete) {
     866          return {
     867            mentionPosition: effectiveRange.length - lastWordOfEffectiveRange.length,
     868            mentionText: lastWordOfEffectiveRange,
     869            mentionSelectedElement: selected,
     870            mentionSelectedPath: path,
     871            mentionSelectedOffset: offset
     872          };
     873        }
     874
     875        if (effectiveRange !== undefined && effectiveRange !== null) {
     876          var mostRecentTriggerCharPos = -1;
     877          var triggerChar;
     878          this.tribute.collection.forEach(function (config) {
     879            var c = config.trigger;
     880            var idx = config.requireLeadingSpace ? _this2.lastIndexWithLeadingSpace(effectiveRange, c) : effectiveRange.lastIndexOf(c);
     881
     882            if (idx > mostRecentTriggerCharPos) {
     883              mostRecentTriggerCharPos = idx;
     884              triggerChar = c;
     885              requireLeadingSpace = config.requireLeadingSpace;
     886            }
     887          });
     888
     889          if (mostRecentTriggerCharPos >= 0 && (mostRecentTriggerCharPos === 0 || !requireLeadingSpace || /[\xA0\s]/g.test(effectiveRange.substring(mostRecentTriggerCharPos - 1, mostRecentTriggerCharPos)))) {
     890            var currentTriggerSnippet = effectiveRange.substring(mostRecentTriggerCharPos + triggerChar.length, effectiveRange.length);
     891            triggerChar = effectiveRange.substring(mostRecentTriggerCharPos, mostRecentTriggerCharPos + triggerChar.length);
     892            var firstSnippetChar = currentTriggerSnippet.substring(0, 1);
     893            var leadingSpace = currentTriggerSnippet.length > 0 && (firstSnippetChar === ' ' || firstSnippetChar === '\xA0');
     894
     895            if (hasTrailingSpace) {
     896              currentTriggerSnippet = currentTriggerSnippet.trim();
     897            }
     898
     899            var regex = allowSpaces ? /[^\S ]/g : /[\xA0\s]/g;
     900            this.tribute.hasTrailingSpace = regex.test(currentTriggerSnippet);
     901
     902            if (!leadingSpace && (menuAlreadyActive || !regex.test(currentTriggerSnippet))) {
     903              return {
     904                mentionPosition: mostRecentTriggerCharPos,
     905                mentionText: currentTriggerSnippet,
     906                mentionSelectedElement: selected,
     907                mentionSelectedPath: path,
     908                mentionSelectedOffset: offset,
     909                mentionTriggerChar: triggerChar
     910              };
     911            }
     912          }
     913        }
     914      }
     915    }, {
     916      key: "lastIndexWithLeadingSpace",
     917      value: function lastIndexWithLeadingSpace(str, trigger) {
     918        var reversedStr = str.split('').reverse().join('');
     919        var index = -1;
     920
     921        for (var cidx = 0, len = str.length; cidx < len; cidx++) {
     922          var firstChar = cidx === str.length - 1;
     923          var leadingSpace = /\s/.test(reversedStr[cidx + 1]);
     924          var match = true;
     925
     926          for (var triggerIdx = trigger.length - 1; triggerIdx >= 0; triggerIdx--) {
     927            if (trigger[triggerIdx] !== reversedStr[cidx - triggerIdx]) {
     928              match = false;
     929              break;
     930            }
     931          }
     932
     933          if (match && (firstChar || leadingSpace)) {
     934            index = str.length - 1 - cidx;
     935            break;
     936          }
     937        }
     938
     939        return index;
     940      }
     941    }, {
     942      key: "isContentEditable",
     943      value: function isContentEditable(element) {
     944        return element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA';
     945      }
     946    }, {
     947      key: "isMenuOffScreen",
     948      value: function isMenuOffScreen(coordinates, menuDimensions) {
     949        var windowWidth = window.innerWidth;
     950        var windowHeight = window.innerHeight;
     951        var doc = document.documentElement;
     952        var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
     953        var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
     954        var menuTop = typeof coordinates.top === 'number' ? coordinates.top : windowTop + windowHeight - coordinates.bottom - menuDimensions.height;
     955        var menuRight = typeof coordinates.right === 'number' ? coordinates.right : coordinates.left + menuDimensions.width;
     956        var menuBottom = typeof coordinates.bottom === 'number' ? coordinates.bottom : coordinates.top + menuDimensions.height;
     957        var menuLeft = typeof coordinates.left === 'number' ? coordinates.left : windowLeft + windowWidth - coordinates.right - menuDimensions.width;
     958        return {
     959          top: menuTop < Math.floor(windowTop),
     960          right: menuRight > Math.ceil(windowLeft + windowWidth),
     961          bottom: menuBottom > Math.ceil(windowTop + windowHeight),
     962          left: menuLeft < Math.floor(windowLeft)
     963        };
     964      }
     965    }, {
     966      key: "getMenuDimensions",
     967      value: function getMenuDimensions() {
     968        // Width of the menu depends of its contents and position
     969        // We must check what its width would be without any obstruction
     970        // This way, we can achieve good positioning for flipping the menu
     971        var dimensions = {
     972          width: null,
     973          height: null
     974        };
     975        this.tribute.menu.style.cssText = "top: 0px;\n                                 left: 0px;\n                                 position: fixed;\n                                 display: block;\n                                 visibility; hidden;";
     976        dimensions.width = this.tribute.menu.offsetWidth;
     977        dimensions.height = this.tribute.menu.offsetHeight;
     978        this.tribute.menu.style.cssText = "display: none;";
     979        return dimensions;
     980      }
     981    }, {
     982      key: "getTextAreaOrInputUnderlinePosition",
     983      value: function getTextAreaOrInputUnderlinePosition(element, position, flipped) {
     984        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'];
     985        var isFirefox = window.mozInnerScreenX !== null;
     986        var div = this.getDocument().createElement('div');
     987        div.id = 'input-textarea-caret-position-mirror-div';
     988        this.getDocument().body.appendChild(div);
     989        var style = div.style;
     990        var computed = window.getComputedStyle ? getComputedStyle(element) : element.currentStyle;
     991        style.whiteSpace = 'pre-wrap';
     992
     993        if (element.nodeName !== 'INPUT') {
     994          style.wordWrap = 'break-word';
     995        } // position off-screen
     996
     997
     998        style.position = 'absolute';
     999        style.visibility = 'hidden'; // transfer the element's properties to the div
     1000
     1001        properties.forEach(function (prop) {
     1002          style[prop] = computed[prop];
     1003        });
     1004
     1005        if (isFirefox) {
     1006          style.width = "".concat(parseInt(computed.width) - 2, "px");
     1007          if (element.scrollHeight > parseInt(computed.height)) style.overflowY = 'scroll';
     1008        } else {
     1009          style.overflow = 'hidden';
     1010        }
     1011
     1012        div.textContent = element.value.substring(0, position);
     1013
     1014        if (element.nodeName === 'INPUT') {
     1015          div.textContent = div.textContent.replace(/\s/g, ' ');
     1016        }
     1017
     1018        var span = this.getDocument().createElement('span');
     1019        span.textContent = element.value.substring(position) || '.';
     1020        div.appendChild(span);
     1021        var rect = element.getBoundingClientRect();
     1022        var doc = document.documentElement;
     1023        var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
     1024        var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
     1025        var top = 0;
     1026        var left = 0;
     1027
     1028        if (this.menuContainerIsBody) {
     1029          top = rect.top;
     1030          left = rect.left;
     1031        }
     1032
     1033        var coordinates = {
     1034          top: top + windowTop + span.offsetTop + parseInt(computed.borderTopWidth) + parseInt(computed.fontSize) - element.scrollTop,
     1035          left: left + windowLeft + span.offsetLeft + parseInt(computed.borderLeftWidth)
     1036        };
     1037        var windowWidth = window.innerWidth;
     1038        var windowHeight = window.innerHeight;
     1039        var menuDimensions = this.getMenuDimensions();
     1040        var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
     1041
     1042        if (menuIsOffScreen.right) {
     1043          coordinates.right = windowWidth - coordinates.left;
     1044          coordinates.left = 'auto';
     1045        }
     1046
     1047        var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
     1048
     1049        if (menuIsOffScreen.bottom) {
     1050          var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
     1051          var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
     1052          coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top - span.offsetTop);
     1053          coordinates.top = 'auto';
     1054        }
     1055
     1056        menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
     1057
     1058        if (menuIsOffScreen.left) {
     1059          coordinates.left = windowWidth > menuDimensions.width ? windowLeft + windowWidth - menuDimensions.width : windowLeft;
     1060          delete coordinates.right;
     1061        }
     1062
     1063        if (menuIsOffScreen.top) {
     1064          coordinates.top = windowHeight > menuDimensions.height ? windowTop + windowHeight - menuDimensions.height : windowTop;
     1065          delete coordinates.bottom;
     1066        }
     1067
     1068        this.getDocument().body.removeChild(div);
     1069        return coordinates;
     1070      }
     1071    }, {
     1072      key: "getContentEditableCaretPosition",
     1073      value: function getContentEditableCaretPosition(selectedNodePosition) {
     1074        var range;
     1075        var sel = this.getWindowSelection();
     1076        range = this.getDocument().createRange();
     1077        range.setStart(sel.anchorNode, selectedNodePosition);
     1078        range.setEnd(sel.anchorNode, selectedNodePosition);
     1079        range.collapse(false);
     1080        var rect = range.getBoundingClientRect();
     1081        var doc = document.documentElement;
     1082        var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
     1083        var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
     1084        var left = rect.left;
     1085        var top = rect.top;
     1086        var coordinates = {
     1087          left: left + windowLeft,
     1088          top: top + rect.height + windowTop
     1089        };
     1090        var windowWidth = window.innerWidth;
     1091        var windowHeight = window.innerHeight;
     1092        var menuDimensions = this.getMenuDimensions();
     1093        var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
     1094
     1095        if (menuIsOffScreen.right) {
     1096          coordinates.left = 'auto';
     1097          coordinates.right = windowWidth - rect.left - windowLeft;
     1098        }
     1099
     1100        var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
     1101
     1102        if (menuIsOffScreen.bottom) {
     1103          var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
     1104          var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
     1105          coordinates.top = 'auto';
     1106          coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top);
     1107        }
     1108
     1109        menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
     1110
     1111        if (menuIsOffScreen.left) {
     1112          coordinates.left = windowWidth > menuDimensions.width ? windowLeft + windowWidth - menuDimensions.width : windowLeft;
     1113          delete coordinates.right;
     1114        }
     1115
     1116        if (menuIsOffScreen.top) {
     1117          coordinates.top = windowHeight > menuDimensions.height ? windowTop + windowHeight - menuDimensions.height : windowTop;
     1118          delete coordinates.bottom;
     1119        }
     1120
     1121        if (!this.menuContainerIsBody) {
     1122          coordinates.left = coordinates.left ? coordinates.left - this.tribute.menuContainer.offsetLeft : coordinates.left;
     1123          coordinates.top = coordinates.top ? coordinates.top - this.tribute.menuContainer.offsetTop : coordinates.top;
     1124        }
     1125
     1126        return coordinates;
     1127      }
     1128    }, {
     1129      key: "scrollIntoView",
     1130      value: function scrollIntoView(elem) {
     1131        var reasonableBuffer = 20,
     1132            clientRect;
     1133        var maxScrollDisplacement = 100;
     1134        var e = this.menu;
     1135        if (typeof e === 'undefined') return;
     1136
     1137        while (clientRect === undefined || clientRect.height === 0) {
     1138          clientRect = e.getBoundingClientRect();
     1139
     1140          if (clientRect.height === 0) {
     1141            e = e.childNodes[0];
     1142
     1143            if (e === undefined || !e.getBoundingClientRect) {
     1144              return;
     1145            }
     1146          }
     1147        }
     1148
     1149        var elemTop = clientRect.top;
     1150        var elemBottom = elemTop + clientRect.height;
     1151
     1152        if (elemTop < 0) {
     1153          window.scrollTo(0, window.pageYOffset + clientRect.top - reasonableBuffer);
     1154        } else if (elemBottom > window.innerHeight) {
     1155          var maxY = window.pageYOffset + clientRect.top - reasonableBuffer;
     1156
     1157          if (maxY - window.pageYOffset > maxScrollDisplacement) {
     1158            maxY = window.pageYOffset + maxScrollDisplacement;
     1159          }
     1160
     1161          var targetY = window.pageYOffset - (window.innerHeight - elemBottom);
     1162
     1163          if (targetY > maxY) {
     1164            targetY = maxY;
     1165          }
     1166
     1167          window.scrollTo(0, targetY);
     1168        }
     1169      }
     1170    }, {
     1171      key: "menuContainerIsBody",
     1172      get: function get() {
     1173        return this.tribute.menuContainer === document.body || !this.tribute.menuContainer;
     1174      }
     1175    }]);
     1176
     1177    return TributeRange;
     1178  }();
     1179
     1180  // Thanks to https://github.com/mattyork/fuzzy
     1181  var TributeSearch = /*#__PURE__*/function () {
     1182    function TributeSearch(tribute) {
     1183      _classCallCheck(this, TributeSearch);
     1184
     1185      this.tribute = tribute;
     1186      this.tribute.search = this;
     1187    }
     1188
     1189    _createClass(TributeSearch, [{
     1190      key: "simpleFilter",
     1191      value: function simpleFilter(pattern, array) {
     1192        var _this = this;
     1193
     1194        return array.filter(function (string) {
     1195          return _this.test(pattern, string);
     1196        });
     1197      }
     1198    }, {
     1199      key: "test",
     1200      value: function test(pattern, string) {
     1201        return this.match(pattern, string) !== null;
     1202      }
     1203    }, {
     1204      key: "match",
     1205      value: function match(pattern, string, opts) {
     1206        opts = opts || {};
     1207        var len = string.length,
     1208            pre = opts.pre || '',
     1209            post = opts.post || '',
     1210            compareString = opts.caseSensitive && string || string.toLowerCase();
     1211
     1212        if (opts.skip) {
     1213          return {
     1214            rendered: string,
     1215            score: 0
     1216          };
     1217        }
     1218
     1219        pattern = opts.caseSensitive && pattern || pattern.toLowerCase();
     1220        var patternCache = this.traverse(compareString, pattern, 0, 0, []);
     1221
     1222        if (!patternCache) {
     1223          return null;
     1224        }
     1225
     1226        return {
     1227          rendered: this.render(string, patternCache.cache, pre, post),
     1228          score: patternCache.score
     1229        };
     1230      }
     1231    }, {
     1232      key: "traverse",
     1233      value: function traverse(string, pattern, stringIndex, patternIndex, patternCache) {
     1234        if (this.tribute.autocompleteSeparator) {
     1235          // if the pattern search at end
     1236          pattern = pattern.split(this.tribute.autocompleteSeparator).splice(-1)[0];
     1237        }
     1238
     1239        if (pattern.length === patternIndex) {
     1240          // calculate score and copy the cache containing the indices where it's found
     1241          return {
     1242            score: this.calculateScore(patternCache),
     1243            cache: patternCache.slice()
     1244          };
     1245        } // if string at end or remaining pattern > remaining string
     1246
     1247
     1248        if (string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) {
     1249          return undefined;
     1250        }
     1251
     1252        var c = pattern[patternIndex];
     1253        var index = string.indexOf(c, stringIndex);
     1254        var best, temp;
     1255
     1256        while (index > -1) {
     1257          patternCache.push(index);
     1258          temp = this.traverse(string, pattern, index + 1, patternIndex + 1, patternCache);
     1259          patternCache.pop(); // if downstream traversal failed, return best answer so far
     1260
     1261          if (!temp) {
     1262            return best;
     1263          }
     1264
     1265          if (!best || best.score < temp.score) {
     1266            best = temp;
     1267          }
     1268
     1269          index = string.indexOf(c, index + 1);
     1270        }
     1271
     1272        return best;
     1273      }
     1274    }, {
     1275      key: "calculateScore",
     1276      value: function calculateScore(patternCache) {
     1277        var score = 0;
     1278        var temp = 1;
     1279        patternCache.forEach(function (index, i) {
     1280          if (i > 0) {
     1281            if (patternCache[i - 1] + 1 === index) {
     1282              temp += temp + 1;
     1283            } else {
     1284              temp = 1;
     1285            }
     1286          }
     1287
     1288          score += temp;
     1289        });
     1290        return score;
     1291      }
     1292    }, {
     1293      key: "render",
     1294      value: function render(string, indices, pre, post) {
     1295        var rendered = string.substring(0, indices[0]);
     1296        indices.forEach(function (index, i) {
     1297          rendered += pre + string[index] + post + string.substring(index + 1, indices[i + 1] ? indices[i + 1] : string.length);
     1298        });
     1299        return rendered;
     1300      }
     1301    }, {
     1302      key: "filter",
     1303      value: function filter(pattern, arr, opts) {
     1304        var _this2 = this;
     1305
     1306        opts = opts || {};
     1307        return arr.reduce(function (prev, element, idx, arr) {
     1308          var str = element;
     1309
     1310          if (opts.extract) {
     1311            str = opts.extract(element);
     1312
     1313            if (!str) {
     1314              // take care of undefineds / nulls / etc.
     1315              str = '';
     1316            }
     1317          }
     1318
     1319          var rendered = _this2.match(pattern, str, opts);
     1320
     1321          if (rendered != null) {
     1322            prev[prev.length] = {
     1323              string: rendered.rendered,
     1324              score: rendered.score,
     1325              index: idx,
     1326              original: element
     1327            };
     1328          }
     1329
     1330          return prev;
     1331        }, []).sort(function (a, b) {
     1332          var compare = b.score - a.score;
     1333          if (compare) return compare;
     1334          return a.index - b.index;
     1335        });
     1336      }
     1337    }]);
     1338
     1339    return TributeSearch;
     1340  }();
     1341
     1342  var Tribute = /*#__PURE__*/function () {
     1343    function Tribute(_ref) {
     1344      var _this = this;
     1345
     1346      var _ref$values = _ref.values,
     1347          values = _ref$values === void 0 ? null : _ref$values,
     1348          _ref$loadingItemTempl = _ref.loadingItemTemplate,
     1349          loadingItemTemplate = _ref$loadingItemTempl === void 0 ? null : _ref$loadingItemTempl,
     1350          _ref$iframe = _ref.iframe,
     1351          iframe = _ref$iframe === void 0 ? null : _ref$iframe,
     1352          _ref$selectClass = _ref.selectClass,
     1353          selectClass = _ref$selectClass === void 0 ? "highlight" : _ref$selectClass,
     1354          _ref$containerClass = _ref.containerClass,
     1355          containerClass = _ref$containerClass === void 0 ? "tribute-container" : _ref$containerClass,
     1356          _ref$itemClass = _ref.itemClass,
     1357          itemClass = _ref$itemClass === void 0 ? "" : _ref$itemClass,
     1358          _ref$trigger = _ref.trigger,
     1359          trigger = _ref$trigger === void 0 ? "@" : _ref$trigger,
     1360          _ref$autocompleteMode = _ref.autocompleteMode,
     1361          autocompleteMode = _ref$autocompleteMode === void 0 ? false : _ref$autocompleteMode,
     1362          _ref$autocompleteSepa = _ref.autocompleteSeparator,
     1363          autocompleteSeparator = _ref$autocompleteSepa === void 0 ? null : _ref$autocompleteSepa,
     1364          _ref$selectTemplate = _ref.selectTemplate,
     1365          selectTemplate = _ref$selectTemplate === void 0 ? null : _ref$selectTemplate,
     1366          _ref$menuItemTemplate = _ref.menuItemTemplate,
     1367          menuItemTemplate = _ref$menuItemTemplate === void 0 ? null : _ref$menuItemTemplate,
     1368          _ref$lookup = _ref.lookup,
     1369          lookup = _ref$lookup === void 0 ? "key" : _ref$lookup,
     1370          _ref$fillAttr = _ref.fillAttr,
     1371          fillAttr = _ref$fillAttr === void 0 ? "value" : _ref$fillAttr,
     1372          _ref$collection = _ref.collection,
     1373          collection = _ref$collection === void 0 ? null : _ref$collection,
     1374          _ref$menuContainer = _ref.menuContainer,
     1375          menuContainer = _ref$menuContainer === void 0 ? null : _ref$menuContainer,
     1376          _ref$noMatchTemplate = _ref.noMatchTemplate,
     1377          noMatchTemplate = _ref$noMatchTemplate === void 0 ? null : _ref$noMatchTemplate,
     1378          _ref$requireLeadingSp = _ref.requireLeadingSpace,
     1379          requireLeadingSpace = _ref$requireLeadingSp === void 0 ? true : _ref$requireLeadingSp,
     1380          _ref$allowSpaces = _ref.allowSpaces,
     1381          allowSpaces = _ref$allowSpaces === void 0 ? false : _ref$allowSpaces,
     1382          _ref$replaceTextSuffi = _ref.replaceTextSuffix,
     1383          replaceTextSuffix = _ref$replaceTextSuffi === void 0 ? null : _ref$replaceTextSuffi,
     1384          _ref$positionMenu = _ref.positionMenu,
     1385          positionMenu = _ref$positionMenu === void 0 ? true : _ref$positionMenu,
     1386          _ref$spaceSelectsMatc = _ref.spaceSelectsMatch,
     1387          spaceSelectsMatch = _ref$spaceSelectsMatc === void 0 ? false : _ref$spaceSelectsMatc,
     1388          _ref$searchOpts = _ref.searchOpts,
     1389          searchOpts = _ref$searchOpts === void 0 ? {} : _ref$searchOpts,
     1390          _ref$menuItemLimit = _ref.menuItemLimit,
     1391          menuItemLimit = _ref$menuItemLimit === void 0 ? null : _ref$menuItemLimit,
     1392          _ref$menuShowMinLengt = _ref.menuShowMinLength,
     1393          menuShowMinLength = _ref$menuShowMinLengt === void 0 ? 0 : _ref$menuShowMinLengt;
     1394
     1395      _classCallCheck(this, Tribute);
     1396
     1397      this.autocompleteMode = autocompleteMode;
     1398      this.autocompleteSeparator = autocompleteSeparator;
     1399      this.menuSelected = 0;
     1400      this.current = {};
     1401      this.inputEvent = false;
     1402      this.isActive = false;
     1403      this.menuContainer = menuContainer;
     1404      this.allowSpaces = allowSpaces;
     1405      this.replaceTextSuffix = replaceTextSuffix;
     1406      this.positionMenu = positionMenu;
     1407      this.hasTrailingSpace = false;
     1408      this.spaceSelectsMatch = spaceSelectsMatch;
     1409
     1410      if (this.autocompleteMode) {
     1411        trigger = "";
     1412        allowSpaces = false;
     1413      }
     1414
     1415      if (values) {
     1416        this.collection = [{
     1417          // symbol that starts the lookup
     1418          trigger: trigger,
     1419          // is it wrapped in an iframe
     1420          iframe: iframe,
     1421          // class applied to selected item
     1422          selectClass: selectClass,
     1423          // class applied to the Container
     1424          containerClass: containerClass,
     1425          // class applied to each item
     1426          itemClass: itemClass,
     1427          // function called on select that retuns the content to insert
     1428          selectTemplate: (selectTemplate || Tribute.defaultSelectTemplate).bind(this),
     1429          // function called that returns content for an item
     1430          menuItemTemplate: (menuItemTemplate || Tribute.defaultMenuItemTemplate).bind(this),
     1431          // function called when menu is empty, disables hiding of menu.
     1432          noMatchTemplate: function (t) {
     1433            if (typeof t === "string") {
     1434              if (t.trim() === "") return null;
     1435              return t;
     1436            }
     1437
     1438            if (typeof t === "function") {
     1439              return t.bind(_this);
     1440            }
     1441
     1442            return noMatchTemplate || function () {
     1443              return "<li>No Match Found!</li>";
     1444            }.bind(_this);
     1445          }(noMatchTemplate),
     1446          // column to search against in the object
     1447          lookup: lookup,
     1448          // column that contains the content to insert by default
     1449          fillAttr: fillAttr,
     1450          // array of objects or a function returning an array of objects
     1451          values: values,
     1452          // useful for when values is an async function
     1453          loadingItemTemplate: loadingItemTemplate,
     1454          requireLeadingSpace: requireLeadingSpace,
     1455          searchOpts: searchOpts,
     1456          menuItemLimit: menuItemLimit,
     1457          menuShowMinLength: menuShowMinLength
     1458        }];
     1459      } else if (collection) {
     1460        if (this.autocompleteMode) console.warn("Tribute in autocomplete mode does not work for collections");
     1461        this.collection = collection.map(function (item) {
     1462          return {
     1463            trigger: item.trigger || trigger,
     1464            iframe: item.iframe || iframe,
     1465            selectClass: item.selectClass || selectClass,
     1466            containerClass: item.containerClass || containerClass,
     1467            itemClass: item.itemClass || itemClass,
     1468            selectTemplate: (item.selectTemplate || Tribute.defaultSelectTemplate).bind(_this),
     1469            menuItemTemplate: (item.menuItemTemplate || Tribute.defaultMenuItemTemplate).bind(_this),
     1470            // function called when menu is empty, disables hiding of menu.
     1471            noMatchTemplate: function (t) {
     1472              if (typeof t === "string") {
     1473                if (t.trim() === "") return null;
     1474                return t;
     1475              }
     1476
     1477              if (typeof t === "function") {
     1478                return t.bind(_this);
     1479              }
     1480
     1481              return noMatchTemplate || function () {
     1482                return "<li>No Match Found!</li>";
     1483              }.bind(_this);
     1484            }(noMatchTemplate),
     1485            lookup: item.lookup || lookup,
     1486            fillAttr: item.fillAttr || fillAttr,
     1487            values: item.values,
     1488            loadingItemTemplate: item.loadingItemTemplate,
     1489            requireLeadingSpace: item.requireLeadingSpace,
     1490            searchOpts: item.searchOpts || searchOpts,
     1491            menuItemLimit: item.menuItemLimit || menuItemLimit,
     1492            menuShowMinLength: item.menuShowMinLength || menuShowMinLength
     1493          };
     1494        });
     1495      } else {
     1496        throw new Error("[Tribute] No collection specified.");
     1497      }
     1498
     1499      new TributeRange(this);
     1500      new TributeEvents(this);
     1501      new TributeMenuEvents(this);
     1502      new TributeSearch(this);
     1503    }
     1504
     1505    _createClass(Tribute, [{
     1506      key: "triggers",
     1507      value: function triggers() {
     1508        return this.collection.map(function (config) {
     1509          return config.trigger;
     1510        });
     1511      }
     1512    }, {
     1513      key: "attach",
     1514      value: function attach(el) {
     1515        if (!el) {
     1516          throw new Error("[Tribute] Must pass in a DOM node or NodeList.");
     1517        } // Check if it is a jQuery collection
     1518
     1519
     1520        if (typeof jQuery !== "undefined" && el instanceof jQuery) {
     1521          el = el.get();
     1522        } // Is el an Array/Array-like object?
     1523
     1524
     1525        if (el.constructor === NodeList || el.constructor === HTMLCollection || el.constructor === Array) {
     1526          var length = el.length;
     1527
     1528          for (var i = 0; i < length; ++i) {
     1529            this._attach(el[i]);
     1530          }
     1531        } else {
     1532          this._attach(el);
     1533        }
     1534      }
     1535    }, {
     1536      key: "_attach",
     1537      value: function _attach(el) {
     1538        if (el.hasAttribute("data-tribute")) {
     1539          console.warn("Tribute was already bound to " + el.nodeName);
     1540        }
     1541
     1542        this.ensureEditable(el);
     1543        this.events.bind(el);
     1544        el.setAttribute("data-tribute", true);
     1545      }
     1546    }, {
     1547      key: "ensureEditable",
     1548      value: function ensureEditable(element) {
     1549        if (Tribute.inputTypes().indexOf(element.nodeName) === -1) {
     1550          if (element.contentEditable) {
     1551            element.contentEditable = true;
     1552          } else {
     1553            throw new Error("[Tribute] Cannot bind to " + element.nodeName);
     1554          }
     1555        }
     1556      }
     1557    }, {
     1558      key: "createMenu",
     1559      value: function createMenu(containerClass) {
     1560        var wrapper = this.range.getDocument().createElement("div"),
     1561            ul = this.range.getDocument().createElement("ul");
     1562        wrapper.className = containerClass;
     1563        wrapper.appendChild(ul);
     1564
     1565        if (this.menuContainer) {
     1566          return this.menuContainer.appendChild(wrapper);
     1567        }
     1568
     1569        return this.range.getDocument().body.appendChild(wrapper);
     1570      }
     1571    }, {
     1572      key: "showMenuFor",
     1573      value: function showMenuFor(element, scrollTo) {
     1574        var _this2 = this;
     1575
     1576        // Only proceed if menu isn't already shown for the current element & mentionText
     1577        if (this.isActive && this.current.element === element && this.current.mentionText === this.currentMentionTextSnapshot) {
     1578          return;
     1579        }
     1580
     1581        this.currentMentionTextSnapshot = this.current.mentionText; // create the menu if it doesn't exist.
     1582
     1583        if (!this.menu) {
     1584          this.menu = this.createMenu(this.current.collection.containerClass);
     1585          element.tributeMenu = this.menu;
     1586          this.menuEvents.bind(this.menu);
     1587        }
     1588
     1589        this.isActive = true;
     1590        this.menuSelected = 0;
     1591
     1592        if (!this.current.mentionText) {
     1593          this.current.mentionText = "";
     1594        }
     1595
     1596        var processValues = function processValues(values) {
     1597          // Tribute may not be active any more by the time the value callback returns
     1598          if (!_this2.isActive) {
     1599            return;
     1600          }
     1601
     1602          var items = _this2.search.filter(_this2.current.mentionText, values, {
     1603            pre: _this2.current.collection.searchOpts.pre || "<span>",
     1604            post: _this2.current.collection.searchOpts.post || "</span>",
     1605            skip: _this2.current.collection.searchOpts.skip,
     1606            extract: function extract(el) {
     1607              if (typeof _this2.current.collection.lookup === "string") {
     1608                return el[_this2.current.collection.lookup];
     1609              } else if (typeof _this2.current.collection.lookup === "function") {
     1610                return _this2.current.collection.lookup(el, _this2.current.mentionText);
     1611              } else {
     1612                throw new Error("Invalid lookup attribute, lookup must be string or function.");
     1613              }
     1614            }
     1615          });
     1616
     1617          if (_this2.current.collection.menuItemLimit) {
     1618            items = items.slice(0, _this2.current.collection.menuItemLimit);
     1619          }
     1620
     1621          _this2.current.filteredItems = items;
     1622
     1623          var ul = _this2.menu.querySelector("ul");
     1624
     1625          _this2.range.positionMenuAtCaret(scrollTo);
     1626
     1627          if (!items.length) {
     1628            var noMatchEvent = new CustomEvent("tribute-no-match", {
     1629              detail: _this2.menu
     1630            });
     1631
     1632            _this2.current.element.dispatchEvent(noMatchEvent);
     1633
     1634            if (typeof _this2.current.collection.noMatchTemplate === "function" && !_this2.current.collection.noMatchTemplate() || !_this2.current.collection.noMatchTemplate) {
     1635              _this2.hideMenu();
     1636            } else {
     1637              typeof _this2.current.collection.noMatchTemplate === "function" ? ul.innerHTML = _this2.current.collection.noMatchTemplate() : ul.innerHTML = _this2.current.collection.noMatchTemplate;
     1638            }
     1639
     1640            return;
     1641          }
     1642
     1643          ul.innerHTML = "";
     1644
     1645          var fragment = _this2.range.getDocument().createDocumentFragment();
     1646
     1647          items.forEach(function (item, index) {
     1648            var li = _this2.range.getDocument().createElement("li");
     1649
     1650            li.setAttribute("data-index", index);
     1651            li.className = _this2.current.collection.itemClass;
     1652            li.addEventListener("mousemove", function (e) {
     1653              var _this2$_findLiTarget = _this2._findLiTarget(e.target),
     1654                  _this2$_findLiTarget2 = _slicedToArray(_this2$_findLiTarget, 2),
     1655                  li = _this2$_findLiTarget2[0],
     1656                  index = _this2$_findLiTarget2[1];
     1657
     1658              if (e.movementY !== 0) {
     1659                _this2.events.setActiveLi(index);
     1660              }
     1661            });
     1662
     1663            if (_this2.menuSelected === index) {
     1664              li.classList.add(_this2.current.collection.selectClass);
     1665            }
     1666
     1667            li.innerHTML = _this2.current.collection.menuItemTemplate(item);
     1668            fragment.appendChild(li);
     1669          });
     1670          ul.appendChild(fragment);
     1671        };
     1672
     1673        if (typeof this.current.collection.values === "function") {
     1674          if (this.current.collection.loadingItemTemplate) {
     1675            this.menu.querySelector("ul").innerHTML = this.current.collection.loadingItemTemplate;
     1676            this.range.positionMenuAtCaret(scrollTo);
     1677          }
     1678
     1679          this.current.collection.values(this.current.mentionText, processValues);
     1680        } else {
     1681          processValues(this.current.collection.values);
     1682        }
     1683      }
     1684    }, {
     1685      key: "_findLiTarget",
     1686      value: function _findLiTarget(el) {
     1687        if (!el) return [];
     1688        var index = el.getAttribute("data-index");
     1689        return !index ? this._findLiTarget(el.parentNode) : [el, index];
     1690      }
     1691    }, {
     1692      key: "showMenuForCollection",
     1693      value: function showMenuForCollection(element, collectionIndex) {
     1694        if (element !== document.activeElement) {
     1695          this.placeCaretAtEnd(element);
     1696        }
     1697
     1698        this.current.collection = this.collection[collectionIndex || 0];
     1699        this.current.externalTrigger = true;
     1700        this.current.element = element;
     1701        if (element.isContentEditable) this.insertTextAtCursor(this.current.collection.trigger);else this.insertAtCaret(element, this.current.collection.trigger);
     1702        this.showMenuFor(element);
     1703      } // TODO: make sure this works for inputs/textareas
     1704
     1705    }, {
     1706      key: "placeCaretAtEnd",
     1707      value: function placeCaretAtEnd(el) {
     1708        el.focus();
     1709
     1710        if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
     1711          var range = document.createRange();
     1712          range.selectNodeContents(el);
     1713          range.collapse(false);
     1714          var sel = window.getSelection();
     1715          sel.removeAllRanges();
     1716          sel.addRange(range);
     1717        } else if (typeof document.body.createTextRange != "undefined") {
     1718          var textRange = document.body.createTextRange();
     1719          textRange.moveToElementText(el);
     1720          textRange.collapse(false);
     1721          textRange.select();
     1722        }
     1723      } // for contenteditable
     1724
     1725    }, {
     1726      key: "insertTextAtCursor",
     1727      value: function insertTextAtCursor(text) {
     1728        var sel, range;
     1729        sel = window.getSelection();
     1730        range = sel.getRangeAt(0);
     1731        range.deleteContents();
     1732        var textNode = document.createTextNode(text);
     1733        range.insertNode(textNode);
     1734        range.selectNodeContents(textNode);
     1735        range.collapse(false);
     1736        sel.removeAllRanges();
     1737        sel.addRange(range);
     1738      } // for regular inputs
     1739
     1740    }, {
     1741      key: "insertAtCaret",
     1742      value: function insertAtCaret(textarea, text) {
     1743        var scrollPos = textarea.scrollTop;
     1744        var caretPos = textarea.selectionStart;
     1745        var front = textarea.value.substring(0, caretPos);
     1746        var back = textarea.value.substring(textarea.selectionEnd, textarea.value.length);
     1747        textarea.value = front + text + back;
     1748        caretPos = caretPos + text.length;
     1749        textarea.selectionStart = caretPos;
     1750        textarea.selectionEnd = caretPos;
     1751        textarea.focus();
     1752        textarea.scrollTop = scrollPos;
     1753      }
     1754    }, {
     1755      key: "hideMenu",
     1756      value: function hideMenu() {
     1757        if (this.menu) {
     1758          this.menu.style.cssText = "display: none;";
     1759          this.isActive = false;
     1760          this.menuSelected = 0;
     1761          this.current = {};
     1762        }
     1763      }
     1764    }, {
     1765      key: "selectItemAtIndex",
     1766      value: function selectItemAtIndex(index, originalEvent) {
     1767        index = parseInt(index);
     1768        if (typeof index !== "number" || isNaN(index)) return;
     1769        var item = this.current.filteredItems[index];
     1770        var content = this.current.collection.selectTemplate(item);
     1771        if (content !== null) this.replaceText(content, originalEvent, item);
     1772      }
     1773    }, {
     1774      key: "replaceText",
     1775      value: function replaceText(content, originalEvent, item) {
     1776        this.range.replaceTriggerText(content, true, true, originalEvent, item);
     1777      }
     1778    }, {
     1779      key: "_append",
     1780      value: function _append(collection, newValues, replace) {
     1781        if (typeof collection.values === "function") {
     1782          throw new Error("Unable to append to values, as it is a function.");
     1783        } else if (!replace) {
     1784          collection.values = collection.values.concat(newValues);
     1785        } else {
     1786          collection.values = newValues;
     1787        }
     1788      }
     1789    }, {
     1790      key: "append",
     1791      value: function append(collectionIndex, newValues, replace) {
     1792        var index = parseInt(collectionIndex);
     1793        if (typeof index !== "number") throw new Error("please provide an index for the collection to update.");
     1794        var collection = this.collection[index];
     1795
     1796        this._append(collection, newValues, replace);
     1797      }
     1798    }, {
     1799      key: "appendCurrent",
     1800      value: function appendCurrent(newValues, replace) {
     1801        if (this.isActive) {
     1802          this._append(this.current.collection, newValues, replace);
     1803        } else {
     1804          throw new Error("No active state. Please use append instead and pass an index.");
     1805        }
     1806      }
     1807    }, {
     1808      key: "detach",
     1809      value: function detach(el) {
     1810        if (!el) {
     1811          throw new Error("[Tribute] Must pass in a DOM node or NodeList.");
     1812        } // Check if it is a jQuery collection
     1813
     1814
     1815        if (typeof jQuery !== "undefined" && el instanceof jQuery) {
     1816          el = el.get();
     1817        } // Is el an Array/Array-like object?
     1818
     1819
     1820        if (el.constructor === NodeList || el.constructor === HTMLCollection || el.constructor === Array) {
     1821          var length = el.length;
     1822
     1823          for (var i = 0; i < length; ++i) {
     1824            this._detach(el[i]);
     1825          }
     1826        } else {
     1827          this._detach(el);
     1828        }
     1829      }
     1830    }, {
     1831      key: "_detach",
     1832      value: function _detach(el) {
     1833        var _this3 = this;
     1834
     1835        this.events.unbind(el);
     1836
     1837        if (el.tributeMenu) {
     1838          this.menuEvents.unbind(el.tributeMenu);
     1839        }
     1840
     1841        setTimeout(function () {
     1842          el.removeAttribute("data-tribute");
     1843          _this3.isActive = false;
     1844
     1845          if (el.tributeMenu) {
     1846            el.tributeMenu.remove();
     1847          }
     1848        });
     1849      }
     1850    }, {
     1851      key: "isActive",
     1852      get: function get() {
     1853        return this._isActive;
     1854      },
     1855      set: function set(val) {
     1856        if (this._isActive != val) {
     1857          this._isActive = val;
     1858
     1859          if (this.current.element) {
     1860            var noMatchEvent = new CustomEvent("tribute-active-".concat(val));
     1861            this.current.element.dispatchEvent(noMatchEvent);
     1862          }
     1863        }
     1864      }
     1865    }], [{
     1866      key: "defaultSelectTemplate",
     1867      value: function defaultSelectTemplate(item) {
     1868        if (typeof item === "undefined") return "".concat(this.current.collection.trigger).concat(this.current.mentionText);
     1869
     1870        if (this.range.isContentEditable(this.current.element)) {
     1871          return '<span class="tribute-mention">' + (this.current.collection.trigger + item.original[this.current.collection.fillAttr]) + "</span>";
     1872        }
     1873
     1874        return this.current.collection.trigger + item.original[this.current.collection.fillAttr];
     1875      }
     1876    }, {
     1877      key: "defaultMenuItemTemplate",
     1878      value: function defaultMenuItemTemplate(matchItem) {
     1879        return matchItem.string;
     1880      }
     1881    }, {
     1882      key: "inputTypes",
     1883      value: function inputTypes() {
     1884        return ["TEXTAREA", "INPUT"];
     1885      }
     1886    }]);
     1887
     1888    return Tribute;
     1889  }();
     1890
     1891  /**
     1892   * Tribute.js
     1893   * Native ES6 JavaScript @mention Plugin
     1894   **/
     1895
     1896  return Tribute;
     1897
     1898})));
  • src/bp-friends/bp-friends-functions.php

    diff --git a/src/bp-friends/bp-friends-functions.php b/src/bp-friends/bp-friends-functions.php
    index a2a906b5a..8aae0eac6 100644
    a b function bp_friends_prime_mentions_results() { 
    819819                $result        = new stdClass();
    820820                $result->ID    = $user->user_nicename;
    821821                $result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
     822                $result->user_id = $user->ID;
    822823
    823824                if ( ! empty( $user->display_name ) && ! bp_disable_profile_sync() ) {
    824825                        $result->name = $user->display_name;
    825826                } else {
    826827                        $result->name = bp_core_get_user_displayname( $user->ID );
    827828                }
     829                $result->search = "{$result->ID} {$result->name}";
    828830
    829831                $results[] = $result;
    830832        }
  • src/bp-templates/bp-nouveau/includes/messages/functions.php

    diff --git a/src/bp-templates/bp-nouveau/includes/messages/functions.php b/src/bp-templates/bp-nouveau/includes/messages/functions.php
    index c10e9280a..2774185ae 100644
    a b function bp_nouveau_messages_register_scripts( $scripts = array() ) { 
    4949        return array_merge( $scripts, array(
    5050                'bp-nouveau-messages-at' => array(
    5151                        'file'         => buddypress()->plugin_url . 'bp-activity/js/mentions%s.js',
    52                         'dependencies' => array( 'bp-nouveau', 'jquery', 'jquery-atwho' ),
     52                        'dependencies' => array( 'bp-nouveau', 'jquery', 'bp-api-request', 'zurb-tribute' ),
    5353                        'version'      => bp_get_version(),
    5454                        'footer'       => true,
    5555                ),
    function bp_nouveau_messages_enqueue_scripts() { 
    7272        }
    7373
    7474        wp_enqueue_script( 'bp-nouveau-messages' );
    75 
    76         // Add The tiny MCE init specific function.
    77         add_filter( 'tiny_mce_before_init', 'bp_nouveau_messages_at_on_tinymce_init', 10, 2 );
    7875}
    7976
    8077/**
    function bp_nouveau_messages_mce_buttons( $buttons = array() ) { 
    314311        return $buttons;
    315312}
    316313
    317 /**
    318  * @since 3.0.0
    319  */
    320 function bp_nouveau_messages_at_on_tinymce_init( $settings, $editor_id ) {
    321         // We only apply the mentions init to the visual post editor in the WP dashboard.
    322         if ( 'message_content' === $editor_id ) {
    323                 $settings['init_instance_callback'] = 'window.bp.Nouveau.Messages.tinyMCEinit';
    324         }
    325 
    326         return $settings;
    327 }
    328 
    329314/**
    330315 * @since 3.0.0
    331316 */
  • src/bp-templates/bp-nouveau/js/buddypress-messages.js

    diff --git a/src/bp-templates/bp-nouveau/js/buddypress-messages.js b/src/bp-templates/bp-nouveau/js/buddypress-messages.js
    index 44732ceaa..cfd0ee9af 100644
    a b window.bp = window.bp || {}; 
    101101                        }
    102102                },
    103103
    104                 tinyMCEinit: function() {
    105                         if ( typeof window.tinyMCE === 'undefined' || window.tinyMCE.activeEditor === null || typeof window.tinyMCE.activeEditor === 'undefined' ) {
    106                                 return;
    107                         } else {
    108                                 // Mentions isn't available, so bail.
    109                                 if ( _.isEmpty( exports.mentions ) ) {
    110                                         return;
    111                                 }
    112 
    113                                 $( window.tinyMCE.activeEditor.contentDocument.activeElement )
    114                                         .atwho( 'setIframe', $( '#message_content_ifr' )[0] )
    115                                         .bp_mentions( {
    116                                                 data: [],
    117                                                 suffix: ' '
    118                                         } );
    119                         }
    120                 },
    121 
    122104                removeFeedback: function() {
    123105                        var feedback;
    124106