Ticket #3278: 3278.03.patch
File 3278.03.patch, 61.6 KB (added by , 11 years ago) |
---|
-
Gruntfile.js
diff --git Gruntfile.js Gruntfile.js index ea6b69b..bb49281 100644
module.exports = function( grunt ) { 6 6 BUILD_DIR = 'build/', 7 7 8 8 BP_CSS = [ 9 'bp-activity/css/*.css', 9 10 'bp-activity/admin/css/*.css', 10 11 'bp-core/admin/css/*.css', 11 12 'bp-core/css/*.css', … … module.exports = function( grunt ) { 18 19 ], 19 20 20 21 BP_JS = [ 22 'bp-activity/js/*.js', 21 23 'bp-activity/admin/js/*.js', 22 24 'bp-core/js/*.js', 23 25 'bp-friends/js/*.js', … … module.exports = function( grunt ) { 30 32 ], 31 33 32 34 BP_EXCLUDED_JS = [ 35 '!bp-core/js/jquery.atwho.js', 36 '!bp-core/js/jquery.caret.js', 33 37 '!bp-templates/bp-legacy/js/*.js' 34 38 ]; 35 39 -
src/bp-activity/bp-activity-actions.php
diff --git src/bp-activity/bp-activity-actions.php src/bp-activity/bp-activity-actions.php index d8e4c23..640a585 100644
function bp_activity_setup_akismet() { 647 647 // Instantiate Akismet for BuddyPress 648 648 $bp->activity->akismet = new BP_Akismet(); 649 649 } 650 651 /** 652 * AJAX endpoint for Suggestions API lookups. 653 * 654 * @since BuddyPress (2.1.0) 655 */ 656 function bp_ajax_get_suggestions() { 657 if ( ! bp_is_user_active() || empty( $_GET['term'] ) || empty( $_GET['type'] ) ) { 658 wp_send_json_error( 'missing_parameter' ); 659 exit; 660 } 661 662 $results = bp_core_get_suggestions( array( 663 'term' => sanitize_text_field( $_GET['term'] ), 664 'type' => sanitize_text_field( $_GET['type'] ), 665 ) ); 666 667 if ( is_wp_error( $results ) ) { 668 wp_send_json_error( $results->get_error_message() ); 669 exit; 670 } 671 672 wp_send_json_success( $results ); 673 } 674 add_action( 'wp_ajax_bp_get_suggestions', 'bp_ajax_get_suggestions' ); -
new file src/bp-activity/bp-activity-cssjs.php
diff --git src/bp-activity/bp-activity-cssjs.php src/bp-activity/bp-activity-cssjs.php new file mode 100644 index 0000000..9a7735c
- + 1 <?php 2 3 /** 4 * Activity component CSS/JS 5 * 6 * @package BuddyPress 7 * @subpackage ActivityScripts 8 */ 9 10 // Exit if accessed directly 11 if ( ! defined( 'ABSPATH' ) ) exit; 12 13 /** 14 * Enqueue @mentions JS. 15 * 16 * @since BuddyPress (2.1) 17 */ 18 function bp_activity_mentions_script() { 19 if ( ! bp_activity_maybe_load_mentions_scripts() ) { 20 return; 21 } 22 23 $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; 24 $file = is_rtl() ? "mentions-rtl{$min}.css" : "mentions{$min}.css"; 25 26 wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'jquery', 'jquery-atwho' ), bp_get_version(), true ); 27 wp_enqueue_style( 'bp-mentions-css', buddypress()->plugin_url . "bp-activity/css/{$file}", array(), bp_get_version() ); 28 29 // Print a list of the current user's friends to the page for quicker @mentions lookups. 30 do_action( 'bp_activity_mentions_prime_results' ); 31 } 32 add_action( 'bp_enqueue_scripts', 'bp_activity_mentions_script' ); 33 34 /** 35 * Enqueue @mentions JS in wp-admin. 36 * 37 * @since BuddyPress (2.1) 38 */ 39 function bp_activity_mentions_dashboard_script() { 40 if ( ! bp_activity_maybe_load_mentions_scripts() ) { 41 return; 42 } 43 44 // Special handling for New/Edit screens in wp-admin 45 if ( 46 ! get_current_screen() || 47 ! in_array( get_current_screen()->base, array( 'page', 'post' ) ) || 48 ! post_type_supports( get_current_screen()->post_type, 'editor' ) ) { 49 return; 50 } 51 52 $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; 53 $file = is_rtl() ? "mentions-rtl{$min}.css" : "mentions{$min}.css"; 54 55 wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'jquery', 'jquery-atwho' ), bp_get_version(), true ); 56 wp_enqueue_style( 'bp-mentions-css', buddypress()->plugin_url . "bp-activity/css/{$file}", array(), bp_get_version() ); 57 58 // Print a list of the current user's friends to the page for quicker @mentions lookups. 59 do_action( 'bp_activity_mentions_prime_results' ); 60 } 61 add_action( 'bp_admin_enqueue_scripts', 'bp_activity_mentions_dashboard_script' ); 62 No newline at end of file -
src/bp-activity/bp-activity-functions.php
diff --git src/bp-activity/bp-activity-functions.php src/bp-activity/bp-activity-functions.php index e77d8dc..5f70994 100644
function bp_activity_do_mentions() { 49 49 } 50 50 51 51 /** 52 * Should BuddyPress load the mentions scripts and related assets, including results to prime the 53 * mentions suggestions? 54 * 55 * @return bool True if mentions scripts should be loaded. 56 * @since BuddyPress (2.1.0) 57 */ 58 function bp_activity_maybe_load_mentions_scripts() { 59 $retval = 60 bp_activity_do_mentions() && 61 bp_is_user_active() && 62 ( bp_is_activity_component() || bp_is_blog_page() && is_singular() && comments_open() || is_admin() ); 63 64 return (bool) apply_filters( 'bp_activity_maybe_load_mentions_scripts', $retval ); 65 } 66 67 /** 52 68 * Locate usernames in an activity content string, as designated by an @ sign. 53 69 * 54 70 * @since BuddyPress (1.5.0) -
src/bp-activity/bp-activity-loader.php
diff --git src/bp-activity/bp-activity-loader.php src/bp-activity/bp-activity-loader.php index b0c556c..e3a8dad 100644
class BP_Activity_Component extends BP_Component { 47 47 public function includes( $includes = array() ) { 48 48 // Files to include 49 49 $includes = array( 50 'cssjs', 50 51 'actions', 51 52 'screens', 52 53 'filters', … … class BP_Activity_Component extends BP_Component { 367 368 function bp_setup_activity() { 368 369 buddypress()->activity = new BP_Activity_Component(); 369 370 } 370 add_action( 'bp_setup_components', 'bp_setup_activity', 6 ); 371 No newline at end of file 371 add_action( 'bp_setup_components', 'bp_setup_activity', 6 ); -
new file src/bp-activity/css/mentions-rtl.css
diff --git src/bp-activity/css/mentions-rtl.css src/bp-activity/css/mentions-rtl.css new file mode 100644 index 0000000..7185ed6
- + 1 .atwho-view { 2 background: #FFF; 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 right: 0; 10 margin-top: 18px; 11 position: absolute; 12 top: 0; 13 z-index: 1000; /* >999 for wp-admin */ 14 } 15 .atwho-view ul { 16 list-style: none; 17 margin: auto; 18 padding: 0; 19 } 20 .atwho-view ul li { 21 border-bottom: 1px solid #EFEFEF; 22 box-sizing: content-box; 23 cursor: pointer; 24 display: block; 25 font-size: 14px; 26 height: 20px; 27 line-height: 20px; 28 margin: 0; 29 overflow: hidden; 30 padding: 5px 10px; 31 } 32 .atwho-view img { 33 border-radius: 2px; 34 float: left; 35 height: 20px; 36 margin-right: 10px; 37 width: 20px; 38 } 39 .atwho-view strong { 40 background: #EFEFEF; 41 font: bold; 42 } 43 .atwho-view .username strong { 44 color: #D54E21; 45 } 46 .atwho-view small { 47 color: #AAA; 48 float: left; 49 font-size: smaller; 50 font-weight: normal; 51 margin-right: 40px; 52 } 53 .atwho-view .cur { 54 background: rgba(239, 239, 239, 0.5); 55 } 56 57 @media (max-width: 900px) { 58 .atwho-view img { 59 float: right; 60 margin-right: 0; 61 margin-left: 10px; 62 } 63 .atwho-view small { 64 display: none; 65 } 66 } 67 @media (max-width: 400px) { 68 .atwho-view ul li { 69 font-size: 16px; 70 line-height: 23px; 71 padding: 13px; 72 } 73 .atwho-view ul li img { 74 height: 30px; 75 margin-top: -5px; 76 width: 30px; 77 } 78 } 79 No newline at end of file -
new file src/bp-activity/css/mentions.css
diff --git src/bp-activity/css/mentions.css src/bp-activity/css/mentions.css new file mode 100644 index 0000000..9bc5134
- + 1 .atwho-view { 2 background: #FFF; 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 left: 0; 10 margin-top: 18px; 11 position: absolute; 12 top: 0; 13 z-index: 1000; /* >999 for wp-admin */ 14 } 15 .atwho-view ul { 16 list-style: none; 17 margin: auto; 18 padding: 0; 19 } 20 .atwho-view ul li { 21 border-bottom: 1px solid #EFEFEF; 22 box-sizing: content-box; 23 cursor: pointer; 24 display: block; 25 font-size: 14px; 26 height: 20px; 27 line-height: 20px; 28 margin: 0; 29 overflow: hidden; 30 padding: 5px 10px; 31 } 32 .atwho-view img { 33 border-radius: 2px; 34 float: right; 35 height: 20px; 36 margin-left: 10px; 37 width: 20px; 38 } 39 .atwho-view strong { 40 background: #EFEFEF; 41 font: bold; 42 } 43 .atwho-view .username strong { 44 color: #D54E21; 45 } 46 .atwho-view small { 47 color: #AAA; 48 float: right; 49 font-size: smaller; 50 font-weight: normal; 51 margin-left: 40px; 52 } 53 .atwho-view .cur { 54 background: rgba(239, 239, 239, 0.5); 55 } 56 57 @media (max-width: 900px) { 58 .atwho-view img { 59 float: left; 60 margin-left: 0; 61 margin-right: 10px; 62 } 63 .atwho-view small { 64 display: none; 65 } 66 } 67 @media (max-width: 400px) { 68 .atwho-view ul li { 69 font-size: 16px; 70 line-height: 23px; 71 padding: 13px; 72 } 73 .atwho-view ul li img { 74 height: 30px; 75 margin-top: -5px; 76 width: 30px; 77 } 78 } 79 No newline at end of file -
new file src/bp-activity/js/mentions.js
diff --git src/bp-activity/js/mentions.js src/bp-activity/js/mentions.js new file mode 100644 index 0000000..ae36039
- + 1 (function( $, undefined ) { 2 var mentionsQueryCache = [], 3 mentionsItem; 4 5 /** 6 * Adds BuddyPress @mentions to form inputs. 7 * 8 * @param {array|object} options If array, becomes the suggestions' data source. If object, passed as config to $.atwho(). 9 * @since BuddyPress (2.1.0) 10 */ 11 $.fn.bp_mentions = function( options ) { 12 if ( $.isArray( options ) ) { 13 options = { data: options }; 14 } 15 16 /** 17 * Default options for at.js; see https://github.com/ichord/At.js/. 18 */ 19 var suggestionsDefaults = { 20 delay: 200, 21 hide_without_suffix: true, 22 insert_tpl: '</>${atwho-data-value}</>', // For contentEditable, the fake tags make jQuery insert a textNode. 23 limit: 10, 24 start_with_space: false, 25 suffix: '', 26 27 callbacks: { 28 /** 29 * Custom filter to only match the start of spaced words. 30 * Based on the core/default one. 31 * 32 * @param {string} query 33 * @param {array} data 34 * @param {string} search_key 35 * @return {array} 36 * @since BuddyPress (2.1.0) 37 */ 38 filter: function( query, data, search_key ) { 39 var item, _i, _len, _results = [], 40 regxp = new RegExp( '^' + query + '| ' + query, 'ig' ); // start of string, or preceded by a space. 41 42 for ( _i = 0, _len = data.length; _i < _len; _i++ ) { 43 item = data[ _i ]; 44 if ( item[ search_key ].toLowerCase().match( regxp ) ) { 45 _results.push( item ); 46 } 47 } 48 49 return _results; 50 }, 51 52 /** 53 * Removes some spaces around highlighted string and tweaks regex to allow spaces 54 * (to match display_name). Based on the core default. 55 * 56 * @param {unknown} li 57 * @param {string} query 58 * @return {string} 59 * @since BuddyPress (2.1.0) 60 */ 61 highlighter: function( li, query ) { 62 if ( ! query ) { 63 return li; 64 } 65 66 var regexp = new RegExp( '>(\\s*|[\\w\\s]*)(' + this.at.replace( '+', '\\+') + '?' + query.replace( '+', '\\+' ) + ')([\\w ]*)\\s*<', 'ig' ); 67 return li.replace( regexp, function( str, $1, $2, $3 ) { 68 return '>' + $1 + '<strong>' + $2 + '</strong>' + $3 + '<'; 69 }); 70 }, 71 72 /** 73 * Reposition the suggestion list dynamically. 74 * 75 * @param {unknown} offset 76 * @since BuddyPress (2.1.0) 77 */ 78 before_reposition: function( offset ) { 79 var $view = $( '#atwho-ground-' + this.id + ' .atwho-view' ), 80 caret = this.$inputor.caret( 'offset', { iframe: $( '#content_ifr' )[0] } ).left, 81 move; 82 83 // If the caret is past horizontal half, then flip it, yo. 84 if ( caret > ( $( 'body' ).width() / 2 ) ) { 85 $view.addClass( 'flip' ); 86 move = caret - offset.left - this.view.$el.width(); 87 } else { 88 $view.removeClass( 'flip' ); 89 move = caret - offset.left + 1; 90 } 91 92 offset.top += 1; 93 offset.left += move; 94 }, 95 96 /** 97 * Override default behaviour which inserts junk tags in the WordPress Visual editor. 98 * 99 * @param {unknown} $inputor Element which we're inserting content into. 100 * @param {string) content The content that will be inserted. 101 * @param {string) suffix Applied to the end of the content string. 102 * @return {string} 103 * @since BuddyPress (2.1.0) 104 */ 105 inserting_wrapper: function( $inputor, content, suffix ) { 106 var new_suffix = ( suffix === '' ) ? suffix : suffix || ' '; 107 return '' + content + new_suffix; 108 } 109 } 110 }, 111 112 /** 113 * Default options for our @mentions; see https://github.com/ichord/At.js/. 114 */ 115 mentionsDefaults = { 116 callbacks: { 117 /** 118 * If there are no matches for the query in this.data, then query BuddyPress. 119 * 120 * @param {string} query Partial @mention to search for. 121 * @param {function} render_view Render page callback function. 122 * @since BuddyPress (2.1.0) 123 */ 124 remote_filter: function( query, render_view ) { 125 var self = $( this ); 126 127 mentionsItem = mentionsQueryCache[ query ]; 128 if ( typeof mentionsItem === 'object' ) { 129 render_view( mentionsItem ); 130 return; 131 } 132 133 if ( self.xhr ) { 134 self.xhr.abort(); 135 } 136 137 self.xhr = $.getJSON( ajaxurl, { 'action': 'bp_get_suggestions', 'term': query, 'type': 'members' } ) 138 /** 139 * Success callback for the @suggestions lookup. 140 * 141 * @param {object} response Details of users matching the query. 142 * @since BuddyPress (2.1.0) 143 */ 144 .done(function( response ) { 145 if ( ! response.success ) { 146 return; 147 } 148 149 var data = $.map( response.data, 150 /** 151 * Create a composite index to determine ordering of results; 152 * nicename matches will appear on top. 153 * 154 * @param {array} suggestion A suggestion's original data. 155 * @return {array} A suggestion's new data. 156 * @since BuddyPress (2.1.0) 157 */ 158 function( suggestion ) { 159 suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name; 160 return suggestion; 161 } 162 ); 163 164 mentionsQueryCache[ query ] = data; 165 render_view( data ); 166 }); 167 } 168 }, 169 170 data: $.map( options.data, 171 /** 172 * Create a composite index to search against of nicename + display name. 173 * This will also determine ordering of results, so nicename matches will appear on top. 174 * 175 * @param {array} suggestion A suggestion's original data. 176 * @return {array} A suggestion's new data. 177 * @since BuddyPress (2.1.0) 178 */ 179 function( suggestion ) { 180 suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name; 181 return suggestion; 182 } 183 ), 184 185 at: '@', 186 search_key: 'search', 187 tpl: '<li data-value="@${ID}"><img src="${image}" /><span class="username">@${ID}</span><small>${name}</small></li>' 188 }, 189 190 opts = $.extend( true, {}, suggestionsDefaults, mentionsDefaults, options ); 191 return $.fn.atwho.call( this, opts ); 192 }; 193 194 $( document ).ready(function() { 195 var users = []; 196 197 if ( typeof window.BP_Suggestions === 'object' ) { 198 users = window.BP_Suggestions.friends || users; 199 } 200 201 // Activity/reply, post comments, dashboard post 'text' editor. 202 $( '.bp-suggestions, #comments form textarea, .wp-editor-area' ).bp_mentions( users ); 203 }); 204 })( jQuery ); 205 No newline at end of file -
src/bp-core/bp-core-classes.php
diff --git src/bp-core/bp-core-classes.php src/bp-core/bp-core-classes.php index 5bbdf0b..ed9c260 100644
class BP_User_Query { 368 368 // 'search_terms' searches user_login and user_nicename 369 369 // xprofile field matches happen in bp_xprofile_bp_user_query_search() 370 370 if ( false !== $search_terms ) { 371 $search_terms _like= bp_esc_like( $search_terms );371 $search_terms = bp_esc_like( $search_terms ); 372 372 373 373 if ( $search_wildcard === 'left' ) { 374 $search_terms_like = '%' . $search_terms_like; 374 $search_terms_nospace = '%' . $search_terms; 375 $search_terms_space = '%' . $search_terms . ' %'; 375 376 } elseif ( $search_wildcard === 'right' ) { 376 $search_terms_like = $search_terms_like . '%'; 377 $search_terms_nospace = $search_terms . '%'; 378 $search_terms_space = '% ' . $search_terms . '%'; 377 379 } else { 378 $search_terms_like = '%' . $search_terms_like . '%'; 380 $search_terms_nospace = '%' . $search_terms . '%'; 381 $search_terms_space = '%' . $search_terms . '%'; 379 382 } 380 383 381 $sql['where']['search'] = $wpdb->prepare( "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE ( user_login LIKE %s OR user_nicename LIKE %s ) )", $search_terms_like, $search_terms_like ); 384 $sql['where']['search'] = $wpdb->prepare( 385 "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE ( user_login LIKE %s OR user_login LIKE %s OR user_nicename LIKE %s OR user_nicename LIKE %s ) )", 386 $search_terms_nospace, 387 $search_terms_space, 388 $search_terms_nospace, 389 $search_terms_space 390 ); 382 391 } 383 392 384 393 // 'meta_key', 'meta_value' allow usermeta search … … class BP_Members_Suggestions extends BP_Suggestions { 2566 2575 * } 2567 2576 */ 2568 2577 protected $default_args = array( 2569 'limit' => 1 6,2578 'limit' => 10, 2570 2579 'only_friends' => false, 2571 2580 'term' => '', 2572 2581 'type' => '', … … class BP_Members_Suggestions extends BP_Suggestions { 2606 2615 'page' => 1, 2607 2616 'per_page' => $this->args['limit'], 2608 2617 'search_terms' => $this->args['term'], 2618 'search_wildcard' => is_rtl() ? 'left' : 'right', 2609 2619 ); 2610 2620 2611 2621 // Only return matches of friends of this user. -
src/bp-core/bp-core-cssjs.php
diff --git src/bp-core/bp-core-cssjs.php src/bp-core/bp-core-cssjs.php index b6fa396..c7f7011 100644
function bp_core_register_common_scripts() { 19 19 $url = buddypress()->plugin_url . 'bp-core/js/'; 20 20 21 21 $scripts = apply_filters( 'bp_core_register_common_scripts', array( 22 // Legacy 22 23 'bp-confirm' => array( 'file' => "{$url}confirm{$ext}", 'dependencies' => array( 'jquery' ) ), 23 24 'bp-widget-members' => array( 'file' => "{$url}widget-members{$ext}", 'dependencies' => array( 'jquery' ) ), 24 25 'bp-jquery-query' => array( 'file' => "{$url}jquery-query{$ext}", 'dependencies' => array( 'jquery' ) ), 25 26 'bp-jquery-cookie' => array( 'file' => "{$url}jquery-cookie{$ext}", 'dependencies' => array( 'jquery' ) ), 27 28 // 2.1 29 'jquery-caret' => array( 'file' => "{$url}jquery.caret{$ext}", 'dependencies' => array( 'jquery' ) ), 30 'jquery-atwho' => array( 'file' => "{$url}jquery.atwho{$ext}", 'dependencies' => array( 'jquery', 'jquery-caret' ) ), 26 31 ) ); 27 32 28 33 foreach ( $scripts as $id => $script ) { -
new file src/bp-core/js/jquery.atwho.js
diff --git src/bp-core/js/jquery.atwho.js src/bp-core/js/jquery.atwho.js new file mode 100755 index 0000000..d5eefe3
- + 1 /*! jquery.atwho - v0.5.0 - 2014-07-14 2 * Copyright (c) 2014 chord.luo <chord.luo@gmail.com>; 3 * homepage: http://ichord.github.com/At.js 4 * Licensed MIT 5 */ 6 7 (function() { 8 (function(factory) { 9 if (typeof define === 'function' && define.amd) { 10 return define(['jquery'], factory); 11 } else { 12 return factory(window.jQuery); 13 } 14 })(function($) { 15 16 var $CONTAINER, Api, App, Controller, DEFAULT_CALLBACKS, KEY_CODE, Model, View, 17 __slice = [].slice; 18 19 App = (function() { 20 function App(inputor) { 21 this.current_flag = null; 22 this.controllers = {}; 23 this.alias_maps = {}; 24 this.$inputor = $(inputor); 25 this.iframe = null; 26 this.setIframe(); 27 this.listen(); 28 } 29 30 App.prototype.setIframe = function(iframe) { 31 if (iframe) { 32 this.window = iframe.contentWindow; 33 this.document = iframe.contentDocument || this.window.document; 34 return this.iframe = iframe; 35 } else { 36 this.document = document; 37 this.window = window; 38 return this.iframe = null; 39 } 40 }; 41 42 App.prototype.controller = function(at) { 43 var c, current, current_flag, _ref; 44 if (this.alias_maps[at]) { 45 current = this.controllers[this.alias_maps[at]]; 46 } else { 47 _ref = this.controllers; 48 for (current_flag in _ref) { 49 c = _ref[current_flag]; 50 if (current_flag === at) { 51 current = c; 52 break; 53 } 54 } 55 } 56 if (current) { 57 return current; 58 } else { 59 return this.controllers[this.current_flag]; 60 } 61 }; 62 63 App.prototype.set_context_for = function(at) { 64 this.current_flag = at; 65 return this; 66 }; 67 68 App.prototype.reg = function(flag, setting) { 69 var controller, _base; 70 controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag)); 71 if (setting.alias) { 72 this.alias_maps[setting.alias] = flag; 73 } 74 controller.init(setting); 75 return this; 76 }; 77 78 App.prototype.listen = function() { 79 return this.$inputor.on('keyup.atwhoInner', (function(_this) { 80 return function(e) { 81 return _this.on_keyup(e); 82 }; 83 })(this)).on('keydown.atwhoInner', (function(_this) { 84 return function(e) { 85 return _this.on_keydown(e); 86 }; 87 })(this)).on('scroll.atwhoInner', (function(_this) { 88 return function(e) { 89 var _ref; 90 return (_ref = _this.controller()) != null ? _ref.view.hide(e) : void 0; 91 }; 92 })(this)).on('blur.atwhoInner', (function(_this) { 93 return function(e) { 94 var c; 95 if (c = _this.controller()) { 96 return c.view.hide(e, c.get_opt("display_timeout")); 97 } 98 }; 99 })(this)).on('click.atwhoInner', (function(_this) { 100 return function(e) { 101 var _ref; 102 return (_ref = _this.controller()) != null ? _ref.view.hide(e) : void 0; 103 }; 104 })(this)); 105 }; 106 107 App.prototype.shutdown = function() { 108 var c, _, _ref; 109 _ref = this.controllers; 110 for (_ in _ref) { 111 c = _ref[_]; 112 c.destroy(); 113 delete this.controllers[_]; 114 } 115 return this.$inputor.off('.atwhoInner'); 116 }; 117 118 App.prototype.dispatch = function() { 119 return $.map(this.controllers, (function(_this) { 120 return function(c) { 121 var delay; 122 if (delay = c.get_opt('delay')) { 123 clearTimeout(_this.delayedCallback); 124 return _this.delayedCallback = setTimeout(function() { 125 if (c.look_up()) { 126 return _this.set_context_for(c.at); 127 } 128 }, delay); 129 } else { 130 if (c.look_up()) { 131 return _this.set_context_for(c.at); 132 } 133 } 134 }; 135 })(this)); 136 }; 137 138 App.prototype.on_keyup = function(e) { 139 var _ref; 140 switch (e.keyCode) { 141 case KEY_CODE.ESC: 142 e.preventDefault(); 143 if ((_ref = this.controller()) != null) { 144 _ref.view.hide(); 145 } 146 break; 147 case KEY_CODE.DOWN: 148 case KEY_CODE.UP: 149 case KEY_CODE.CTRL: 150 $.noop(); 151 break; 152 case KEY_CODE.P: 153 case KEY_CODE.N: 154 if (!e.ctrlKey) { 155 this.dispatch(); 156 } 157 break; 158 default: 159 this.dispatch(); 160 } 161 }; 162 163 App.prototype.on_keydown = function(e) { 164 var view, _ref; 165 view = (_ref = this.controller()) != null ? _ref.view : void 0; 166 if (!(view && view.visible())) { 167 return; 168 } 169 switch (e.keyCode) { 170 case KEY_CODE.ESC: 171 e.preventDefault(); 172 view.hide(e); 173 break; 174 case KEY_CODE.UP: 175 e.preventDefault(); 176 view.prev(); 177 break; 178 case KEY_CODE.DOWN: 179 e.preventDefault(); 180 view.next(); 181 break; 182 case KEY_CODE.P: 183 if (!e.ctrlKey) { 184 return; 185 } 186 e.preventDefault(); 187 view.prev(); 188 break; 189 case KEY_CODE.N: 190 if (!e.ctrlKey) { 191 return; 192 } 193 e.preventDefault(); 194 view.next(); 195 break; 196 case KEY_CODE.TAB: 197 case KEY_CODE.ENTER: 198 if (!view.visible()) { 199 return; 200 } 201 e.preventDefault(); 202 view.choose(e); 203 break; 204 default: 205 $.noop(); 206 } 207 }; 208 209 return App; 210 211 })(); 212 213 Controller = (function() { 214 Controller.prototype.uid = function() { 215 return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime()); 216 }; 217 218 function Controller(app, at) { 219 this.app = app; 220 this.at = at; 221 this.$inputor = this.app.$inputor; 222 this.id = this.$inputor[0].id || this.uid(); 223 this.setting = null; 224 this.query = null; 225 this.pos = 0; 226 this.cur_rect = null; 227 this.range = null; 228 $CONTAINER.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>")); 229 this.model = new Model(this); 230 this.view = new View(this); 231 } 232 233 Controller.prototype.init = function(setting) { 234 this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting); 235 this.view.init(); 236 return this.model.reload(this.setting.data); 237 }; 238 239 Controller.prototype.destroy = function() { 240 this.trigger('beforeDestroy'); 241 this.model.destroy(); 242 this.view.destroy(); 243 return this.$el.remove(); 244 }; 245 246 Controller.prototype.call_default = function() { 247 var args, error, func_name; 248 func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 249 try { 250 return DEFAULT_CALLBACKS[func_name].apply(this, args); 251 } catch (_error) { 252 error = _error; 253 return $.error("" + error + " Or maybe At.js doesn't have function " + func_name); 254 } 255 }; 256 257 Controller.prototype.trigger = function(name, data) { 258 var alias, event_name; 259 if (data == null) { 260 data = []; 261 } 262 data.push(this); 263 alias = this.get_opt('alias'); 264 event_name = alias ? "" + name + "-" + alias + ".atwho" : "" + name + ".atwho"; 265 return this.$inputor.trigger(event_name, data); 266 }; 267 268 Controller.prototype.callbacks = function(func_name) { 269 return this.get_opt("callbacks")[func_name] || DEFAULT_CALLBACKS[func_name]; 270 }; 271 272 Controller.prototype.get_opt = function(at, default_value) { 273 var e; 274 try { 275 return this.setting[at]; 276 } catch (_error) { 277 e = _error; 278 return null; 279 } 280 }; 281 282 Controller.prototype.content = function() { 283 if (this.$inputor.is('textarea, input')) { 284 return this.$inputor.val(); 285 } else { 286 return this.$inputor.text(); 287 } 288 }; 289 290 Controller.prototype.catch_query = function() { 291 var caret_pos, content, end, query, start, subtext; 292 content = this.content(); 293 caret_pos = this.$inputor.caret('pos', { 294 iframe: this.app.iframe 295 }); 296 subtext = content.slice(0, caret_pos); 297 query = this.callbacks("matcher").call(this, this.at, subtext, this.get_opt('start_with_space')); 298 if (typeof query === "string" && query.length <= this.get_opt('max_len', 20)) { 299 start = caret_pos - query.length; 300 end = start + query.length; 301 this.pos = start; 302 query = { 303 'text': query, 304 'head_pos': start, 305 'end_pos': end 306 }; 307 this.trigger("matched", [this.at, query.text]); 308 } else { 309 query = null; 310 this.view.hide(); 311 } 312 return this.query = query; 313 }; 314 315 Controller.prototype.rect = function() { 316 var c, scale_bottom; 317 if (!(c = this.$inputor.caret('offset', this.pos - 1, { 318 iframe: this.app.iframe 319 }))) { 320 return; 321 } 322 if (this.$inputor.attr('contentEditable') === 'true') { 323 c = (this.cur_rect || (this.cur_rect = c)) || c; 324 } 325 scale_bottom = this.app.document.selection ? 0 : 2; 326 return { 327 left: c.left, 328 top: c.top, 329 bottom: c.top + c.height + scale_bottom 330 }; 331 }; 332 333 Controller.prototype.reset_rect = function() { 334 if (this.$inputor.attr('contentEditable') === 'true') { 335 return this.cur_rect = null; 336 } 337 }; 338 339 Controller.prototype.mark_range = function() { 340 if (this.$inputor.attr('contentEditable') === 'true') { 341 if (this.app.window.getSelection) { 342 this.range = this.app.window.getSelection().getRangeAt(0); 343 } 344 if (this.app.document.selection) { 345 return this.ie8_range = this.app.document.selection.createRange(); 346 } 347 } 348 }; 349 350 Controller.prototype.insert_content_for = function($li) { 351 var data, data_value, tpl; 352 data_value = $li.data('value'); 353 tpl = this.get_opt('insert_tpl'); 354 if (this.$inputor.is('textarea, input') || !tpl) { 355 return data_value; 356 } 357 data = $.extend({}, $li.data('item-data'), { 358 'atwho-data-value': data_value, 359 'atwho-at': this.at 360 }); 361 return this.callbacks("tpl_eval").call(this, tpl, data); 362 }; 363 364 Controller.prototype.insert = function(content, $li) { 365 var $inputor, content_node, pos, range, sel, source, start_str, text, wrapped_content; 366 $inputor = this.$inputor; 367 wrapped_content = this.callbacks('inserting_wrapper').call(this, $inputor, content, this.get_opt("suffix")); 368 if ($inputor.is('textarea, input')) { 369 source = $inputor.val(); 370 start_str = source.slice(0, Math.max(this.query.head_pos - this.at.length, 0)); 371 text = "" + start_str + wrapped_content + (source.slice(this.query['end_pos'] || 0)); 372 $inputor.val(text); 373 $inputor.caret('pos', start_str.length + wrapped_content.length, { 374 iframe: this.app.iframe 375 }); 376 } else if (range = this.range) { 377 pos = range.startOffset - (this.query.end_pos - this.query.head_pos) - this.at.length; 378 range.setStart(range.endContainer, Math.max(pos, 0)); 379 range.setEnd(range.endContainer, range.endOffset); 380 range.deleteContents(); 381 content_node = $(wrapped_content, this.app.document)[0]; 382 range.insertNode(content_node); 383 range.setEndAfter(content_node); 384 range.collapse(false); 385 sel = this.app.window.getSelection(); 386 sel.removeAllRanges(); 387 sel.addRange(range); 388 } else if (range = this.ie8_range) { 389 range.moveStart('character', this.query.end_pos - this.query.head_pos - this.at.length); 390 range.pasteHTML(wrapped_content); 391 range.collapse(false); 392 range.select(); 393 } 394 if (!$inputor.is(':focus')) { 395 $inputor.focus(); 396 } 397 return $inputor.change(); 398 }; 399 400 Controller.prototype.render_view = function(data) { 401 var search_key; 402 search_key = this.get_opt("search_key"); 403 data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), search_key); 404 return this.view.render(data.slice(0, this.get_opt('limit'))); 405 }; 406 407 Controller.prototype.look_up = function() { 408 var query, _callback; 409 if (!(query = this.catch_query())) { 410 return; 411 } 412 _callback = function(data) { 413 if (data && data.length > 0) { 414 return this.render_view(data); 415 } else { 416 return this.view.hide(); 417 } 418 }; 419 this.model.query(query.text, $.proxy(_callback, this)); 420 return query; 421 }; 422 423 return Controller; 424 425 })(); 426 427 Model = (function() { 428 function Model(context) { 429 this.context = context; 430 this.at = this.context.at; 431 this.storage = this.context.$inputor; 432 } 433 434 Model.prototype.destroy = function() { 435 return this.storage.data(this.at, null); 436 }; 437 438 Model.prototype.saved = function() { 439 return this.fetch() > 0; 440 }; 441 442 Model.prototype.query = function(query, callback) { 443 var data, search_key, _remote_filter; 444 data = this.fetch(); 445 search_key = this.context.get_opt("search_key"); 446 data = this.context.callbacks('filter').call(this.context, query, data, search_key) || []; 447 _remote_filter = this.context.callbacks('remote_filter'); 448 if (data.length > 0 || (!_remote_filter && data.length === 0)) { 449 return callback(data); 450 } else { 451 return _remote_filter.call(this.context, query, callback); 452 } 453 }; 454 455 Model.prototype.fetch = function() { 456 return this.storage.data(this.at) || []; 457 }; 458 459 Model.prototype.save = function(data) { 460 return this.storage.data(this.at, this.context.callbacks("before_save").call(this.context, data || [])); 461 }; 462 463 Model.prototype.load = function(data) { 464 if (!(this.saved() || !data)) { 465 return this._load(data); 466 } 467 }; 468 469 Model.prototype.reload = function(data) { 470 return this._load(data); 471 }; 472 473 Model.prototype._load = function(data) { 474 if (typeof data === "string") { 475 return $.ajax(data, { 476 dataType: "json" 477 }).done((function(_this) { 478 return function(data) { 479 return _this.save(data); 480 }; 481 })(this)); 482 } else { 483 return this.save(data); 484 } 485 }; 486 487 return Model; 488 489 })(); 490 491 View = (function() { 492 function View(context) { 493 this.context = context; 494 this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>"); 495 this.timeout_id = null; 496 this.context.$el.append(this.$el); 497 this.bind_event(); 498 } 499 500 View.prototype.init = function() { 501 var id; 502 id = this.context.get_opt("alias") || this.context.at.charCodeAt(0); 503 return this.$el.attr({ 504 'id': "at-view-" + id 505 }); 506 }; 507 508 View.prototype.destroy = function() { 509 return this.$el.remove(); 510 }; 511 512 View.prototype.bind_event = function() { 513 var $menu; 514 $menu = this.$el.find('ul'); 515 return $menu.on('mouseenter.atwho-view', 'li', function(e) { 516 $menu.find('.cur').removeClass('cur'); 517 return $(e.currentTarget).addClass('cur'); 518 }).on('click', (function(_this) { 519 return function(e) { 520 _this.choose(e); 521 return e.preventDefault(); 522 }; 523 })(this)); 524 }; 525 526 View.prototype.visible = function() { 527 return this.$el.is(":visible"); 528 }; 529 530 View.prototype.choose = function(e) { 531 var $li, content; 532 if (($li = this.$el.find(".cur")).length) { 533 content = this.context.insert_content_for($li); 534 this.context.insert(this.context.callbacks("before_insert").call(this.context, content, $li), $li); 535 this.context.trigger("inserted", [$li, e]); 536 this.hide(e); 537 } 538 if (this.context.get_opt("hide_without_suffix")) { 539 return this.stop_showing = true; 540 } 541 }; 542 543 View.prototype.reposition = function(rect) { 544 var offset, _ref; 545 if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) { 546 rect.bottom = rect.top - this.$el.height(); 547 } 548 offset = { 549 left: rect.left, 550 top: rect.bottom 551 }; 552 if ((_ref = this.context.callbacks("before_reposition")) != null) { 553 _ref.call(this.context, offset); 554 } 555 this.$el.offset(offset); 556 return this.context.trigger("reposition", [offset]); 557 }; 558 559 View.prototype.next = function() { 560 var cur, next; 561 cur = this.$el.find('.cur').removeClass('cur'); 562 next = cur.next(); 563 if (!next.length) { 564 next = this.$el.find('li:first'); 565 } 566 return next.addClass('cur'); 567 }; 568 569 View.prototype.prev = function() { 570 var cur, prev; 571 cur = this.$el.find('.cur').removeClass('cur'); 572 prev = cur.prev(); 573 if (!prev.length) { 574 prev = this.$el.find('li:last'); 575 } 576 return prev.addClass('cur'); 577 }; 578 579 View.prototype.show = function() { 580 var rect; 581 if (this.stop_showing) { 582 this.stop_showing = false; 583 return; 584 } 585 this.context.mark_range(); 586 if (!this.visible()) { 587 this.$el.show(); 588 this.context.trigger('shown'); 589 } 590 if (rect = this.context.rect()) { 591 return this.reposition(rect); 592 } 593 }; 594 595 View.prototype.hide = function(e, time) { 596 var callback; 597 if (!this.visible()) { 598 return; 599 } 600 if (isNaN(time)) { 601 this.context.reset_rect(); 602 this.$el.hide(); 603 return this.context.trigger('hidden', [e]); 604 } else { 605 callback = (function(_this) { 606 return function() { 607 return _this.hide(); 608 }; 609 })(this); 610 clearTimeout(this.timeout_id); 611 return this.timeout_id = setTimeout(callback, time); 612 } 613 }; 614 615 View.prototype.render = function(list) { 616 var $li, $ul, item, li, tpl, _i, _len; 617 if (!($.isArray(list) && list.length > 0)) { 618 this.hide(); 619 return; 620 } 621 this.$el.find('ul').empty(); 622 $ul = this.$el.find('ul'); 623 tpl = this.context.get_opt('tpl'); 624 for (_i = 0, _len = list.length; _i < _len; _i++) { 625 item = list[_i]; 626 item = $.extend({}, item, { 627 'atwho-at': this.context.at 628 }); 629 li = this.context.callbacks("tpl_eval").call(this.context, tpl, item); 630 $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text)); 631 $li.data("item-data", item); 632 $ul.append($li); 633 } 634 this.show(); 635 if (this.context.get_opt('highlight_first')) { 636 return $ul.find("li:first").addClass("cur"); 637 } 638 }; 639 640 return View; 641 642 })(); 643 644 KEY_CODE = { 645 DOWN: 40, 646 UP: 38, 647 ESC: 27, 648 TAB: 9, 649 ENTER: 13, 650 CTRL: 17, 651 P: 80, 652 N: 78 653 }; 654 655 DEFAULT_CALLBACKS = { 656 before_save: function(data) { 657 var item, _i, _len, _results; 658 if (!$.isArray(data)) { 659 return data; 660 } 661 _results = []; 662 for (_i = 0, _len = data.length; _i < _len; _i++) { 663 item = data[_i]; 664 if ($.isPlainObject(item)) { 665 _results.push(item); 666 } else { 667 _results.push({ 668 name: item 669 }); 670 } 671 } 672 return _results; 673 }, 674 matcher: function(flag, subtext, should_start_with_space) { 675 var match, regexp; 676 flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 677 if (should_start_with_space) { 678 flag = '(?:^|\\s)' + flag; 679 } 680 regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi'); 681 match = regexp.exec(subtext); 682 if (match) { 683 return match[2] || match[1]; 684 } else { 685 return null; 686 } 687 }, 688 filter: function(query, data, search_key) { 689 var item, _i, _len, _results; 690 _results = []; 691 for (_i = 0, _len = data.length; _i < _len; _i++) { 692 item = data[_i]; 693 if (~item[search_key].toLowerCase().indexOf(query.toLowerCase())) { 694 _results.push(item); 695 } 696 } 697 return _results; 698 }, 699 remote_filter: null, 700 sorter: function(query, items, search_key) { 701 var item, _i, _len, _results; 702 if (!query) { 703 return items; 704 } 705 _results = []; 706 for (_i = 0, _len = items.length; _i < _len; _i++) { 707 item = items[_i]; 708 item.atwho_order = item[search_key].toLowerCase().indexOf(query.toLowerCase()); 709 if (item.atwho_order > -1) { 710 _results.push(item); 711 } 712 } 713 return _results.sort(function(a, b) { 714 return a.atwho_order - b.atwho_order; 715 }); 716 }, 717 tpl_eval: function(tpl, map) { 718 var error; 719 try { 720 return tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) { 721 return map[key]; 722 }); 723 } catch (_error) { 724 error = _error; 725 return ""; 726 } 727 }, 728 highlighter: function(li, query) { 729 var regexp; 730 if (!query) { 731 return li; 732 } 733 regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'); 734 return li.replace(regexp, function(str, $1, $2, $3) { 735 return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <'; 736 }); 737 }, 738 before_insert: function(value, $li) { 739 return value; 740 }, 741 inserting_wrapper: function($inputor, content, suffix) { 742 var new_suffix, wrapped_content; 743 new_suffix = suffix === "" ? suffix : suffix || " "; 744 if ($inputor.is('textarea, input')) { 745 return '' + content + new_suffix; 746 } else if ($inputor.attr('contentEditable') === 'true') { 747 new_suffix = suffix === "" ? suffix : suffix || " "; 748 if (/firefox/i.test(navigator.userAgent)) { 749 wrapped_content = "<span>" + content + new_suffix + "</span>"; 750 } else { 751 suffix = "<span contenteditable='false'>" + new_suffix + "<span>"; 752 wrapped_content = "<span contenteditable='false'>" + content + suffix + "</span>"; 753 } 754 if (this.app.document.selection) { 755 wrapped_content = "<span contenteditable='true'>" + content + "</span>"; 756 } 757 return wrapped_content; 758 } 759 } 760 }; 761 762 Api = { 763 load: function(at, data) { 764 var c; 765 if (c = this.controller(at)) { 766 return c.model.load(data); 767 } 768 }, 769 setIframe: function(iframe) { 770 this.setIframe(iframe); 771 return null; 772 }, 773 run: function() { 774 return this.dispatch(); 775 }, 776 destroy: function() { 777 this.shutdown(); 778 return this.$inputor.data('atwho', null); 779 } 780 }; 781 782 $CONTAINER = $("<div id='atwho-container'></div>"); 783 784 $.fn.atwho = function(method) { 785 var result, _args; 786 _args = arguments; 787 $('body').append($CONTAINER); 788 result = null; 789 this.filter('textarea, input, [contenteditable=true]').each(function() { 790 var $this, app; 791 if (!(app = ($this = $(this)).data("atwho"))) { 792 $this.data('atwho', (app = new App(this))); 793 } 794 if (typeof method === 'object' || !method) { 795 return app.reg(method.at, method); 796 } else if (Api[method] && app) { 797 return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1)); 798 } else { 799 return $.error("Method " + method + " does not exist on jQuery.caret"); 800 } 801 }); 802 return result || this; 803 }; 804 805 $.fn.atwho["default"] = { 806 at: void 0, 807 alias: void 0, 808 data: null, 809 tpl: "<li data-value='${atwho-at}${name}'>${name}</li>", 810 insert_tpl: "<span id='${id}'>${atwho-data-value}</span>", 811 callbacks: DEFAULT_CALLBACKS, 812 search_key: "name", 813 suffix: void 0, 814 hide_without_suffix: false, 815 start_with_space: true, 816 highlight_first: true, 817 limit: 5, 818 max_len: 20, 819 display_timeout: 300, 820 delay: null 821 }; 822 823 }); 824 }).call(this); -
new file src/bp-core/js/jquery.atwho.txt
diff --git src/bp-core/js/jquery.atwho.txt src/bp-core/js/jquery.atwho.txt new file mode 100755 index 0000000..36cd1c1
- + 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/jquery.caret.js
diff --git src/bp-core/js/jquery.caret.js src/bp-core/js/jquery.caret.js new file mode 100755 index 0000000..caa7876
- + 1 /* 2 Implement Github like autocomplete mentions 3 http://ichord.github.com/At.js 4 5 Copyright (c) 2013 chord.luo@gmail.com 6 Licensed under the MIT license. 7 */ 8 9 10 /* 11 本插件操作 textarea 或者 input 内的插入符 12 只实现了获得插入符在文本框中的位置,我设置 13 插入符的位置. 14 */ 15 16 17 (function() { 18 (function(factory) { 19 if (typeof define === 'function' && define.amd) { 20 return define(['jquery'], factory); 21 } else { 22 return factory(window.jQuery); 23 } 24 })(function($) { 25 "use strict"; 26 var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy; 27 pluginName = 'caret'; 28 EditableCaret = (function() { 29 function EditableCaret($inputor) { 30 this.$inputor = $inputor; 31 this.domInputor = this.$inputor[0]; 32 } 33 34 EditableCaret.prototype.setPos = function(pos) { 35 return this.domInputor; 36 }; 37 38 EditableCaret.prototype.getIEPosition = function() { 39 return $.noop(); 40 }; 41 42 EditableCaret.prototype.getPosition = function() { 43 return $.noop(); 44 }; 45 46 EditableCaret.prototype.getOldIEPos = function() { 47 var preCaretTextRange, textRange; 48 textRange = oDocument.selection.createRange(); 49 preCaretTextRange = oDocument.body.createTextRange(); 50 preCaretTextRange.moveToElementText(this.domInputor); 51 preCaretTextRange.setEndPoint("EndToEnd", textRange); 52 return preCaretTextRange.text.length; 53 }; 54 55 EditableCaret.prototype.getPos = function() { 56 var clonedRange, pos, range; 57 if (range = this.range()) { 58 clonedRange = range.cloneRange(); 59 clonedRange.selectNodeContents(this.domInputor); 60 clonedRange.setEnd(range.endContainer, range.endOffset); 61 pos = clonedRange.toString().length; 62 clonedRange.detach(); 63 return pos; 64 } else if (oDocument.selection) { 65 return this.getOldIEPos(); 66 } 67 }; 68 69 EditableCaret.prototype.getOldIEOffset = function() { 70 var range, rect; 71 range = oDocument.selection.createRange().duplicate(); 72 range.moveStart("character", -1); 73 rect = range.getBoundingClientRect(); 74 return { 75 height: rect.bottom - rect.top, 76 left: rect.left, 77 top: rect.top 78 }; 79 }; 80 81 EditableCaret.prototype.getOffset = function(pos) { 82 var clonedRange, offset, range, rect; 83 if (oWindow.getSelection && (range = this.range())) { 84 if (range.endOffset - 1 < 0) { 85 return null; 86 } 87 clonedRange = range.cloneRange(); 88 clonedRange.setStart(range.endContainer, range.endOffset - 1); 89 clonedRange.setEnd(range.endContainer, range.endOffset); 90 rect = clonedRange.getBoundingClientRect(); 91 offset = { 92 height: rect.height, 93 left: rect.left + rect.width, 94 top: rect.top 95 }; 96 clonedRange.detach(); 97 } else if (oDocument.selection) { 98 offset = this.getOldIEOffset(); 99 } 100 if (offset && !oFrame) { 101 offset.top += $(oWindow).scrollTop(); 102 offset.left += $(oWindow).scrollLeft(); 103 } 104 return offset; 105 }; 106 107 EditableCaret.prototype.range = function() { 108 var sel; 109 if (!oWindow.getSelection) { 110 return; 111 } 112 sel = oWindow.getSelection(); 113 if (sel.rangeCount > 0) { 114 return sel.getRangeAt(0); 115 } else { 116 return null; 117 } 118 }; 119 120 return EditableCaret; 121 122 })(); 123 InputCaret = (function() { 124 function InputCaret($inputor) { 125 this.$inputor = $inputor; 126 this.domInputor = this.$inputor[0]; 127 } 128 129 InputCaret.prototype.getIEPos = function() { 130 var endRange, inputor, len, normalizedValue, pos, range, textInputRange; 131 inputor = this.domInputor; 132 range = oDocument.selection.createRange(); 133 pos = 0; 134 if (range && range.parentElement() === inputor) { 135 normalizedValue = inputor.value.replace(/\r\n/g, "\n"); 136 len = normalizedValue.length; 137 textInputRange = inputor.createTextRange(); 138 textInputRange.moveToBookmark(range.getBookmark()); 139 endRange = inputor.createTextRange(); 140 endRange.collapse(false); 141 if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { 142 pos = len; 143 } else { 144 pos = -textInputRange.moveStart("character", -len); 145 } 146 } 147 return pos; 148 }; 149 150 InputCaret.prototype.getPos = function() { 151 if (oDocument.selection) { 152 return this.getIEPos(); 153 } else { 154 return this.domInputor.selectionStart; 155 } 156 }; 157 158 InputCaret.prototype.setPos = function(pos) { 159 var inputor, range; 160 inputor = this.domInputor; 161 if (oDocument.selection) { 162 range = inputor.createTextRange(); 163 range.move("character", pos); 164 range.select(); 165 } else if (inputor.setSelectionRange) { 166 inputor.setSelectionRange(pos, pos); 167 } 168 return inputor; 169 }; 170 171 InputCaret.prototype.getIEOffset = function(pos) { 172 var h, textRange, x, y; 173 textRange = this.domInputor.createTextRange(); 174 pos || (pos = this.getPos()); 175 textRange.move('character', pos); 176 x = textRange.boundingLeft; 177 y = textRange.boundingTop; 178 h = textRange.boundingHeight; 179 return { 180 left: x, 181 top: y, 182 height: h 183 }; 184 }; 185 186 InputCaret.prototype.getOffset = function(pos) { 187 var $inputor, offset, position; 188 $inputor = this.$inputor; 189 if (oDocument.selection) { 190 offset = this.getIEOffset(pos); 191 offset.top += $(oWindow).scrollTop() + $inputor.scrollTop(); 192 offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft(); 193 return offset; 194 } else { 195 offset = $inputor.offset(); 196 position = this.getPosition(pos); 197 return offset = { 198 left: offset.left + position.left - $inputor.scrollLeft(), 199 top: offset.top + position.top - $inputor.scrollTop(), 200 height: position.height 201 }; 202 } 203 }; 204 205 InputCaret.prototype.getPosition = function(pos) { 206 var $inputor, at_rect, end_range, format, html, mirror, start_range; 207 $inputor = this.$inputor; 208 format = function(value) { 209 return value.replace(/</g, '<').replace(/>/g, '>').replace(/`/g, '`').replace(/"/g, '"').replace(/\r\n|\r|\n/g, "<br />"); 210 }; 211 if (pos === void 0) { 212 pos = this.getPos(); 213 } 214 start_range = $inputor.val().slice(0, pos); 215 end_range = $inputor.val().slice(pos); 216 html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>"; 217 html += "<span id='caret' style='position: relative; display: inline;'>|</span>"; 218 html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>"; 219 mirror = new Mirror($inputor); 220 return at_rect = mirror.create(html).rect(); 221 }; 222 223 InputCaret.prototype.getIEPosition = function(pos) { 224 var h, inputorOffset, offset, x, y; 225 offset = this.getIEOffset(pos); 226 inputorOffset = this.$inputor.offset(); 227 x = offset.left - inputorOffset.left; 228 y = offset.top - inputorOffset.top; 229 h = offset.height; 230 return { 231 left: x, 232 top: y, 233 height: h 234 }; 235 }; 236 237 return InputCaret; 238 239 })(); 240 Mirror = (function() { 241 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"]; 242 243 function Mirror($inputor) { 244 this.$inputor = $inputor; 245 } 246 247 Mirror.prototype.mirrorCss = function() { 248 var css, 249 _this = this; 250 css = { 251 position: 'absolute', 252 left: -9999, 253 top: 0, 254 zIndex: -20000 255 }; 256 if (this.$inputor.prop('tagName') === 'TEXTAREA') { 257 this.css_attr.push('width'); 258 } 259 $.each(this.css_attr, function(i, p) { 260 return css[p] = _this.$inputor.css(p); 261 }); 262 return css; 263 }; 264 265 Mirror.prototype.create = function(html) { 266 this.$mirror = $('<div></div>'); 267 this.$mirror.css(this.mirrorCss()); 268 this.$mirror.html(html); 269 this.$inputor.after(this.$mirror); 270 return this; 271 }; 272 273 Mirror.prototype.rect = function() { 274 var $flag, pos, rect; 275 $flag = this.$mirror.find("#caret"); 276 pos = $flag.position(); 277 rect = { 278 left: pos.left, 279 top: pos.top, 280 height: $flag.height() 281 }; 282 this.$mirror.remove(); 283 return rect; 284 }; 285 286 return Mirror; 287 288 })(); 289 Utils = { 290 contentEditable: function($inputor) { 291 return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true'); 292 } 293 }; 294 methods = { 295 pos: function(pos) { 296 if (pos || pos === 0) { 297 return this.setPos(pos); 298 } else { 299 return this.getPos(); 300 } 301 }, 302 position: function(pos) { 303 if (oDocument.selection) { 304 return this.getIEPosition(pos); 305 } else { 306 return this.getPosition(pos); 307 } 308 }, 309 offset: function(pos) { 310 var iOffset, offset; 311 offset = this.getOffset(pos); 312 if (oFrame) { 313 iOffset = $(oFrame).offset(); 314 offset.top += iOffset.top; 315 offset.left += iOffset.left; 316 } 317 return offset; 318 } 319 }; 320 oDocument = null; 321 oWindow = null; 322 oFrame = null; 323 setContextBy = function(settings) { 324 var iframe; 325 if (iframe = settings != null ? settings.iframe : void 0) { 326 oFrame = iframe; 327 oWindow = iframe.contentWindow; 328 return oDocument = iframe.contentDocument || oWindow.document; 329 } else { 330 oFrame = void 0; 331 oWindow = window; 332 return oDocument = document; 333 } 334 }; 335 discoveryIframeOf = function($dom) { 336 var error; 337 oDocument = $dom[0].ownerDocument; 338 oWindow = oDocument.defaultView || oDocument.parentWindow; 339 try { 340 return oFrame = oWindow.frameElement; 341 } catch (_error) { 342 error = _error; 343 } 344 }; 345 $.fn.caret = function(method, value, settings) { 346 var caret; 347 if (methods[method]) { 348 if ($.isPlainObject(value)) { 349 setContextBy(value); 350 value = void 0; 351 } else { 352 setContextBy(settings); 353 } 354 caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this); 355 return methods[method].apply(caret, [value]); 356 } else { 357 return $.error("Method " + method + " does not exist on jQuery.caret"); 358 } 359 }; 360 $.fn.caret.EditableCaret = EditableCaret; 361 $.fn.caret.InputCaret = InputCaret; 362 $.fn.caret.Utils = Utils; 363 return $.fn.caret.apis = methods; 364 }); 365 366 }).call(this); -
new file src/bp-core/js/jquery.caret.txt
diff --git src/bp-core/js/jquery.caret.txt src/bp-core/js/jquery.caret.txt new file mode 100755 index 0000000..36cd1c1
- + 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. -
src/bp-friends/bp-friends-functions.php
diff --git src/bp-friends/bp-friends-functions.php src/bp-friends/bp-friends-functions.php index 5f797c7..0e4a402 100644
function friends_remove_data( $user_id ) { 566 566 add_action( 'wpmu_delete_user', 'friends_remove_data' ); 567 567 add_action( 'delete_user', 'friends_remove_data' ); 568 568 add_action( 'bp_make_spam_user', 'friends_remove_data' ); 569 570 /** 571 * Used by the Activity component's @mentions to print a JSON list of the current user's friends. 572 * 573 * This is intended to speed up @mentions lookups for a majority of use cases. 574 * 575 * @see bp_activity_mentions_script() 576 */ 577 function bp_friends_prime_mentions_results() { 578 if ( ! bp_activity_maybe_load_mentions_scripts() ) { 579 return; 580 } 581 582 $friends_query = array( 583 'count_total' => '', // Prevents total count 584 'populate_extras' => false, 585 586 'type' => 'alphabetical', 587 'user_id' => get_current_user_id(), 588 ); 589 590 $friends_query = new BP_User_Query( $friends_query ); 591 $results = array(); 592 593 foreach ( $friends_query->results as $user ) { 594 $result = new stdClass(); 595 $result->ID = $user->user_nicename; 596 $result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) ); 597 $result->name = bp_core_get_user_displayname( $user->ID ); 598 599 $results[] = $result; 600 } 601 602 wp_localize_script( 'bp-mentions', 'BP_Suggestions', array( 603 'friends' => $results, 604 ) ); 605 } 606 add_action( 'bp_activity_mentions_prime_results', 'bp_friends_prime_mentions_results' ); 607 No newline at end of file -
src/bp-groups/bp-groups-classes.php
diff --git src/bp-groups/bp-groups-classes.php src/bp-groups/bp-groups-classes.php index 02e26be..7121ea4 100644
class BP_Groups_Member_Suggestions extends BP_Members_Suggestions { 4387 4387 'page' => 1, 4388 4388 'per_page' => $this->args['limit'], 4389 4389 'search_terms' => $this->args['term'], 4390 'search_wildcard' => 'right', 4390 4391 ); 4391 4392 4392 4393 // Only return matches of friends of this user. -
src/bp-templates/bp-legacy/buddypress/activity/entry.php
diff --git src/bp-templates/bp-legacy/buddypress/activity/entry.php src/bp-templates/bp-legacy/buddypress/activity/entry.php index 24e296b..2d759b9 100644
97 97 <div class="ac-reply-avatar"><?php bp_loggedin_user_avatar( 'width=' . BP_AVATAR_THUMB_WIDTH . '&height=' . BP_AVATAR_THUMB_HEIGHT ); ?></div> 98 98 <div class="ac-reply-content"> 99 99 <div class="ac-textarea"> 100 <textarea id="ac-input-<?php bp_activity_id(); ?>" class="ac-input " name="ac_input_<?php bp_activity_id(); ?>"></textarea>100 <textarea id="ac-input-<?php bp_activity_id(); ?>" class="ac-input bp-suggestions" name="ac_input_<?php bp_activity_id(); ?>"></textarea> 101 101 </div> 102 102 <input type="submit" name="ac_form_submit" value="<?php esc_attr_e( 'Post', 'buddypress' ); ?>" /> <a href="#" class="ac-reply-cancel"><?php _e( 'Cancel', 'buddypress' ); ?></a> 103 103 <input type="hidden" name="comment_form_id" value="<?php bp_activity_id(); ?>" /> -
src/bp-templates/bp-legacy/buddypress/activity/post-form.php
diff --git src/bp-templates/bp-legacy/buddypress/activity/post-form.php src/bp-templates/bp-legacy/buddypress/activity/post-form.php index acf7368..a45213a 100644
27 27 28 28 <div id="whats-new-content"> 29 29 <div id="whats-new-textarea"> 30 <textarea name="whats-new" id="whats-new" cols="50" rows="10"><?php if ( isset( $_GET['r'] ) ) : ?>@<?php echo esc_textarea( $_GET['r'] ); ?> <?php endif; ?></textarea>30 <textarea class="bp-suggestions" name="whats-new" id="whats-new" cols="50" rows="10"><?php if ( isset( $_GET['r'] ) ) : ?>@<?php echo esc_textarea( $_GET['r'] ); ?> <?php endif; ?></textarea> 31 31 </div> 32 32 33 33 <div id="whats-new-options"> -
src/bp-xprofile/bp-xprofile-functions.php
diff --git src/bp-xprofile/bp-xprofile-functions.php src/bp-xprofile/bp-xprofile-functions.php index 7cce675..8cedbb0 100644
function bp_xprofile_bp_user_query_search( $sql, BP_User_Query $query ) { 590 590 591 591 $bp = buddypress(); 592 592 593 $search_terms_clean = esc_sql( esc_sql( $query->query_vars['search_terms'] ));593 $search_terms_clean = bp_esc_like( $query->query_vars['search_terms'] ); 594 594 595 595 if ( $query->query_vars['search_wildcard'] === 'left' ) { 596 $search_terms_clean = '%' . $search_terms_clean; 596 $search_terms_nospace = '%' . $search_terms_clean; 597 $search_terms_space = '%' . $search_terms_clean . ' %'; 597 598 } elseif ( $query->query_vars['search_wildcard'] === 'right' ) { 598 $search_terms_clean = $search_terms_clean . '%'; 599 $search_terms_nospace = $search_terms_clean . '%'; 600 $search_terms_space = '% ' . $search_terms_clean . '%'; 599 601 } else { 600 $search_terms_clean = '%' . $search_terms_clean . '%'; 602 $search_terms_nospace = '%' . $search_terms_clean . '%'; 603 $search_terms_space = '%' . $search_terms_clean . '%'; 601 604 } 602 605 603 606 // Combine the core search (against wp_users) into a single OR clause 604 607 // with the xprofile_data search 608 $search_xprofile = $wpdb->prepare( 609 "u.{$query->uid_name} IN ( SELECT user_id FROM {$bp->profile->table_name_data} WHERE value LIKE %s OR value LIKE %s )", 610 $search_terms_nospace, 611 $search_terms_space 612 ); 613 605 614 $search_core = $sql['where']['search']; 606 $search_xprofile = "u.{$query->uid_name} IN ( SELECT user_id FROM {$bp->profile->table_name_data} WHERE value LIKE '{$search_terms_clean}' )";607 615 $search_combined = "( {$search_xprofile} OR {$search_core} )"; 608 609 616 $sql['where']['search'] = $search_combined; 610 617 611 618 return $sql;