r76327 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r76326‎ | r76327 | r76328 >
Date:19:38, 8 November 2010
Author:neilk
Status:ok
Tags:
Comment:
fixed missing dependencies on suggestions, autoEllipsis (wish this could be done more centralized...) fixes bug#25797
Modified paths:
  • /trunk/extensions/UploadWizard/UploadWizardHooks.php (modified) (history)
  • /trunk/extensions/UploadWizard/resources/combined.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/combined.min.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/dir.combined.css (modified) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/dir.combined.min.css (modified) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.autoEllipsis.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.css (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.js (added) (history)

Diff [purge]

Index: trunk/extensions/UploadWizard/UploadWizardHooks.php
@@ -28,6 +28,8 @@
2929 'resources/jquery/jquery.arrowSteps.js',
3030 'resources/jquery/jquery.autocomplete.js',
3131 'resources/jquery/jquery.spinner.js',
 32+ 'resources/jquery/jquery.autoEllipsis.js',
 33+ 'resources/jquery/jquery.suggestions.js',
3234
3335 // mediawiki-specific interface helper (relies on mediawiki globals)
3436 'resources/jquery/jquery.mwCoolCats.js',
@@ -115,6 +117,7 @@
116118 ),
117119 'styles' => array(
118120 'resources/jquery/jquery.tipsy.css',
 121+ 'resources/jquery/jquery.suggestions.css',
119122 'resources/uploadWizard.css',
120123 'resources/jquery/jquery.arrowSteps.css',
121124 'resources/jquery/jquery.mwCoolCats.css',
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.css
@@ -0,0 +1,68 @@
 2+/* suggestions plugin */
 3+
 4+.suggestions {
 5+ overflow: hidden;
 6+ position: absolute;
 7+ top: 0px;
 8+ left: 0px;
 9+ width: 0px;
 10+ border: none;
 11+ z-index: 99;
 12+ padding: 0;
 13+ margin: -1px 0 0 0;
 14+}
 15+.suggestions-special {
 16+ position: relative;
 17+ background-color: Window;
 18+ font-size: 0.8em;
 19+ cursor: pointer;
 20+ border: solid 1px #aaaaaa;
 21+ padding: 0;
 22+ margin: 0;
 23+ margin-top: -2px;
 24+ display: none;
 25+ padding: 0.25em 0.25em;
 26+ line-height: 1.25em;
 27+}
 28+.suggestions-results {
 29+ background-color: white;
 30+ background-color: Window;
 31+ font-size: 0.8em;
 32+ cursor: pointer;
 33+ border: solid 1px #aaaaaa;
 34+ padding: 0;
 35+ margin: 0;
 36+}
 37+.suggestions-result {
 38+ color: black;
 39+ color: WindowText;
 40+ margin: 0;
 41+ line-height: 1.5em;
 42+ padding: 0.01em 0.25em;
 43+}
 44+.suggestions-result-current {
 45+ background-color: #4C59A6;
 46+ background-color: Highlight;
 47+ color: white;
 48+ color: HighlightText;
 49+}
 50+.suggestions-special .special-label {
 51+ font-size: 0.8em;
 52+ color: gray;
 53+}
 54+.suggestions-special .special-query {
 55+ color: black;
 56+ font-style: italic;
 57+}
 58+.suggestions-special .special-hover {
 59+ background-color: silver;
 60+}
 61+.suggestions-result-current .special-label,
 62+.suggestions-result-current .special-query {
 63+ color: white;
 64+ color: HighlightText;
 65+}
 66+.autoellipsis-matched,
 67+.highlight {
 68+ font-weight: bold;
 69+}
\ No newline at end of file
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.css
___________________________________________________________________
Added: svn:eol-style
170 + native
Index: trunk/extensions/UploadWizard/resources/jquery/dir.combined.css
@@ -28,7 +28,74 @@
2929 /* slightly different syntax for IE8 */
3030 -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray')";
3131 }
32 -.arrowSteps {
 32+/* suggestions plugin */
 33+
 34+.suggestions {
 35+ overflow: hidden;
 36+ position: absolute;
 37+ top: 0px;
 38+ left: 0px;
 39+ width: 0px;
 40+ border: none;
 41+ z-index: 99;
 42+ padding: 0;
 43+ margin: -1px 0 0 0;
 44+}
 45+.suggestions-special {
 46+ position: relative;
 47+ background-color: Window;
 48+ font-size: 0.8em;
 49+ cursor: pointer;
 50+ border: solid 1px #aaaaaa;
 51+ padding: 0;
 52+ margin: 0;
 53+ margin-top: -2px;
 54+ display: none;
 55+ padding: 0.25em 0.25em;
 56+ line-height: 1.25em;
 57+}
 58+.suggestions-results {
 59+ background-color: white;
 60+ background-color: Window;
 61+ font-size: 0.8em;
 62+ cursor: pointer;
 63+ border: solid 1px #aaaaaa;
 64+ padding: 0;
 65+ margin: 0;
 66+}
 67+.suggestions-result {
 68+ color: black;
 69+ color: WindowText;
 70+ margin: 0;
 71+ line-height: 1.5em;
 72+ padding: 0.01em 0.25em;
 73+}
 74+.suggestions-result-current {
 75+ background-color: #4C59A6;
 76+ background-color: Highlight;
 77+ color: white;
 78+ color: HighlightText;
 79+}
 80+.suggestions-special .special-label {
 81+ font-size: 0.8em;
 82+ color: gray;
 83+}
 84+.suggestions-special .special-query {
 85+ color: black;
 86+ font-style: italic;
 87+}
 88+.suggestions-special .special-hover {
 89+ background-color: silver;
 90+}
 91+.suggestions-result-current .special-label,
 92+.suggestions-result-current .special-query {
 93+ color: white;
 94+ color: HighlightText;
 95+}
 96+.autoellipsis-matched,
 97+.highlight {
 98+ font-weight: bold;
 99+}.arrowSteps {
33100 list-style-type: none;
34101 list-style-image: none;
35102 border: 1px solid #666666;
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.js
@@ -0,0 +1,521 @@
 2+/**
 3+ * This plugin provides a generic way to add suggestions to a text box.
 4+ *
 5+ * Usage:
 6+ *
 7+ * Set options:
 8+ * $('#textbox').suggestions( { option1: value1, option2: value2 } );
 9+ * $('#textbox').suggestions( option, value );
 10+ * Get option:
 11+ * value = $('#textbox').suggestions( option );
 12+ * Initialize:
 13+ * $('#textbox').suggestions();
 14+ *
 15+ * Options:
 16+ *
 17+ * fetch(query): Callback that should fetch suggestions and set the suggestions property. Executed in the context of the
 18+ * textbox
 19+ * Type: Function
 20+ * cancel: Callback function to call when any pending asynchronous suggestions fetches should be canceled.
 21+ * Executed in the context of the textbox
 22+ * Type: Function
 23+ * special: Set of callbacks for rendering and selecting
 24+ * Type: Object of Functions 'render' and 'select'
 25+ * result: Set of callbacks for rendering and selecting
 26+ * Type: Object of Functions 'render' and 'select'
 27+ * $region: jQuery selection of element to place the suggestions below and match width of
 28+ * Type: jQuery Object, Default: $(this)
 29+ * suggestions: Suggestions to display
 30+ * Type: Array of strings
 31+ * maxRows: Maximum number of suggestions to display at one time
 32+ * Type: Number, Range: 1 - 100, Default: 7
 33+ * delay: Number of ms to wait for the user to stop typing
 34+ * Type: Number, Range: 0 - 1200, Default: 120
 35+ * submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked
 36+ * Type: Boolean, Default: false
 37+ * maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set to e.g. 2, the suggestions box
 38+ * will never be grown beyond 2 times the width of the textbox.
 39+ * Type: Number, Range: 1 - infinity, Default: 3
 40+ * positionFromLeft: Whether to position the suggestion box with the left attribute or the right
 41+ * Type: Boolean, Default: true
 42+ * highlightInput: Whether to hightlight matched portions of the input or not
 43+ * Type: Boolean, Default: false
 44+ */
 45+( function( $ ) {
 46+
 47+$.suggestions = {
 48+ /**
 49+ * Cancel any delayed updateSuggestions() call and inform the user so
 50+ * they can cancel their result fetching if they use AJAX or something
 51+ */
 52+ cancel: function( context ) {
 53+ if ( context.data.timerID != null ) {
 54+ clearTimeout( context.data.timerID );
 55+ }
 56+ if ( typeof context.config.cancel == 'function' ) {
 57+ context.config.cancel.call( context.data.$textbox );
 58+ }
 59+ },
 60+ /**
 61+ * Restore the text the user originally typed in the textbox, before it was overwritten by highlight(). This
 62+ * restores the value the currently displayed suggestions are based on, rather than the value just before
 63+ * highlight() overwrote it; the former is arguably slightly more sensible.
 64+ */
 65+ restore: function( context ) {
 66+ context.data.$textbox.val( context.data.prevText );
 67+ },
 68+ /**
 69+ * Ask the user-specified callback for new suggestions. Any previous delayed call to this function still pending
 70+ * will be canceled. If the value in the textbox hasn't changed since the last time suggestions were fetched, this
 71+ * function does nothing.
 72+ * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time
 73+ */
 74+ update: function( context, delayed ) {
 75+ // Only fetch if the value in the textbox changed
 76+ function maybeFetch() {
 77+ if ( context.data.$textbox.val() !== context.data.prevText ) {
 78+ context.data.prevText = context.data.$textbox.val();
 79+ if ( typeof context.config.fetch == 'function' ) {
 80+ context.config.fetch.call( context.data.$textbox, context.data.$textbox.val() );
 81+ }
 82+ }
 83+ }
 84+ // Cancel previous call
 85+ if ( context.data.timerID != null ) {
 86+ clearTimeout( context.data.timerID );
 87+ }
 88+ if ( delayed ) {
 89+ // Start a new asynchronous call
 90+ context.data.timerID = setTimeout( maybeFetch, context.config.delay );
 91+ } else {
 92+ maybeFetch();
 93+ }
 94+ $.suggestions.special( context );
 95+ },
 96+ special: function( context ) {
 97+ // Allow custom rendering - but otherwise don't do any rendering
 98+ if ( typeof context.config.special.render == 'function' ) {
 99+ // Wait for the browser to update the value
 100+ setTimeout( function() {
 101+ // Render special
 102+ $special = context.data.$container.find( '.suggestions-special' );
 103+ context.config.special.render.call( $special, context.data.$textbox.val() );
 104+ }, 1 );
 105+ }
 106+ },
 107+ /**
 108+ * Sets the value of a property, and updates the widget accordingly
 109+ * @param {String} property Name of property
 110+ * @param {Mixed} value Value to set property with
 111+ */
 112+ configure: function( context, property, value ) {
 113+ // Validate creation using fallback values
 114+ switch( property ) {
 115+ case 'fetch':
 116+ case 'cancel':
 117+ case 'special':
 118+ case 'result':
 119+ case '$region':
 120+ context.config[property] = value;
 121+ break;
 122+ case 'suggestions':
 123+ context.config[property] = value;
 124+ // Update suggestions
 125+ if ( typeof context.data !== 'undefined' ) {
 126+ if ( context.data.$textbox.val().length == 0 ) {
 127+ // Hide the div when no suggestion exist
 128+ context.data.$container.hide();
 129+ } else {
 130+ // Rebuild the suggestions list
 131+ context.data.$container.show();
 132+ // Update the size and position of the list
 133+ var newCSS = {
 134+ 'top': context.config.$region.offset().top + context.config.$region.outerHeight(),
 135+ 'bottom': 'auto',
 136+ 'width': context.config.$region.outerWidth(),
 137+ 'height': 'auto'
 138+ }
 139+ if ( context.config.positionFromLeft ) {
 140+ newCSS['left'] = context.config.$region.offset().left;
 141+ newCSS['right'] = 'auto';
 142+ } else {
 143+ newCSS['left'] = 'auto';
 144+ newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
 145+ }
 146+ context.data.$container.css( newCSS );
 147+ var $results = context.data.$container.children( '.suggestions-results' );
 148+ $results.empty();
 149+ var expWidth = -1;
 150+ var $autoEllipseMe = $( [] );
 151+ var matchedText = null;
 152+ for ( var i = 0; i < context.config.suggestions.length; i++ ) {
 153+ var text = context.config.suggestions[i];
 154+ var $result = $( '<div />' )
 155+ .addClass( 'suggestions-result' )
 156+ .attr( 'rel', i )
 157+ .data( 'text', context.config.suggestions[i] )
 158+ .mousemove( function( e ) {
 159+ context.data.selectedWithMouse = true;
 160+ $.suggestions.highlight(
 161+ context, $(this).closest( '.suggestions-results div' ), false
 162+ );
 163+ } )
 164+ .appendTo( $results );
 165+ // Allow custom rendering
 166+ if ( typeof context.config.result.render == 'function' ) {
 167+ context.config.result.render.call( $result, context.config.suggestions[i] );
 168+ } else {
 169+ // Add <span> with text
 170+ if( context.config.highlightInput ) {
 171+ matchedText = context.data.prevText;
 172+ }
 173+ $result.append( $( '<span />' )
 174+ .css( 'whiteSpace', 'nowrap' )
 175+ .text( text )
 176+ );
 177+
 178+ // Widen results box if needed
 179+ // New width is only calculated here, applied later
 180+ var $span = $result.children( 'span' );
 181+ if ( $span.outerWidth() > $result.width() && $span.outerWidth() > expWidth ) {
 182+ // factor in any padding, margin, or border space on the parent
 183+ expWidth = $span.outerWidth() + ( context.data.$container.width() - $span.parent().width());
 184+ }
 185+ $autoEllipseMe = $autoEllipseMe.add( $result );
 186+ }
 187+ }
 188+ // Apply new width for results box, if any
 189+ if ( expWidth > context.data.$container.width() ) {
 190+ var maxWidth = context.config.maxExpandFactor*context.data.$textbox.width();
 191+ context.data.$container.width( Math.min( expWidth, maxWidth ) );
 192+ }
 193+ // autoEllipse the results. Has to be done after changing the width
 194+ $autoEllipseMe.autoEllipsis( { hasSpan: true, tooltip: true, matchText: matchedText } );
 195+ }
 196+ }
 197+ break;
 198+ case 'maxRows':
 199+ context.config[property] = Math.max( 1, Math.min( 100, value ) );
 200+ break;
 201+ case 'delay':
 202+ context.config[property] = Math.max( 0, Math.min( 1200, value ) );
 203+ break;
 204+ case 'maxExpandFactor':
 205+ context.config[property] = Math.max( 1, value );
 206+ break;
 207+ case 'submitOnClick':
 208+ case 'positionFromLeft':
 209+ case 'highlightInput':
 210+ context.config[property] = value ? true : false;
 211+ break;
 212+ }
 213+ },
 214+ /**
 215+ * Highlight a result in the results table
 216+ * @param result <tr> to highlight: jQuery object, or 'prev' or 'next'
 217+ * @param updateTextbox If true, put the suggestion in the textbox
 218+ */
 219+ highlight: function( context, result, updateTextbox ) {
 220+ var selected = context.data.$container.find( '.suggestions-result-current' );
 221+ if ( !result.get || selected.get( 0 ) != result.get( 0 ) ) {
 222+ if ( result == 'prev' ) {
 223+ if( selected.is( '.suggestions-special' ) ) {
 224+ result = context.data.$container.find( '.suggestions-result:last' )
 225+ } else {
 226+ result = selected.prev();
 227+ if ( selected.length == 0 ) {
 228+ // we are at the begginning, so lets jump to the last item
 229+ if ( context.data.$container.find( '.suggestions-special' ).html() != "" ) {
 230+ result = context.data.$container.find( '.suggestions-special' );
 231+ } else {
 232+ result = context.data.$container.find( '.suggestions-results div:last' );
 233+ }
 234+ }
 235+ }
 236+ } else if ( result == 'next' ) {
 237+ if ( selected.length == 0 ) {
 238+ // No item selected, go to the first one
 239+ result = context.data.$container.find( '.suggestions-results div:first' );
 240+ if ( result.length == 0 && context.data.$container.find( '.suggestions-special' ).html() != "" ) {
 241+ // No suggestion exists, go to the special one directly
 242+ result = context.data.$container.find( '.suggestions-special' );
 243+ }
 244+ } else {
 245+ result = selected.next();
 246+ if ( selected.is( '.suggestions-special' ) ) {
 247+ result = $( [] );
 248+ } else if (
 249+ result.length == 0 &&
 250+ context.data.$container.find( '.suggestions-special' ).html() != ""
 251+ ) {
 252+ // We were at the last item, jump to the specials!
 253+ result = context.data.$container.find( '.suggestions-special' );
 254+ }
 255+ }
 256+ }
 257+ selected.removeClass( 'suggestions-result-current' );
 258+ result.addClass( 'suggestions-result-current' );
 259+ }
 260+ if ( updateTextbox ) {
 261+ if ( result.length == 0 || result.is( '.suggestions-special' ) ) {
 262+ $.suggestions.restore( context );
 263+ } else {
 264+ context.data.$textbox.val( result.data( 'text' ) );
 265+ // .val() doesn't call any event handlers, so
 266+ // let the world know what happened
 267+ context.data.$textbox.change();
 268+ }
 269+ context.data.$textbox.trigger( 'change' );
 270+ }
 271+ },
 272+ /**
 273+ * Respond to keypress event
 274+ * @param {Integer} key Code of key pressed
 275+ */
 276+ keypress: function( e, context, key ) {
 277+ var wasVisible = context.data.$container.is( ':visible' );
 278+ var preventDefault = false;
 279+ switch ( key ) {
 280+ // Arrow down
 281+ case 40:
 282+ if ( wasVisible ) {
 283+ $.suggestions.highlight( context, 'next', true );
 284+ context.data.selectedWithMouse = false;
 285+ } else {
 286+ $.suggestions.update( context, false );
 287+ }
 288+ preventDefault = true;
 289+ break;
 290+ // Arrow up
 291+ case 38:
 292+ if ( wasVisible ) {
 293+ $.suggestions.highlight( context, 'prev', true );
 294+ context.data.selectedWithMouse = false;
 295+ }
 296+ preventDefault = wasVisible;
 297+ break;
 298+ // Escape
 299+ case 27:
 300+ context.data.$container.hide();
 301+ $.suggestions.restore( context );
 302+ $.suggestions.cancel( context );
 303+ context.data.$textbox.trigger( 'change' );
 304+ preventDefault = wasVisible;
 305+ break;
 306+ // Enter
 307+ case 13:
 308+ context.data.$container.hide();
 309+ preventDefault = wasVisible;
 310+ selected = context.data.$container.find( '.suggestions-result-current' );
 311+ if ( selected.size() == 0 || context.data.selectedWithMouse ) {
 312+ // if nothing is selected OR if something was selected with the mouse,
 313+ // cancel any current requests and submit the form
 314+ $.suggestions.cancel( context );
 315+ context.config.$region.closest( 'form' ).submit();
 316+ } else if ( selected.is( '.suggestions-special' ) ) {
 317+ if ( typeof context.config.special.select == 'function' ) {
 318+ context.config.special.select.call( selected, context.data.$textbox );
 319+ }
 320+ } else {
 321+ if ( typeof context.config.result.select == 'function' ) {
 322+ $.suggestions.highlight( context, selected, true );
 323+ context.config.result.select.call( selected, context.data.$textbox );
 324+ } else {
 325+ $.suggestions.highlight( context, selected, true );
 326+ }
 327+ }
 328+ break;
 329+ default:
 330+ $.suggestions.update( context, true );
 331+ break;
 332+ }
 333+ if ( preventDefault ) {
 334+ e.preventDefault();
 335+ e.stopImmediatePropagation();
 336+ }
 337+ }
 338+};
 339+$.fn.suggestions = function() {
 340+
 341+ // Multi-context fields
 342+ var returnValue = null;
 343+ var args = arguments;
 344+
 345+ $(this).each( function() {
 346+
 347+ /* Construction / Loading */
 348+
 349+ var context = $(this).data( 'suggestions-context' );
 350+ if ( typeof context == 'undefined' || context == null ) {
 351+ context = {
 352+ config: {
 353+ 'fetch' : function() {},
 354+ 'cancel': function() {},
 355+ 'special': {},
 356+ 'result': {},
 357+ '$region': $(this),
 358+ 'suggestions': [],
 359+ 'maxRows': 7,
 360+ 'delay': 120,
 361+ 'submitOnClick': false,
 362+ 'maxExpandFactor': 3,
 363+ 'positionFromLeft': true,
 364+ 'highlightInput': false
 365+ }
 366+ };
 367+ }
 368+
 369+ /* API */
 370+
 371+ // Handle various calling styles
 372+ if ( args.length > 0 ) {
 373+ if ( typeof args[0] == 'object' ) {
 374+ // Apply set of properties
 375+ for ( var key in args[0] ) {
 376+ $.suggestions.configure( context, key, args[0][key] );
 377+ }
 378+ } else if ( typeof args[0] == 'string' ) {
 379+ if ( args.length > 1 ) {
 380+ // Set property values
 381+ $.suggestions.configure( context, args[0], args[1] );
 382+ } else if ( returnValue == null ) {
 383+ // Get property values, but don't give access to internal data - returns only the first
 384+ returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] );
 385+ }
 386+ }
 387+ }
 388+
 389+ /* Initialization */
 390+
 391+ if ( typeof context.data == 'undefined' ) {
 392+ context.data = {
 393+ // ID of running timer
 394+ 'timerID': null,
 395+ // Text in textbox when suggestions were last fetched
 396+ 'prevText': null,
 397+ // Number of results visible without scrolling
 398+ 'visibleResults': 0,
 399+ // Suggestion the last mousedown event occured on
 400+ 'mouseDownOn': $( [] ),
 401+ '$textbox': $(this),
 402+ 'selectedWithMouse': false
 403+ };
 404+ // Setup the css for positioning the results box
 405+ var newCSS = {
 406+ 'top': Math.round( context.data.$textbox.offset().top + context.data.$textbox.outerHeight() ),
 407+ 'width': context.data.$textbox.outerWidth(),
 408+ 'display': 'none'
 409+ }
 410+ if ( context.config.positionFromLeft ) {
 411+ newCSS['left'] = context.config.$region.offset().left;
 412+ newCSS['right'] = 'auto';
 413+ } else {
 414+ newCSS['left'] = 'auto';
 415+ newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
 416+ }
 417+
 418+ context.data.$container = $( '<div />' )
 419+ .css( newCSS )
 420+ .addClass( 'suggestions' )
 421+ .append(
 422+ $( '<div />' ).addClass( 'suggestions-results' )
 423+ // Can't use click() because the container div is hidden when the textbox loses focus. Instead,
 424+ // listen for a mousedown followed by a mouseup on the same div
 425+ .mousedown( function( e ) {
 426+ context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' );
 427+ } )
 428+ .mouseup( function( e ) {
 429+ var $result = $( e.target ).closest( '.suggestions-results div' );
 430+ var $other = context.data.mouseDownOn;
 431+ context.data.mouseDownOn = $( [] );
 432+ if ( $result.get( 0 ) != $other.get( 0 ) ) {
 433+ return;
 434+ }
 435+ $.suggestions.highlight( context, $result, true );
 436+ context.data.$container.hide();
 437+ if ( typeof context.config.result.select == 'function' ) {
 438+ context.config.result.select.call( $result, context.data.$textbox );
 439+ }
 440+ context.data.$textbox.focus();
 441+ } )
 442+ )
 443+ .append(
 444+ $( '<div />' ).addClass( 'suggestions-special' )
 445+ // Can't use click() because the container div is hidden when the textbox loses focus. Instead,
 446+ // listen for a mousedown followed by a mouseup on the same div
 447+ .mousedown( function( e ) {
 448+ context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' );
 449+ } )
 450+ .mouseup( function( e ) {
 451+ var $special = $( e.target ).closest( '.suggestions-special' );
 452+ var $other = context.data.mouseDownOn;
 453+ context.data.mouseDownOn = $( [] );
 454+ if ( $special.get( 0 ) != $other.get( 0 ) ) {
 455+ return;
 456+ }
 457+ context.data.$container.hide();
 458+ if ( typeof context.config.special.select == 'function' ) {
 459+ context.config.special.select.call( $special, context.data.$textbox );
 460+ }
 461+ context.data.$textbox.focus();
 462+ } )
 463+ .mousemove( function( e ) {
 464+ context.data.selectedWithMouse = true;
 465+ $.suggestions.highlight(
 466+ context, $( e.target ).closest( '.suggestions-special' ), false
 467+ );
 468+ } )
 469+ )
 470+ .appendTo( $( 'body' ) );
 471+ $(this)
 472+ // Stop browser autocomplete from interfering
 473+ .attr( 'autocomplete', 'off')
 474+ .keydown( function( e ) {
 475+ // Store key pressed to handle later
 476+ context.data.keypressed = ( e.keyCode == undefined ) ? e.which : e.keyCode;
 477+ context.data.keypressedCount = 0;
 478+
 479+ switch ( context.data.keypressed ) {
 480+ // This preventDefault logic is duplicated from
 481+ // $.suggestions.keypress(), which sucks
 482+ case 40:
 483+ e.preventDefault();
 484+ e.stopImmediatePropagation();
 485+ break;
 486+ case 38:
 487+ case 27:
 488+ case 13:
 489+ if ( context.data.$container.is( ':visible' ) ) {
 490+ e.preventDefault();
 491+ e.stopImmediatePropagation();
 492+ }
 493+ }
 494+ } )
 495+ .keypress( function( e ) {
 496+ context.data.keypressedCount++;
 497+ $.suggestions.keypress( e, context, context.data.keypressed );
 498+ } )
 499+ .keyup( function( e ) {
 500+ // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a
 501+ // keypress in between, solve it
 502+ if ( context.data.keypressedCount == 0 ) {
 503+ $.suggestions.keypress( e, context, context.data.keypressed );
 504+ }
 505+ } )
 506+ .blur( function() {
 507+ // When losing focus because of a mousedown
 508+ // on a suggestion, don't hide the suggestions
 509+ if ( context.data.mouseDownOn.length > 0 ) {
 510+ return;
 511+ }
 512+ context.data.$container.hide();
 513+ $.suggestions.cancel( context );
 514+ } );
 515+ }
 516+ // Store the context for next time
 517+ $(this).data( 'suggestions-context', context );
 518+ } );
 519+ return returnValue !== null ? returnValue : $(this);
 520+};
 521+
 522+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.js
___________________________________________________________________
Added: svn:eol-style
1523 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.autoEllipsis.js
@@ -0,0 +1,131 @@
 2+/**
 3+ * Plugin that automatically truncates the plain text contents of an element and adds an ellipsis
 4+ */
 5+( function( $ ) {
 6+
 7+// Cache ellipsed substrings for every string-width combination
 8+var cache = { };
 9+// Use a seperate cache when match highlighting is enabled
 10+var matchTextCache = { };
 11+
 12+$.fn.autoEllipsis = function( options ) {
 13+ options = $.extend( {
 14+ 'position': 'center',
 15+ 'tooltip': false,
 16+ 'restoreText': false,
 17+ 'hasSpan': false,
 18+ 'matchText': null
 19+ }, options );
 20+ $(this).each( function() {
 21+ var $this = $(this);
 22+ if ( options.restoreText ) {
 23+ if ( ! $this.data( 'autoEllipsis.originalText' ) ) {
 24+ $this.data( 'autoEllipsis.originalText', $this.text() );
 25+ } else {
 26+ $this.text( $this.data( 'autoEllipsis.originalText' ) );
 27+ }
 28+ }
 29+
 30+ // container element - used for measuring against
 31+ var $container = $this;
 32+ // trimmable text element - only the text within this element will be trimmed
 33+ var $trimmableText = null;
 34+ // protected text element - the width of this element is counted, but next is never trimmed from it
 35+ var $protectedText = null;
 36+
 37+ if ( options.hasSpan ) {
 38+ $trimmableText = $this.children( options.selector );
 39+ } else {
 40+ $trimmableText = $( '<span />' )
 41+ .css( 'whiteSpace', 'nowrap' )
 42+ .text( $this.text() );
 43+ $this
 44+ .empty()
 45+ .append( $trimmableText );
 46+ }
 47+
 48+ var text = $container.text();
 49+ var trimmableText = $trimmableText.text();
 50+ var w = $container.width();
 51+ var pw = $protectedText ? $protectedText.width() : 0;
 52+ // Try cache
 53+ if ( !( text in cache ) ) {
 54+ cache[text] = {};
 55+ }
 56+ if ( options.matchText && !( text in matchTextCache ) ) {
 57+ matchTextCache[text] = {};
 58+ }
 59+ if ( options.matchText && !( options.matchText in matchTextCache[text] ) ) {
 60+ matchTextCache[text][options.matchText] = {};
 61+ }
 62+ if ( !options.matchText && w in cache[text] ) {
 63+ $container.html( cache[text][w] );
 64+ if ( options.tooltip )
 65+ $container.attr( 'title', text );
 66+ return;
 67+ }
 68+ if( options.matchText && options.matchText in matchTextCache[text] && w in matchTextCache[text][options.matchText] ) {
 69+ $container.html( matchTextCache[text][options.matchText][w] );
 70+ if ( options.tooltip )
 71+ $container.attr( 'title', text );
 72+ return;
 73+ }
 74+ if ( $trimmableText.width() + pw > w ) {
 75+ switch ( options.position ) {
 76+ case 'right':
 77+ // Use binary search-like technique for efficiency
 78+ var l = 0, r = trimmableText.length;
 79+ do {
 80+ var m = Math.ceil( ( l + r ) / 2 );
 81+ $trimmableText.text( trimmableText.substr( 0, m ) + '...' );
 82+ if ( $trimmableText.width() + pw > w ) {
 83+ // Text is too long
 84+ r = m - 1;
 85+ } else {
 86+ l = m;
 87+ }
 88+ } while ( l < r );
 89+ $trimmableText.text( trimmableText.substr( 0, l ) + '...' );
 90+ break;
 91+ case 'center':
 92+ // TODO: Use binary search like for 'right'
 93+ var i = [Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 )];
 94+ var side = 1; // Begin with making the end shorter
 95+ while ( $trimmableText.outerWidth() + pw > w && i[0] > 0 ) {
 96+ $trimmableText.text( trimmableText.substr( 0, i[0] ) + '...' + trimmableText.substr( i[1] ) );
 97+ // Alternate between trimming the end and begining
 98+ if ( side == 0 ) {
 99+ // Make the begining shorter
 100+ i[0]--;
 101+ side = 1;
 102+ } else {
 103+ // Make the end shorter
 104+ i[1]++;
 105+ side = 0;
 106+ }
 107+ }
 108+ break;
 109+ case 'left':
 110+ // TODO: Use binary search like for 'right'
 111+ var r = 0;
 112+ while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) {
 113+ $trimmableText.text( '...' + trimmableText.substr( r ) );
 114+ r++;
 115+ }
 116+ break;
 117+ }
 118+ }
 119+ if ( options.tooltip ) {
 120+ $container.attr( 'title', text );
 121+ }
 122+ if ( options.matchText ) {
 123+ $container.highlightText( options.matchText );
 124+ matchTextCache[text][options.matchText][w] = $container.html();
 125+ } else {
 126+ cache[text][w] = $container.html();
 127+ }
 128+
 129+ } );
 130+};
 131+
 132+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.autoEllipsis.js
___________________________________________________________________
Added: svn:eol-style
1133 + native
Index: trunk/extensions/UploadWizard/resources/jquery/dir.combined.min.css
@@ -15,7 +15,16 @@
1616 -webkit-box-shadow:5px 5px 5px rgba(0,0,0,0.2);-moz-box-shadow:5px 5px 5px rgba(0,0,0,0.2);box-shadow:5px 5px 5px rgba(0,0,0,0.2);
1717 filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5,OffY=5,Color='gray');
1818 -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5,OffY=5,Color='gray')";}
19 -.arrowSteps{list-style-type:none;list-style-image:none;border:1px solid #666666;position:relative;}.arrowSteps li{float:left;padding:0px;margin:0px;border:0 none;}.arrowSteps li div{padding:0.5em;text-align:center;white-space:nowrap;overflow:hidden;}.arrowSteps li.arrow div{background:url(jquery.arrowSteps.divider.png) no-repeat right center;}
 19+.suggestions{overflow:hidden;position:absolute;top:0px;left:0px;width:0px;border:none;z-index:99;padding:0;margin:-1px 0 0 0;}
 20+.suggestions-special{position:relative;background-color:Window;font-size:0.8em;cursor:pointer;border:solid 1px #aaaaaa;padding:0;margin:0;margin-top:-2px;display:none;padding:0.25em 0.25em;line-height:1.25em;}
 21+.suggestions-results{background-color:white;background-color:Window;font-size:0.8em;cursor:pointer;border:solid 1px #aaaaaa;padding:0;margin:0;}
 22+.suggestions-result{color:black;color:WindowText;margin:0;line-height:1.5em;padding:0.01em 0.25em;}
 23+.suggestions-result-current{background-color:#4C59A6;background-color:Highlight;color:white;color:HighlightText;}
 24+.suggestions-special .special-label{font-size:0.8em;color:gray;}
 25+.suggestions-special .special-query{color:black;font-style:italic;}
 26+.suggestions-special .special-hover{background-color:silver;}
 27+.suggestions-result-current .special-label,.suggestions-result-current .special-query{color:white;color:HighlightText;}
 28+.autoellipsis-matched,.highlight{font-weight:bold;}.arrowSteps{list-style-type:none;list-style-image:none;border:1px solid #666666;position:relative;}.arrowSteps li{float:left;padding:0px;margin:0px;border:0 none;}.arrowSteps li div{padding:0.5em;text-align:center;white-space:nowrap;overflow:hidden;}.arrowSteps li.arrow div{background:url(jquery.arrowSteps.divider.png) no-repeat right center;}
2029 .arrowSteps li.arrow.tail div{background:url(jquery.arrowSteps.tail.png) no-repeat right center;}
2130 .arrowSteps li.head div{background:url(jquery.arrowSteps.head.png) no-repeat left center;font-weight:bold;}
2231 .arrowSteps li.arrow.head div{background:url(jquery.arrowSteps.head.png) no-repeat right center;}
Index: trunk/extensions/UploadWizard/resources/combined.js
@@ -4412,6 +4412,527 @@
44134413 return $spinner;
44144414 }
44154415 } )( jQuery );
 4416+/**
 4417+ * This plugin provides a generic way to add suggestions to a text box.
 4418+ *
 4419+ * Usage:
 4420+ *
 4421+ * Set options:
 4422+ * $('#textbox').suggestions( { option1: value1, option2: value2 } );
 4423+ * $('#textbox').suggestions( option, value );
 4424+ * Get option:
 4425+ * value = $('#textbox').suggestions( option );
 4426+ * Initialize:
 4427+ * $('#textbox').suggestions();
 4428+ *
 4429+ * Options:
 4430+ *
 4431+ * fetch(query): Callback that should fetch suggestions and set the suggestions property. Executed in the context of the
 4432+ * textbox
 4433+ * Type: Function
 4434+ * cancel: Callback function to call when any pending asynchronous suggestions fetches should be canceled.
 4435+ * Executed in the context of the textbox
 4436+ * Type: Function
 4437+ * special: Set of callbacks for rendering and selecting
 4438+ * Type: Object of Functions 'render' and 'select'
 4439+ * result: Set of callbacks for rendering and selecting
 4440+ * Type: Object of Functions 'render' and 'select'
 4441+ * $region: jQuery selection of element to place the suggestions below and match width of
 4442+ * Type: jQuery Object, Default: $(this)
 4443+ * suggestions: Suggestions to display
 4444+ * Type: Array of strings
 4445+ * maxRows: Maximum number of suggestions to display at one time
 4446+ * Type: Number, Range: 1 - 100, Default: 7
 4447+ * delay: Number of ms to wait for the user to stop typing
 4448+ * Type: Number, Range: 0 - 1200, Default: 120
 4449+ * submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked
 4450+ * Type: Boolean, Default: false
 4451+ * maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set to e.g. 2, the suggestions box
 4452+ * will never be grown beyond 2 times the width of the textbox.
 4453+ * Type: Number, Range: 1 - infinity, Default: 3
 4454+ * positionFromLeft: Whether to position the suggestion box with the left attribute or the right
 4455+ * Type: Boolean, Default: true
 4456+ * highlightInput: Whether to hightlight matched portions of the input or not
 4457+ * Type: Boolean, Default: false
 4458+ */
 4459+( function( $ ) {
 4460+
 4461+$.suggestions = {
 4462+ /**
 4463+ * Cancel any delayed updateSuggestions() call and inform the user so
 4464+ * they can cancel their result fetching if they use AJAX or something
 4465+ */
 4466+ cancel: function( context ) {
 4467+ if ( context.data.timerID != null ) {
 4468+ clearTimeout( context.data.timerID );
 4469+ }
 4470+ if ( typeof context.config.cancel == 'function' ) {
 4471+ context.config.cancel.call( context.data.$textbox );
 4472+ }
 4473+ },
 4474+ /**
 4475+ * Restore the text the user originally typed in the textbox, before it was overwritten by highlight(). This
 4476+ * restores the value the currently displayed suggestions are based on, rather than the value just before
 4477+ * highlight() overwrote it; the former is arguably slightly more sensible.
 4478+ */
 4479+ restore: function( context ) {
 4480+ context.data.$textbox.val( context.data.prevText );
 4481+ },
 4482+ /**
 4483+ * Ask the user-specified callback for new suggestions. Any previous delayed call to this function still pending
 4484+ * will be canceled. If the value in the textbox hasn't changed since the last time suggestions were fetched, this
 4485+ * function does nothing.
 4486+ * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time
 4487+ */
 4488+ update: function( context, delayed ) {
 4489+ // Only fetch if the value in the textbox changed
 4490+ function maybeFetch() {
 4491+ if ( context.data.$textbox.val() !== context.data.prevText ) {
 4492+ context.data.prevText = context.data.$textbox.val();
 4493+ if ( typeof context.config.fetch == 'function' ) {
 4494+ context.config.fetch.call( context.data.$textbox, context.data.$textbox.val() );
 4495+ }
 4496+ }
 4497+ }
 4498+ // Cancel previous call
 4499+ if ( context.data.timerID != null ) {
 4500+ clearTimeout( context.data.timerID );
 4501+ }
 4502+ if ( delayed ) {
 4503+ // Start a new asynchronous call
 4504+ context.data.timerID = setTimeout( maybeFetch, context.config.delay );
 4505+ } else {
 4506+ maybeFetch();
 4507+ }
 4508+ $.suggestions.special( context );
 4509+ },
 4510+ special: function( context ) {
 4511+ // Allow custom rendering - but otherwise don't do any rendering
 4512+ if ( typeof context.config.special.render == 'function' ) {
 4513+ // Wait for the browser to update the value
 4514+ setTimeout( function() {
 4515+ // Render special
 4516+ $special = context.data.$container.find( '.suggestions-special' );
 4517+ context.config.special.render.call( $special, context.data.$textbox.val() );
 4518+ }, 1 );
 4519+ }
 4520+ },
 4521+ /**
 4522+ * Sets the value of a property, and updates the widget accordingly
 4523+ * @param {String} property Name of property
 4524+ * @param {Mixed} value Value to set property with
 4525+ */
 4526+ configure: function( context, property, value ) {
 4527+ // Validate creation using fallback values
 4528+ switch( property ) {
 4529+ case 'fetch':
 4530+ case 'cancel':
 4531+ case 'special':
 4532+ case 'result':
 4533+ case '$region':
 4534+ context.config[property] = value;
 4535+ break;
 4536+ case 'suggestions':
 4537+ context.config[property] = value;
 4538+ // Update suggestions
 4539+ if ( typeof context.data !== 'undefined' ) {
 4540+ if ( context.data.$textbox.val().length == 0 ) {
 4541+ // Hide the div when no suggestion exist
 4542+ context.data.$container.hide();
 4543+ } else {
 4544+ // Rebuild the suggestions list
 4545+ context.data.$container.show();
 4546+ // Update the size and position of the list
 4547+ var newCSS = {
 4548+ 'top': context.config.$region.offset().top + context.config.$region.outerHeight(),
 4549+ 'bottom': 'auto',
 4550+ 'width': context.config.$region.outerWidth(),
 4551+ 'height': 'auto'
 4552+ }
 4553+ if ( context.config.positionFromLeft ) {
 4554+ newCSS['left'] = context.config.$region.offset().left;
 4555+ newCSS['right'] = 'auto';
 4556+ } else {
 4557+ newCSS['left'] = 'auto';
 4558+ newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
 4559+ }
 4560+ context.data.$container.css( newCSS );
 4561+ var $results = context.data.$container.children( '.suggestions-results' );
 4562+ $results.empty();
 4563+ var expWidth = -1;
 4564+ var $autoEllipseMe = $( [] );
 4565+ var matchedText = null;
 4566+ for ( var i = 0; i < context.config.suggestions.length; i++ ) {
 4567+ var text = context.config.suggestions[i];
 4568+ var $result = $( '<div />' )
 4569+ .addClass( 'suggestions-result' )
 4570+ .attr( 'rel', i )
 4571+ .data( 'text', context.config.suggestions[i] )
 4572+ .mousemove( function( e ) {
 4573+ context.data.selectedWithMouse = true;
 4574+ $.suggestions.highlight(
 4575+ context, $(this).closest( '.suggestions-results div' ), false
 4576+ );
 4577+ } )
 4578+ .appendTo( $results );
 4579+ // Allow custom rendering
 4580+ if ( typeof context.config.result.render == 'function' ) {
 4581+ context.config.result.render.call( $result, context.config.suggestions[i] );
 4582+ } else {
 4583+ // Add <span> with text
 4584+ if( context.config.highlightInput ) {
 4585+ matchedText = context.data.prevText;
 4586+ }
 4587+ $result.append( $( '<span />' )
 4588+ .css( 'whiteSpace', 'nowrap' )
 4589+ .text( text )
 4590+ );
 4591+
 4592+ // Widen results box if needed
 4593+ // New width is only calculated here, applied later
 4594+ var $span = $result.children( 'span' );
 4595+ if ( $span.outerWidth() > $result.width() && $span.outerWidth() > expWidth ) {
 4596+ // factor in any padding, margin, or border space on the parent
 4597+ expWidth = $span.outerWidth() + ( context.data.$container.width() - $span.parent().width());
 4598+ }
 4599+ $autoEllipseMe = $autoEllipseMe.add( $result );
 4600+ }
 4601+ }
 4602+ // Apply new width for results box, if any
 4603+ if ( expWidth > context.data.$container.width() ) {
 4604+ var maxWidth = context.config.maxExpandFactor*context.data.$textbox.width();
 4605+ context.data.$container.width( Math.min( expWidth, maxWidth ) );
 4606+ }
 4607+ // autoEllipse the results. Has to be done after changing the width
 4608+ $autoEllipseMe.autoEllipsis( { hasSpan: true, tooltip: true, matchText: matchedText } );
 4609+ }
 4610+ }
 4611+ break;
 4612+ case 'maxRows':
 4613+ context.config[property] = Math.max( 1, Math.min( 100, value ) );
 4614+ break;
 4615+ case 'delay':
 4616+ context.config[property] = Math.max( 0, Math.min( 1200, value ) );
 4617+ break;
 4618+ case 'maxExpandFactor':
 4619+ context.config[property] = Math.max( 1, value );
 4620+ break;
 4621+ case 'submitOnClick':
 4622+ case 'positionFromLeft':
 4623+ case 'highlightInput':
 4624+ context.config[property] = value ? true : false;
 4625+ break;
 4626+ }
 4627+ },
 4628+ /**
 4629+ * Highlight a result in the results table
 4630+ * @param result <tr> to highlight: jQuery object, or 'prev' or 'next'
 4631+ * @param updateTextbox If true, put the suggestion in the textbox
 4632+ */
 4633+ highlight: function( context, result, updateTextbox ) {
 4634+ var selected = context.data.$container.find( '.suggestions-result-current' );
 4635+ if ( !result.get || selected.get( 0 ) != result.get( 0 ) ) {
 4636+ if ( result == 'prev' ) {
 4637+ if( selected.is( '.suggestions-special' ) ) {
 4638+ result = context.data.$container.find( '.suggestions-result:last' )
 4639+ } else {
 4640+ result = selected.prev();
 4641+ if ( selected.length == 0 ) {
 4642+ // we are at the begginning, so lets jump to the last item
 4643+ if ( context.data.$container.find( '.suggestions-special' ).html() != "" ) {
 4644+ result = context.data.$container.find( '.suggestions-special' );
 4645+ } else {
 4646+ result = context.data.$container.find( '.suggestions-results div:last' );
 4647+ }
 4648+ }
 4649+ }
 4650+ } else if ( result == 'next' ) {
 4651+ if ( selected.length == 0 ) {
 4652+ // No item selected, go to the first one
 4653+ result = context.data.$container.find( '.suggestions-results div:first' );
 4654+ if ( result.length == 0 && context.data.$container.find( '.suggestions-special' ).html() != "" ) {
 4655+ // No suggestion exists, go to the special one directly
 4656+ result = context.data.$container.find( '.suggestions-special' );
 4657+ }
 4658+ } else {
 4659+ result = selected.next();
 4660+ if ( selected.is( '.suggestions-special' ) ) {
 4661+ result = $( [] );
 4662+ } else if (
 4663+ result.length == 0 &&
 4664+ context.data.$container.find( '.suggestions-special' ).html() != ""
 4665+ ) {
 4666+ // We were at the last item, jump to the specials!
 4667+ result = context.data.$container.find( '.suggestions-special' );
 4668+ }
 4669+ }
 4670+ }
 4671+ selected.removeClass( 'suggestions-result-current' );
 4672+ result.addClass( 'suggestions-result-current' );
 4673+ }
 4674+ if ( updateTextbox ) {
 4675+ if ( result.length == 0 || result.is( '.suggestions-special' ) ) {
 4676+ $.suggestions.restore( context );
 4677+ } else {
 4678+ context.data.$textbox.val( result.data( 'text' ) );
 4679+ // .val() doesn't call any event handlers, so
 4680+ // let the world know what happened
 4681+ context.data.$textbox.change();
 4682+ }
 4683+ context.data.$textbox.trigger( 'change' );
 4684+ }
 4685+ },
 4686+ /**
 4687+ * Respond to keypress event
 4688+ * @param {Integer} key Code of key pressed
 4689+ */
 4690+ keypress: function( e, context, key ) {
 4691+ var wasVisible = context.data.$container.is( ':visible' );
 4692+ var preventDefault = false;
 4693+ switch ( key ) {
 4694+ // Arrow down
 4695+ case 40:
 4696+ if ( wasVisible ) {
 4697+ $.suggestions.highlight( context, 'next', true );
 4698+ context.data.selectedWithMouse = false;
 4699+ } else {
 4700+ $.suggestions.update( context, false );
 4701+ }
 4702+ preventDefault = true;
 4703+ break;
 4704+ // Arrow up
 4705+ case 38:
 4706+ if ( wasVisible ) {
 4707+ $.suggestions.highlight( context, 'prev', true );
 4708+ context.data.selectedWithMouse = false;
 4709+ }
 4710+ preventDefault = wasVisible;
 4711+ break;
 4712+ // Escape
 4713+ case 27:
 4714+ context.data.$container.hide();
 4715+ $.suggestions.restore( context );
 4716+ $.suggestions.cancel( context );
 4717+ context.data.$textbox.trigger( 'change' );
 4718+ preventDefault = wasVisible;
 4719+ break;
 4720+ // Enter
 4721+ case 13:
 4722+ context.data.$container.hide();
 4723+ preventDefault = wasVisible;
 4724+ selected = context.data.$container.find( '.suggestions-result-current' );
 4725+ if ( selected.size() == 0 || context.data.selectedWithMouse ) {
 4726+ // if nothing is selected OR if something was selected with the mouse,
 4727+ // cancel any current requests and submit the form
 4728+ $.suggestions.cancel( context );
 4729+ context.config.$region.closest( 'form' ).submit();
 4730+ } else if ( selected.is( '.suggestions-special' ) ) {
 4731+ if ( typeof context.config.special.select == 'function' ) {
 4732+ context.config.special.select.call( selected, context.data.$textbox );
 4733+ }
 4734+ } else {
 4735+ if ( typeof context.config.result.select == 'function' ) {
 4736+ $.suggestions.highlight( context, selected, true );
 4737+ context.config.result.select.call( selected, context.data.$textbox );
 4738+ } else {
 4739+ $.suggestions.highlight( context, selected, true );
 4740+ }
 4741+ }
 4742+ break;
 4743+ default:
 4744+ $.suggestions.update( context, true );
 4745+ break;
 4746+ }
 4747+ if ( preventDefault ) {
 4748+ e.preventDefault();
 4749+ e.stopImmediatePropagation();
 4750+ }
 4751+ }
 4752+};
 4753+$.fn.suggestions = function() {
 4754+
 4755+ // Multi-context fields
 4756+ var returnValue = null;
 4757+ var args = arguments;
 4758+
 4759+ $(this).each( function() {
 4760+
 4761+ /* Construction / Loading */
 4762+
 4763+ var context = $(this).data( 'suggestions-context' );
 4764+ if ( typeof context == 'undefined' || context == null ) {
 4765+ context = {
 4766+ config: {
 4767+ 'fetch' : function() {},
 4768+ 'cancel': function() {},
 4769+ 'special': {},
 4770+ 'result': {},
 4771+ '$region': $(this),
 4772+ 'suggestions': [],
 4773+ 'maxRows': 7,
 4774+ 'delay': 120,
 4775+ 'submitOnClick': false,
 4776+ 'maxExpandFactor': 3,
 4777+ 'positionFromLeft': true,
 4778+ 'highlightInput': false
 4779+ }
 4780+ };
 4781+ }
 4782+
 4783+ /* API */
 4784+
 4785+ // Handle various calling styles
 4786+ if ( args.length > 0 ) {
 4787+ if ( typeof args[0] == 'object' ) {
 4788+ // Apply set of properties
 4789+ for ( var key in args[0] ) {
 4790+ $.suggestions.configure( context, key, args[0][key] );
 4791+ }
 4792+ } else if ( typeof args[0] == 'string' ) {
 4793+ if ( args.length > 1 ) {
 4794+ // Set property values
 4795+ $.suggestions.configure( context, args[0], args[1] );
 4796+ } else if ( returnValue == null ) {
 4797+ // Get property values, but don't give access to internal data - returns only the first
 4798+ returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] );
 4799+ }
 4800+ }
 4801+ }
 4802+
 4803+ /* Initialization */
 4804+
 4805+ if ( typeof context.data == 'undefined' ) {
 4806+ context.data = {
 4807+ // ID of running timer
 4808+ 'timerID': null,
 4809+ // Text in textbox when suggestions were last fetched
 4810+ 'prevText': null,
 4811+ // Number of results visible without scrolling
 4812+ 'visibleResults': 0,
 4813+ // Suggestion the last mousedown event occured on
 4814+ 'mouseDownOn': $( [] ),
 4815+ '$textbox': $(this),
 4816+ 'selectedWithMouse': false
 4817+ };
 4818+ // Setup the css for positioning the results box
 4819+ var newCSS = {
 4820+ 'top': Math.round( context.data.$textbox.offset().top + context.data.$textbox.outerHeight() ),
 4821+ 'width': context.data.$textbox.outerWidth(),
 4822+ 'display': 'none'
 4823+ }
 4824+ if ( context.config.positionFromLeft ) {
 4825+ newCSS['left'] = context.config.$region.offset().left;
 4826+ newCSS['right'] = 'auto';
 4827+ } else {
 4828+ newCSS['left'] = 'auto';
 4829+ newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
 4830+ }
 4831+
 4832+ context.data.$container = $( '<div />' )
 4833+ .css( newCSS )
 4834+ .addClass( 'suggestions' )
 4835+ .append(
 4836+ $( '<div />' ).addClass( 'suggestions-results' )
 4837+ // Can't use click() because the container div is hidden when the textbox loses focus. Instead,
 4838+ // listen for a mousedown followed by a mouseup on the same div
 4839+ .mousedown( function( e ) {
 4840+ context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' );
 4841+ } )
 4842+ .mouseup( function( e ) {
 4843+ var $result = $( e.target ).closest( '.suggestions-results div' );
 4844+ var $other = context.data.mouseDownOn;
 4845+ context.data.mouseDownOn = $( [] );
 4846+ if ( $result.get( 0 ) != $other.get( 0 ) ) {
 4847+ return;
 4848+ }
 4849+ $.suggestions.highlight( context, $result, true );
 4850+ context.data.$container.hide();
 4851+ if ( typeof context.config.result.select == 'function' ) {
 4852+ context.config.result.select.call( $result, context.data.$textbox );
 4853+ }
 4854+ context.data.$textbox.focus();
 4855+ } )
 4856+ )
 4857+ .append(
 4858+ $( '<div />' ).addClass( 'suggestions-special' )
 4859+ // Can't use click() because the container div is hidden when the textbox loses focus. Instead,
 4860+ // listen for a mousedown followed by a mouseup on the same div
 4861+ .mousedown( function( e ) {
 4862+ context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' );
 4863+ } )
 4864+ .mouseup( function( e ) {
 4865+ var $special = $( e.target ).closest( '.suggestions-special' );
 4866+ var $other = context.data.mouseDownOn;
 4867+ context.data.mouseDownOn = $( [] );
 4868+ if ( $special.get( 0 ) != $other.get( 0 ) ) {
 4869+ return;
 4870+ }
 4871+ context.data.$container.hide();
 4872+ if ( typeof context.config.special.select == 'function' ) {
 4873+ context.config.special.select.call( $special, context.data.$textbox );
 4874+ }
 4875+ context.data.$textbox.focus();
 4876+ } )
 4877+ .mousemove( function( e ) {
 4878+ context.data.selectedWithMouse = true;
 4879+ $.suggestions.highlight(
 4880+ context, $( e.target ).closest( '.suggestions-special' ), false
 4881+ );
 4882+ } )
 4883+ )
 4884+ .appendTo( $( 'body' ) );
 4885+ $(this)
 4886+ // Stop browser autocomplete from interfering
 4887+ .attr( 'autocomplete', 'off')
 4888+ .keydown( function( e ) {
 4889+ // Store key pressed to handle later
 4890+ context.data.keypressed = ( e.keyCode == undefined ) ? e.which : e.keyCode;
 4891+ context.data.keypressedCount = 0;
 4892+
 4893+ switch ( context.data.keypressed ) {
 4894+ // This preventDefault logic is duplicated from
 4895+ // $.suggestions.keypress(), which sucks
 4896+ case 40:
 4897+ e.preventDefault();
 4898+ e.stopImmediatePropagation();
 4899+ break;
 4900+ case 38:
 4901+ case 27:
 4902+ case 13:
 4903+ if ( context.data.$container.is( ':visible' ) ) {
 4904+ e.preventDefault();
 4905+ e.stopImmediatePropagation();
 4906+ }
 4907+ }
 4908+ } )
 4909+ .keypress( function( e ) {
 4910+ context.data.keypressedCount++;
 4911+ $.suggestions.keypress( e, context, context.data.keypressed );
 4912+ } )
 4913+ .keyup( function( e ) {
 4914+ // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a
 4915+ // keypress in between, solve it
 4916+ if ( context.data.keypressedCount == 0 ) {
 4917+ $.suggestions.keypress( e, context, context.data.keypressed );
 4918+ }
 4919+ } )
 4920+ .blur( function() {
 4921+ // When losing focus because of a mousedown
 4922+ // on a suggestion, don't hide the suggestions
 4923+ if ( context.data.mouseDownOn.length > 0 ) {
 4924+ return;
 4925+ }
 4926+ context.data.$container.hide();
 4927+ $.suggestions.cancel( context );
 4928+ } );
 4929+ }
 4930+ // Store the context for next time
 4931+ $(this).data( 'suggestions-context', context );
 4932+ } );
 4933+ return returnValue !== null ? returnValue : $(this);
 4934+};
 4935+
 4936+} )( jQuery );
44164937 /**
44174938 * Simple predictive typing category adder for Mediawiki.
44184939 * Relies on globals: wgScriptPath, wgNamespaceIds, wgFormattedNamespaces
Index: trunk/extensions/UploadWizard/resources/combined.min.js
@@ -4417,6 +4417,527 @@
44184418
44194419
44204420
 4421+
 4422+
 4423+
 4424+
 4425+
 4426+
 4427+
 4428+
 4429+
 4430+
 4431+
 4432+
 4433+
 4434+
 4435+
 4436+
 4437+
 4438+
 4439+
 4440+
 4441+
 4442+
 4443+
 4444+
 4445+
 4446+
 4447+
 4448+
 4449+
 4450+
 4451+
 4452+
 4453+
 4454+
 4455+
 4456+
 4457+
 4458+
 4459+(function($){
 4460+
 4461+$.suggestions={
 4462+
 4463+
 4464+
 4465+
 4466+cancel:function(context){
 4467+if(context.data.timerID!=null){
 4468+clearTimeout(context.data.timerID);
 4469+}
 4470+if(typeof context.config.cancel=='function'){
 4471+context.config.cancel.call(context.data.$textbox);
 4472+}
 4473+},
 4474+
 4475+
 4476+
 4477+
 4478+
 4479+restore:function(context){
 4480+context.data.$textbox.val(context.data.prevText);
 4481+},
 4482+
 4483+
 4484+
 4485+
 4486+
 4487+
 4488+update:function(context,delayed){
 4489+
 4490+function maybeFetch(){
 4491+if(context.data.$textbox.val()!==context.data.prevText){
 4492+context.data.prevText=context.data.$textbox.val();
 4493+if(typeof context.config.fetch=='function'){
 4494+context.config.fetch.call(context.data.$textbox,context.data.$textbox.val());
 4495+}
 4496+}
 4497+}
 4498+
 4499+if(context.data.timerID!=null){
 4500+clearTimeout(context.data.timerID);
 4501+}
 4502+if(delayed){
 4503+
 4504+context.data.timerID=setTimeout(maybeFetch,context.config.delay);
 4505+}else{
 4506+maybeFetch();
 4507+}
 4508+$.suggestions.special(context);
 4509+},
 4510+special:function(context){
 4511+
 4512+if(typeof context.config.special.render=='function'){
 4513+
 4514+setTimeout(function(){
 4515+
 4516+$special=context.data.$container.find('.suggestions-special');
 4517+context.config.special.render.call($special,context.data.$textbox.val());
 4518+},1);
 4519+}
 4520+},
 4521+
 4522+
 4523+
 4524+
 4525+
 4526+configure:function(context,property,value){
 4527+
 4528+switch(property){
 4529+case'fetch':
 4530+case'cancel':
 4531+case'special':
 4532+case'result':
 4533+case'$region':
 4534+context.config[property]=value;
 4535+break;
 4536+case'suggestions':
 4537+context.config[property]=value;
 4538+
 4539+if(typeof context.data!=='undefined'){
 4540+if(context.data.$textbox.val().length==0){
 4541+
 4542+context.data.$container.hide();
 4543+}else{
 4544+
 4545+context.data.$container.show();
 4546+
 4547+var newCSS={
 4548+'top':context.config.$region.offset().top+context.config.$region.outerHeight(),
 4549+'bottom':'auto',
 4550+'width':context.config.$region.outerWidth(),
 4551+'height':'auto'
 4552+}
 4553+if(context.config.positionFromLeft){
 4554+newCSS['left']=context.config.$region.offset().left;
 4555+newCSS['right']='auto';
 4556+}else{
 4557+newCSS['left']='auto';
 4558+newCSS['right']=$('body').width()-(context.config.$region.offset().left+context.config.$region.outerWidth());
 4559+}
 4560+context.data.$container.css(newCSS);
 4561+var $results=context.data.$container.children('.suggestions-results');
 4562+$results.empty();
 4563+var expWidth=-1;
 4564+var $autoEllipseMe=$([]);
 4565+var matchedText=null;
 4566+for(var i=0;i<context.config.suggestions.length;i++){
 4567+var text=context.config.suggestions[i];
 4568+var $result=$('<div />')
 4569+.addClass('suggestions-result')
 4570+.attr('rel',i)
 4571+.data('text',context.config.suggestions[i])
 4572+.mousemove(function(e){
 4573+context.data.selectedWithMouse=true;
 4574+$.suggestions.highlight(
 4575+context,$(this).closest('.suggestions-results div'),false
 4576+);
 4577+})
 4578+.appendTo($results);
 4579+
 4580+if(typeof context.config.result.render=='function'){
 4581+context.config.result.render.call($result,context.config.suggestions[i]);
 4582+}else{
 4583+
 4584+if(context.config.highlightInput){
 4585+matchedText=context.data.prevText;
 4586+}
 4587+$result.append($('<span />')
 4588+.css('whiteSpace','nowrap')
 4589+.text(text)
 4590+);
 4591+
 4592+
 4593+
 4594+var $span=$result.children('span');
 4595+if($span.outerWidth()>$result.width()&&$span.outerWidth()>expWidth){
 4596+
 4597+expWidth=$span.outerWidth()+(context.data.$container.width()-$span.parent().width());
 4598+}
 4599+$autoEllipseMe=$autoEllipseMe.add($result);
 4600+}
 4601+}
 4602+
 4603+if(expWidth>context.data.$container.width()){
 4604+var maxWidth=context.config.maxExpandFactor*context.data.$textbox.width();
 4605+context.data.$container.width(Math.min(expWidth,maxWidth));
 4606+}
 4607+
 4608+$autoEllipseMe.autoEllipsis({hasSpan:true,tooltip:true,matchText:matchedText});
 4609+}
 4610+}
 4611+break;
 4612+case'maxRows':
 4613+context.config[property]=Math.max(1,Math.min(100,value));
 4614+break;
 4615+case'delay':
 4616+context.config[property]=Math.max(0,Math.min(1200,value));
 4617+break;
 4618+case'maxExpandFactor':
 4619+context.config[property]=Math.max(1,value);
 4620+break;
 4621+case'submitOnClick':
 4622+case'positionFromLeft':
 4623+case'highlightInput':
 4624+context.config[property]=value?true:false;
 4625+break;
 4626+}
 4627+},
 4628+
 4629+
 4630+
 4631+
 4632+
 4633+highlight:function(context,result,updateTextbox){
 4634+var selected=context.data.$container.find('.suggestions-result-current');
 4635+if(!result.get||selected.get(0)!=result.get(0)){
 4636+if(result=='prev'){
 4637+if(selected.is('.suggestions-special')){
 4638+result=context.data.$container.find('.suggestions-result:last')
 4639+}else{
 4640+result=selected.prev();
 4641+if(selected.length==0){
 4642+
 4643+if(context.data.$container.find('.suggestions-special').html()!=""){
 4644+result=context.data.$container.find('.suggestions-special');
 4645+}else{
 4646+result=context.data.$container.find('.suggestions-results div:last');
 4647+}
 4648+}
 4649+}
 4650+}else if(result=='next'){
 4651+if(selected.length==0){
 4652+
 4653+result=context.data.$container.find('.suggestions-results div:first');
 4654+if(result.length==0&&context.data.$container.find('.suggestions-special').html()!=""){
 4655+
 4656+result=context.data.$container.find('.suggestions-special');
 4657+}
 4658+}else{
 4659+result=selected.next();
 4660+if(selected.is('.suggestions-special')){
 4661+result=$([]);
 4662+}else if(
 4663+result.length==0&&
 4664+context.data.$container.find('.suggestions-special').html()!=""
 4665+){
 4666+
 4667+result=context.data.$container.find('.suggestions-special');
 4668+}
 4669+}
 4670+}
 4671+selected.removeClass('suggestions-result-current');
 4672+result.addClass('suggestions-result-current');
 4673+}
 4674+if(updateTextbox){
 4675+if(result.length==0||result.is('.suggestions-special')){
 4676+$.suggestions.restore(context);
 4677+}else{
 4678+context.data.$textbox.val(result.data('text'));
 4679+
 4680+
 4681+context.data.$textbox.change();
 4682+}
 4683+context.data.$textbox.trigger('change');
 4684+}
 4685+},
 4686+
 4687+
 4688+
 4689+
 4690+keypress:function(e,context,key){
 4691+var wasVisible=context.data.$container.is(':visible');
 4692+var preventDefault=false;
 4693+switch(key){
 4694+
 4695+case 40:
 4696+if(wasVisible){
 4697+$.suggestions.highlight(context,'next',true);
 4698+context.data.selectedWithMouse=false;
 4699+}else{
 4700+$.suggestions.update(context,false);
 4701+}
 4702+preventDefault=true;
 4703+break;
 4704+
 4705+case 38:
 4706+if(wasVisible){
 4707+$.suggestions.highlight(context,'prev',true);
 4708+context.data.selectedWithMouse=false;
 4709+}
 4710+preventDefault=wasVisible;
 4711+break;
 4712+
 4713+case 27:
 4714+context.data.$container.hide();
 4715+$.suggestions.restore(context);
 4716+$.suggestions.cancel(context);
 4717+context.data.$textbox.trigger('change');
 4718+preventDefault=wasVisible;
 4719+break;
 4720+
 4721+case 13:
 4722+context.data.$container.hide();
 4723+preventDefault=wasVisible;
 4724+selected=context.data.$container.find('.suggestions-result-current');
 4725+if(selected.size()==0||context.data.selectedWithMouse){
 4726+
 4727+
 4728+$.suggestions.cancel(context);
 4729+context.config.$region.closest('form').submit();
 4730+}else if(selected.is('.suggestions-special')){
 4731+if(typeof context.config.special.select=='function'){
 4732+context.config.special.select.call(selected,context.data.$textbox);
 4733+}
 4734+}else{
 4735+if(typeof context.config.result.select=='function'){
 4736+$.suggestions.highlight(context,selected,true);
 4737+context.config.result.select.call(selected,context.data.$textbox);
 4738+}else{
 4739+$.suggestions.highlight(context,selected,true);
 4740+}
 4741+}
 4742+break;
 4743+default:
 4744+$.suggestions.update(context,true);
 4745+break;
 4746+}
 4747+if(preventDefault){
 4748+e.preventDefault();
 4749+e.stopImmediatePropagation();
 4750+}
 4751+}
 4752+};
 4753+$.fn.suggestions=function(){
 4754+
 4755+
 4756+var returnValue=null;
 4757+var args=arguments;
 4758+
 4759+$(this).each(function(){
 4760+
 4761+
 4762+
 4763+var context=$(this).data('suggestions-context');
 4764+if(typeof context=='undefined'||context==null){
 4765+context={
 4766+config:{
 4767+'fetch':function(){},
 4768+'cancel':function(){},
 4769+'special':{},
 4770+'result':{},
 4771+'$region':$(this),
 4772+'suggestions':[],
 4773+'maxRows':7,
 4774+'delay':120,
 4775+'submitOnClick':false,
 4776+'maxExpandFactor':3,
 4777+'positionFromLeft':true,
 4778+'highlightInput':false
 4779+}
 4780+};
 4781+}
 4782+
 4783+
 4784+
 4785+
 4786+if(args.length>0){
 4787+if(typeof args[0]=='object'){
 4788+
 4789+for(var key in args[0]){
 4790+$.suggestions.configure(context,key,args[0][key]);
 4791+}
 4792+}else if(typeof args[0]=='string'){
 4793+if(args.length>1){
 4794+
 4795+$.suggestions.configure(context,args[0],args[1]);
 4796+}else if(returnValue==null){
 4797+
 4798+returnValue=(args[0]in context.config?undefined:context.config[args[0]]);
 4799+}
 4800+}
 4801+}
 4802+
 4803+
 4804+
 4805+if(typeof context.data=='undefined'){
 4806+context.data={
 4807+
 4808+'timerID':null,
 4809+
 4810+'prevText':null,
 4811+
 4812+'visibleResults':0,
 4813+
 4814+'mouseDownOn':$([]),
 4815+'$textbox':$(this),
 4816+'selectedWithMouse':false
 4817+};
 4818+
 4819+var newCSS={
 4820+'top':Math.round(context.data.$textbox.offset().top+context.data.$textbox.outerHeight()),
 4821+'width':context.data.$textbox.outerWidth(),
 4822+'display':'none'
 4823+}
 4824+if(context.config.positionFromLeft){
 4825+newCSS['left']=context.config.$region.offset().left;
 4826+newCSS['right']='auto';
 4827+}else{
 4828+newCSS['left']='auto';
 4829+newCSS['right']=$('body').width()-(context.config.$region.offset().left+context.config.$region.outerWidth());
 4830+}
 4831+
 4832+context.data.$container=$('<div />')
 4833+.css(newCSS)
 4834+.addClass('suggestions')
 4835+.append(
 4836+$('<div />').addClass('suggestions-results')
 4837+
 4838+
 4839+.mousedown(function(e){
 4840+context.data.mouseDownOn=$(e.target).closest('.suggestions-results div');
 4841+})
 4842+.mouseup(function(e){
 4843+var $result=$(e.target).closest('.suggestions-results div');
 4844+var $other=context.data.mouseDownOn;
 4845+context.data.mouseDownOn=$([]);
 4846+if($result.get(0)!=$other.get(0)){
 4847+return;
 4848+}
 4849+$.suggestions.highlight(context,$result,true);
 4850+context.data.$container.hide();
 4851+if(typeof context.config.result.select=='function'){
 4852+context.config.result.select.call($result,context.data.$textbox);
 4853+}
 4854+context.data.$textbox.focus();
 4855+})
 4856+)
 4857+.append(
 4858+$('<div />').addClass('suggestions-special')
 4859+
 4860+
 4861+.mousedown(function(e){
 4862+context.data.mouseDownOn=$(e.target).closest('.suggestions-special');
 4863+})
 4864+.mouseup(function(e){
 4865+var $special=$(e.target).closest('.suggestions-special');
 4866+var $other=context.data.mouseDownOn;
 4867+context.data.mouseDownOn=$([]);
 4868+if($special.get(0)!=$other.get(0)){
 4869+return;
 4870+}
 4871+context.data.$container.hide();
 4872+if(typeof context.config.special.select=='function'){
 4873+context.config.special.select.call($special,context.data.$textbox);
 4874+}
 4875+context.data.$textbox.focus();
 4876+})
 4877+.mousemove(function(e){
 4878+context.data.selectedWithMouse=true;
 4879+$.suggestions.highlight(
 4880+context,$(e.target).closest('.suggestions-special'),false
 4881+);
 4882+})
 4883+)
 4884+.appendTo($('body'));
 4885+$(this)
 4886+
 4887+.attr('autocomplete','off')
 4888+.keydown(function(e){
 4889+
 4890+context.data.keypressed=(e.keyCode==undefined)?e.which:e.keyCode;
 4891+context.data.keypressedCount=0;
 4892+
 4893+switch(context.data.keypressed){
 4894+
 4895+
 4896+case 40:
 4897+e.preventDefault();
 4898+e.stopImmediatePropagation();
 4899+break;
 4900+case 38:
 4901+case 27:
 4902+case 13:
 4903+if(context.data.$container.is(':visible')){
 4904+e.preventDefault();
 4905+e.stopImmediatePropagation();
 4906+}
 4907+}
 4908+})
 4909+.keypress(function(e){
 4910+context.data.keypressedCount++;
 4911+$.suggestions.keypress(e,context,context.data.keypressed);
 4912+})
 4913+.keyup(function(e){
 4914+
 4915+
 4916+if(context.data.keypressedCount==0){
 4917+$.suggestions.keypress(e,context,context.data.keypressed);
 4918+}
 4919+})
 4920+.blur(function(){
 4921+
 4922+
 4923+if(context.data.mouseDownOn.length>0){
 4924+return;
 4925+}
 4926+context.data.$container.hide();
 4927+$.suggestions.cancel(context);
 4928+});
 4929+}
 4930+
 4931+$(this).data('suggestions-context',context);
 4932+});
 4933+return returnValue!==null?returnValue:$(this);
 4934+};
 4935+
 4936+})(jQuery);
 4937+
 4938+
 4939+
 4940+
 4941+
44214942 (function($j){$j.fn.mwCoolCats=function(options){
44224943
44234944 var defaults={

Status & tagging log