r94268 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94267‎ | r94268 | r94269 >
Date:19:01, 11 August 2011
Author:krinkle
Status:deferred (Comments)
Tags:
Comment:
ajaxCategories fixes based on review in r93351 CR:
* Using typeof check in clean()
* Use mw.Title to get page title from fullpagename instead of split(':')
* replaceNowikis() and restoreNowikis()
- Improve documentation
- Moved dash in the UNIQUEKEY to between the id and the incrementing integer, and made it start with an empty string (so that all following concatenations are toString'ed).
* makeCaseInsensitive(): Moved the wgCaseSensitiveNamespaces-check out and wrapped it around the caller instead. Also cached the outcome of "Is category namespace sensitive ?".
* createButton(): text-argument is indeed text, not html. Applying html-escaping.
* resolveRedirects():
- Replace access to private property _name of mw.Title with function getMainText().
* handleCategoryAdd() and handleEditLink():
- Restructure title-handling (no local replace() calls and clean(), let mw.Title handle it)
- Renaming arguments and documenting them better
- Renaming local variables and removing redundant parts
- Preserving sortkey as sortkey as long as possible without the pipe
- Calling the combination of sortkey and leading pipe 'suffix' instead of, also, sortkey.
* createCatLink():
- Remove the sanitizing here, the string passed is already clean as it comes from mw.Title now
- Using .text() instead of .append( which is .html-like), category names can contain special characters.
* containsCat():
- Using $.each instead of [].filter. Stopping after first match.
* buildRegex(): Allow whitespace before namespace colon, and allow whitespace after category name (but before ]] and |..]])

Additional changes not for any function in particular:
* Literally return null in $.map callbacks.
* Using the existence-system of mw.Title instead of passing around booleans everywhere
** Removed 'exists' argument from the resolveRedirects() and handleCategoryAdd() functions, instead checking .exists() of the mw.Title object.
* Passing and using mw.Title objects where possible instead of converting back and forth between strings and objects etc.
* Using "TitleObj.getUrl()" instead of "catUrl( titleString )". Removed now unused catUrl() function.
* To improve readability, renamed local uses of 'var that = this' to 'var ajaxcat = this'.
* Syntax error fixes (.parent -> .parent())
* Merging var statements
* Renamed generic members of 'stash' from 'stash.summaries' to 'stash.dialogDescriptions' and 'stash.shortSum' to 'stash.editSummaries'. dialogDescription is always HTML (input should be escaped before hand)
Modified paths:
  • /trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js (modified) (history)

Diff [purge]

Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js
@@ -21,25 +21,20 @@
2222 categoryLinkSelector: 'li a:not(.icon)',
2323 multiEdit: $.inArray( 'user', mw.config.get( 'wgUserGroups' ) ) !== -1,
2424 resolveRedirects: true
25 - };
 25+ },
 26+ isCatNsSensitive = $.inArray( 14, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1;
2627
 28+ /**
 29+ * @return {String}
 30+ */
2731 function clean( s ) {
28 - if ( s !== undefined ) {
 32+ if ( typeof s === 'string' ) {
2933 return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '' );
3034 }
 35+ return '';
3136 }
3237
3338 /**
34 - * Build URL for passed Category
35 - *
36 - * @param cat {String} Category name.
37 - * @return {String} Valid URL
38 - */
39 - function catUrl( cat ) {
40 - return mw.util.wikiGetlink( new mw.Title( cat, catNsId ) );
41 - }
42 -
43 - /**
4439 * Helper function for $.fn.suggestions
4540 *
4641 * @context {jQuery}
@@ -62,15 +57,11 @@
6358 dataType: 'json',
6459 success: function( data ) {
6560 // Process data.query.allpages into an array of titles
66 - var title,
67 - pages = data.query.allpages,
68 - titleArr = [];
 61+ var pages = data.query.allpages,
 62+ titleArr = $.map( pages, function( page ) {
 63+ return new mw.Title( page.title ).getMainText();
 64+ } );
6965
70 - $.each( pages, function( i, page ) {
71 - title = page.title.split( ':', 2 )[1];
72 - titleArr.push( title );
73 - } );
74 -
7566 $el.suggestions( 'suggestions', titleArr );
7667 }
7768 } );
@@ -78,32 +69,33 @@
7970 }
8071
8172 /**
82 - * Replace <nowiki> and comments with unique keys
 73+ * Replace <nowiki> and comments with unique keys in the page text.
8374 *
8475 * @param text {String}
85 - * @param id
86 - * @param keys {Array}
 76+ * @param id {String} Unique key for this nowiki replacement layer call.
 77+ * @param keys {Array} Array where fragments will be stored in.
8778 * @return {String}
8879 */
8980 function replaceNowikis( text, id, keys ) {
9081 var matches = text.match( /(<nowiki\>[\s\S]*?<\/nowiki>|<\!--[\s\S]*?--\>)/g );
9182 for ( var i = 0; matches && i < matches.length; i++ ) {
9283 keys[i] = matches[i];
93 - text = text.replace( matches[i], id + i + '-' );
 84+ text = text.replace( matches[i], '' + id + '-' + i );
9485 }
9586 return text;
9687 }
9788
9889 /**
99 - * Restore <nowiki> and comments from unique keys
 90+ * Restore <nowiki> and comments from unique keys in the page text.
 91+ *
10092 * @param text {String}
101 - * @param id
102 - * @param keys {Array}
 93+ * @param id {String} Unique key of the layer to be restored, as passed to replaceNowikis().
 94+ * @param keys {Array} Array where fragements should be fetched from.
10395 * @return {String}
10496 */
10597 function restoreNowikis( text, id, keys ) {
10698 for ( var i = 0; i < keys.length; i++ ) {
107 - text = text.replace( id + i + '-', keys[i] );
 99+ text = text.replace( '' + id + '-' + i, keys[i] );
108100 }
109101 return text;
110102 }
@@ -118,9 +110,6 @@
119111 */
120112 function makeCaseInsensitive( string ) {
121113 var newString = '';
122 - if ( $.inArray( 14, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
123 - return string;
124 - }
125114 for ( var i = 0; i < string.length; i++ ) {
126115 newString += '[' + string.charAt( i ).toUpperCase() + string.charAt( i ).toLowerCase() + ']';
127116 }
@@ -141,13 +130,16 @@
142131 // Filter out all names for category namespace
143132 categoryNSFragment = $.map( mw.config.get( 'wgNamespaceIds' ), function( id, name ) {
144133 if ( id === catNsId ) {
145 - return makeCaseInsensitive( $.escapeRE( name ) );
 134+ name = $.escapeRE( name );
 135+ return !isCatNsSensitive ? makeCaseInsensitive( name ) : name;
146136 }
 137+ // Otherwise don't include in categoryNSFragment
 138+ return null;
147139 } ).join( '|' );
148140
149141 firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']';
150142 titleFragment = firstChar + titleFragment.substr( 1 );
151 - categoryRegex = '\\[\\[(' + categoryNSFragment + '):' + '[ _]*' + titleFragment + '(\\|[^\\]]*)?\\]\\]';
 143+ categoryRegex = '\\[\\[(' + categoryNSFragment + ')' + '[ _]*' + ':' + '[ _]*' + titleFragment + '[ _]*' + '(\\|[^\\]]*)?\\]\\]';
152144 if ( matchLineBreak ) {
153145 categoryRegex += '[ \\t\\r]*\\n?';
154146 }
@@ -160,7 +152,7 @@
161153 * @param icon {String} The icon class.
162154 * @param title {String} Title attribute.
163155 * @param className {String} (optional) Additional classes to be added to the button.
164 - * @param text {String} (optional) Text of button.
 156+ * @param text {String} (optional) Text label of button.
165157 * @return {jQuery} The button.
166158 */
167159 function createButton( icon, title, className, text ){
@@ -172,7 +164,7 @@
173165
174166 if ( text ) {
175167 var $icon = $( '<span>' ).addClass( 'icon ' + icon ).html( '&#8203;' );
176 - $button.addClass( 'icon-parent' ).append( $icon ).append( text );
 168+ $button.addClass( 'icon-parent' ).append( $icon ).append( mw.html.escape( text ) );
177169 } else {
178170 $button.addClass( 'icon ' + icon );
179171 }
@@ -188,7 +180,7 @@
189181 this.options = options = $.extend( defaultOptions, options );
190182
191183 // Save scope in shortcut
192 - var that = this;
 184+ var ajaxcat = this;
193185
194186 // Elements tied to this instance
195187 this.saveAllButton = null;
@@ -199,8 +191,8 @@
200192
201193 // Stash and hooks
202194 this.stash = {
203 - summaries: [],
204 - shortSum: [],
 195+ dialogDescriptions: [],
 196+ editSummaries: [],
205197 fns: []
206198 };
207199 this.hooks = {
@@ -226,8 +218,8 @@
227219 categoryText = $.ucFirst( $el.parent().find( '.mw-addcategory-input' ).val() || '' );
228220
229221 // Resolve redirects
230 - that.resolveRedirects( categoryText, function( resolvedCat, exists ) {
231 - that.handleCategoryAdd( $link, resolvedCat, false, exists );
 222+ ajaxcat.resolveRedirects( categoryText, function( resolvedCatTitle ) {
 223+ ajaxcat.handleCategoryAdd( $link, resolvedCatTitle, '', false );
232224 } );
233225 };
234226
@@ -239,25 +231,33 @@
240232 var $el = $( this ),
241233 $link = $el.data( 'link' ),
242234 category = $link.text(),
243 - $input = that.makeSuggestionBox( category,
244 - that.handleEditLink,
245 - that.options.multiEdit ? mw.msg( 'ajax-confirm-ok' ) : mw.msg( 'ajax-confirm-save' )
 235+ $input = ajaxcat.makeSuggestionBox( category,
 236+ ajaxcat.handleEditLink,
 237+ ajaxcat.options.multiEdit ? mw.msg( 'ajax-confirm-ok' ) : mw.msg( 'ajax-confirm-save' )
246238 );
247239
248240 $link.after( $input ).hide();
249241
250242 $input.find( '.mw-addcategory-input' ).focus();
251243
 244+ // Get the editButton associated with this category link,
 245+ // and hide it.
252246 $link.data( 'editButton' ).hide();
253247
 248+ // Get the deleteButton associated with this category link,
254249 $link.data( 'deleteButton' )
 250+ // (re)set click handler
255251 .unbind( 'click' )
256252 .click( function() {
 253+ // When the delete button is clicked:
 254+ // - Remove the suggestion box
 255+ // - Show the link and it's edit button
 256+ // - (re)set the click handler again
257257 $input.remove();
258258 $link.show().data( 'editButton' ).show();
259259 $( this )
260260 .unbind( 'click' )
261 - .click( that.handleDeleteLink )
 261+ .click( ajaxcat.handleDeleteLink )
262262 .attr( 'title', mw.msg( 'ajax-remove-category' ) );
263263 })
264264 .attr( 'title', mw.msg( 'ajax-cancel' ) );
@@ -270,36 +270,35 @@
271271 * @param e {jQuery Event}
272272 */
273273 this.handleEditLink = function( e ) {
274 - var categoryNew,
 274+ var input, category, categoryOld,
 275+ sortkey = '', // Wikitext for between '[[Category:Foo' and ']]'.
275276 $el = $( this ),
276 - $link = $el.parent().parent().find( 'a:not(.icon)' ),
277 - sortkey = '';
 277+ $link = $el.parent().parent().find( 'a:not(.icon)' );
278278
279279 // Grab category text
280 - categoryNew = $el.parent().find( '.mw-addcategory-input' ).val();
281 - categoryNew = $.ucFirst( categoryNew.replace( /_/g, ' ' ) );
 280+ input = $el.parent().find( '.mw-addcategory-input' ).val();
282281
283282 // Strip sortkey
284 - var arr = categoryNew.split( '|' );
 283+ var arr = input.split( '|', 2 );
285284 if ( arr.length > 1 ) {
286 - categoryNew = arr.shift();
287 - sortkey = '|' + arr.join( '|' );
 285+ category = arr[0];
 286+ sortkey = arr[1];
288287 }
289288
290289 // Grab text
291 - var added = $link.hasClass( 'mw-added-category' );
292 - that.resetCatLink( $link );
293 - var category = $link.text();
 290+ var isAdded = $link.hasClass( 'mw-added-category' );
 291+ ajaxcat.resetCatLink( $link );
 292+ categoryOld = $link.text();
294293
295 - // Check for dupes ( exluding itself )
296 - if ( category !== categoryNew && that.containsCat( categoryNew ) ) {
 294+ // If something changed and the new cat is already on the page, delete it.
 295+ if ( categoryOld !== category && ajaxcat.containsCat( category ) ) {
297296 $link.data( 'deleteButton' ).click();
298297 return;
299298 }
300299
301300 // Resolve redirects
302 - that.resolveRedirects( categoryNew, function( resolvedCat, exists ) {
303 - that.handleCategoryEdit( $link, category, resolvedCat, sortkey, exists, added );
 301+ ajaxcat.resolveRedirects( category, function( resolvedCatTitle ) {
 302+ ajaxcat.handleCategoryEdit( $link, categoryOld, resolvedCatTitle, sortkey, isAdded );
304303 });
305304 };
306305
@@ -316,19 +315,19 @@
317316
318317 if ( $link.is( '.mw-added-category, .mw-changed-category' ) ) {
319318 // We're just cancelling the addition or edit
320 - that.resetCatLink( $link, $link.hasClass( 'mw-added-category' ) );
 319+ ajaxcat.resetCatLink( $link, $link.hasClass( 'mw-added-category' ) );
321320 return;
322321 } else if ( $link.is( '.mw-removed-category' ) ) {
323322 // It's already removed...
324323 return;
325324 }
326 - that.handleCategoryDelete( $link, category );
 325+ ajaxcat.handleCategoryDelete( $link, category );
327326 };
328327
329328 /**
330329 * When multiEdit mode is enabled,
331330 * this is called when the user clicks "save all"
332 - * Combines the summaries and edit functions.
 331+ * Combines the dialogDescriptions and edit functions.
333332 *
334333 * @context Element
335334 * @return ?
@@ -336,28 +335,28 @@
337336 this.handleStashedCategories = function() {
338337
339338 // Remove "holes" in array
340 - var summary = $.grep( that.stash.summaries, function( n, i ) {
 339+ var dialogDescriptions = $.grep( ajaxcat.stash.dialogDescriptions, function( n, i ) {
341340 return n;
342341 } );
343342
344 - if ( summary.length < 1 ) {
 343+ if ( dialogDescriptions.length < 1 ) {
345344 // Nothing to do here.
346 - that.saveAllButton.hide();
347 - that.cancelAllButton.hide();
 345+ ajaxcat.saveAllButton.hide();
 346+ ajaxcat.cancelAllButton.hide();
348347 return;
349348 } else {
350 - summary = summary.join( '<br/>' );
 349+ dialogDescriptions = dialogDescriptions.join( '<br/>' );
351350 }
352351
353352 // Remove "holes" in array
354 - var summaryShort = $.grep( that.stash.shortSum, function( n,i ) {
 353+ var summaryShort = $.grep( ajaxcat.stash.editSummaries, function( n,i ) {
355354 return n;
356355 } );
357356 summaryShort = summaryShort.join( ', ' );
358357
359 - var fns = that.stash.fns;
 358+ var fns = ajaxcat.stash.fns;
360359
361 - that.doConfirmEdit( {
 360+ ajaxcat.doConfirmEdit( {
362361 modFn: function( oldtext ) {
363362 // Run the text through all action functions
364363 var newtext = oldtext;
@@ -371,10 +370,10 @@
372371 }
373372 return newtext;
374373 },
375 - actionSummary: summary,
376 - shortSummary: summaryShort,
 374+ dialogDescription: dialogDescriptions,
 375+ editSummary: summaryShort,
377376 doneFn: function() {
378 - that.resetAll( true );
 377+ ajaxcat.resetAll( true );
379378 },
380379 $link: null,
381380 action: 'all'
@@ -398,7 +397,7 @@
399398 return;
400399 }
401400 var options = this.options,
402 - that = this,
 401+ ajaxcat = this,
403402 // Create [Add Category] link
404403 $addLink = createButton( 'icon-add',
405404 mw.msg( 'ajax-add-category' ),
@@ -414,8 +413,8 @@
415414 this.addContainer.prepend( $addLink );
416415
417416 // Create edit & delete link for each category.
418 - $( '#catlinks li a' ).each( function() {
419 - that.createCatButtons( $( this ) );
 417+ $( '#catlinks' ).find( 'li a' ).each( function() {
 418+ ajaxcat.createCatButtons( $( this ) );
420419 });
421420
422421 options.$containerNormal.append( this.addContainer );
@@ -433,7 +432,7 @@
434433 );
435434 this.saveAllButton.click( this.handleStashedCategories ).hide();
436435 this.cancelAllButton.click( function() {
437 - that.resetAll( false );
 436+ ajaxcat.resetAll( false );
438437 } ).hide();
439438 options.$containerNormal.append( this.saveAllButton ).append( this.cancelAllButton );
440439 options.$container.append( this.addContainer );
@@ -442,25 +441,22 @@
443442 /**
444443 * Insert a newly added category into the DOM.
445444 *
446 - * @param cat {String} Category name.
 445+ * @param catTitle {mw.Title} Category title for which a link should be created.
447446 * @return {jQuery}
448447 */
449 - createCatLink: function( cat ) {
450 - // User can implicitly state a sort key.
451 - // Remove before display.
452 - // strip out bad characters
453 - cat = clean( cat.replace( /\|.*/, '' ) );
 448+ createCatLink: function( catTitle ) {
 449+ var catName = catTitle.getMainText();
454450
455 - if ( $.isEmpty( cat ) || this.containsCat( cat ) ) {
 451+ if ( this.containsCat( catName ) ) {
456452 return;
457453 }
458454
459455 var $catLinkWrapper = $( this.options.catLinkWrapper ),
460456 $anchor = $( '<a>' )
461 - .append( cat )
 457+ .text( catName )
462458 .attr( {
463459 target: '_blank',
464 - href: catUrl( cat )
 460+ href: catTitle.getUrl()
465461 } );
466462
467463 $catLinkWrapper.append( $anchor );
@@ -481,7 +477,7 @@
482478 var $promptContainer = $( '<div class="mw-addcategory-prompt"></div>' ),
483479 $promptTextbox = $( '<input type="text" size="30" class="mw-addcategory-input"></input>' ),
484480 $addButton = $( '<input type="button" class="mw-addcategory-button"></input>' ),
485 - that = this;
 481+ ajaxcat = this;
486482
487483 if ( prefill !== '' ) {
488484 $promptTextbox.val( prefill );
@@ -517,33 +513,27 @@
518514 },
519515
520516 /**
521 - * Execute or queue a category add.
522 - * @param $link {jQuery}
523 - * @param category
 517+ * Execute or queue a category addition.
 518+ *
 519+ * @param $link {jQuery} Anchor tag of category link inside #catlinks.
 520+ * @param catTitle {mw.Title} Instance of mw.Title of the category to be added.
 521+ * @param catSortkey {String} sort key (optional)
524522 * @param noAppend
525 - * @param exists
526523 * @return {mw.ajaxCategories}
527524 */
528 - handleCategoryAdd: function( $link, category, noAppend, exists ) {
529 - // Handle sortkey
530 - var arr = category.split( '|' ),
531 - sortkey = '',
532 - that = this;
 525+ handleCategoryAdd: function( $link, catTitle, catSortkey, noAppend ) {
 526+ var ajaxcat = this,
 527+ // Suffix is wikitext between '[[Category:Foo' and ']]'.
 528+ suffix = catSortkey ? '|' + catSortkey : '',
 529+ catName = catTitle.getMainText(),
 530+ catFull = catTitle.toText();
533531
534 - if ( arr.length > 1 ) {
535 - category = arr.shift();
536 - sortkey = '|' + arr.join( '|' );
537 - if ( sortkey === '|' ) {
538 - sortkey = '';
539 - }
540 - }
541 -
542532 if ( !$link.length ) {
543 - $link = this.createCatLink( category );
 533+ $link = this.createCatLink( catTitle );
544534 }
545535
546 - if ( this.containsCat( category ) ) {
547 - this.showError( mw.msg( 'ajax-category-already-present', category ) );
 536+ if ( this.containsCat( catName ) ) {
 537+ this.showError( mw.msg( 'ajax-category-already-present', catName ) );
548538 return this;
549539 }
550540
@@ -555,32 +545,28 @@
556546 }
557547
558548 // Mark red if missing
559 - $link.toggleClass( 'new', exists !== true );
 549+ $link[(catTitle.exists() === false ? 'addClass' : 'removeClass')]( 'new' );
560550
561 - // Replace underscores
562 - category = category.replace( /_/g, ' ' );
563 - var catFull = new mw.Title( category, catNsId ).toString().replace( /_/g, ' ' );
564 -
565551 this.doConfirmEdit( {
566552 modFn: function( oldText ) {
567 - var newText = that.runHooks( oldText, 'beforeAdd', category );
568 - newText = newText + "\n[[" + catFull + sortkey + "]]\n";
569 - return that.runHooks( newText, 'afterAdd', category );
 553+ var newText = ajaxcat.runHooks( oldText, 'beforeAdd', catName );
 554+ newText = newText + "\n[[" + catFull + suffix + "]]\n";
 555+ return ajaxcat.runHooks( newText, 'afterAdd', catName );
570556 },
571 - actionSummary: mw.msg( 'ajax-add-category-summary', category ),
572 - shortSummary: '+[[' + catFull + ']]',
 557+ dialogDescription: mw.message( 'ajax-add-category-summary', catName ).escaped(),
 558+ editSummary: '+[[' + catFull + ']]',
573559 doneFn: function( unsaved ) {
574560 if ( !noAppend ) {
575 - that.options.$container
 561+ ajaxcat.options.$container
576562 .find( '#mw-normal-catlinks > .mw-addcategory-prompt' ).children( 'input' ).hide();
577 - that.options.$container
 563+ ajaxcat.options.$container
578564 .find( '#mw-normal-catlinks ul' ).append( $link.parent() );
579565 } else {
580566 // Remove input box & button
581567 $link.data( 'deleteButton' ).click();
582568
583569 // Update link text and href
584 - $link.show().text( category ).attr( 'href', catUrl( category ) );
 570+ $link.show().text( catName ).attr( 'href', catTitle.getUrl() );
585571 }
586572 if ( unsaved ) {
587573 $link.addClass( 'mw-added-category' );
@@ -595,69 +581,71 @@
596582
597583 /**
598584 * Execute or queue a category edit.
599 - * @param $link {jQuery}
600 - * @param category
601 - * @param categoryNew
602 - * @param sortkeyNew
603 - * @param exists {Boolean}
604 - * @param added {Boolean}
 585+ *
 586+ * @param $link {jQuery} Anchor tag of category link in #catlinks.
 587+ * @param oldCatName {String} Name of category before edit
 588+ * @param catTitle {mw.Title} Instance of mw.Title for new category
 589+ * @param catSortkey {String} Sort key of new category link (optional)
 590+ * @param isAdded {Boolean} True if this is a new link, false if it changed an existing one
605591 */
606 - handleCategoryEdit: function( $link, category, categoryNew, sortkeyNew, exists, added ) {
607 - var that = this;
 592+ handleCategoryEdit: function( $link, oldCatName, catTitle, catSortkey, isAdded ) {
 593+ var ajaxcat = this,
 594+ catName = catTitle.getMainText();
608595
609596 // Category add needs to be handled differently
610 - if ( added ) {
 597+ if ( isAdded ) {
611598 // Pass sortkey back
612 - that.handleCategoryAdd( $link, categoryNew + sortkeyNew, true );
 599+ this.handleCategoryAdd( $link, catTitle, catSortkey, true );
613600 return;
614601 }
615602
616 - // User didn't change anything.
617 - if ( category === categoryNew + sortkeyNew ) {
 603+ // User didn't change anything, trigger delete
 604+ // @todo Document why it's deleted.
 605+ if ( oldCatName === catName ) {
618606 $link.data( 'deleteButton' ).click();
619607 return;
620608 }
621609
622610 // Mark red if missing
623 - $link[(exists === false ? 'addClass' : 'removeClass')]( 'new' );
 611+ $link[(catTitle.exists() === false ? 'addClass' : 'removeClass')]( 'new' );
624612
625 - var categoryRegex = buildRegex( category ),
626 - shortSummary = '[[' + new mw.Title( category, catNsId ) + ']] -> [[' + new mw.Title( categoryNew, catNsId ) + ']]';
627 - that.doConfirmEdit({
 613+ var categoryRegex = buildRegex( oldCatName ),
 614+ editSummary = '[[' + new mw.Title( oldCatName, catNsId ).toText() + ']] -> [[' + catTitle.toText() + ']]';
 615+
 616+ ajaxcat.doConfirmEdit({
628617 modFn: function( oldText ) {
629 - var sortkey, newCategoryString,
630 - newText = that.runHooks( oldText, 'beforeChange', category, categoryNew ),
 618+ var newText = ajaxcat.runHooks( oldText, 'beforeChange', oldCatName, catName ),
631619 matches = newText.match( categoryRegex );
632620
633 - //Old cat wasn't found, likely to be transcluded
 621+ // Old cat wasn't found, likely to be transcluded
634622 if ( !$.isArray( matches ) ) {
635 - that.showError( mw.msg( 'ajax-edit-category-error' ) );
 623+ ajaxcat.showError( mw.msg( 'ajax-edit-category-error' ) );
636624 return false;
637625 }
638 - sortkey = sortkeyNew || matches[0].replace( categoryRegex, '$2' );
639 - newCategoryString = '[[' + new mw.Title( categoryNew, catNsId ) + sortkey + ']]';
640626
 627+ var suffix = catSortkey ? '|' + catSortkey : matches[0].replace( categoryRegex, '$2' ),
 628+ newCategoryWikitext = '[[' + catTitle + suffix + ']]';
 629+
641630 if ( matches.length > 1 ) {
642 - // The category is duplicated.
643 - // Remove all but one match
 631+ // The category is duplicated. Remove all but one match
644632 for ( var i = 1; i < matches.length; i++ ) {
645633 oldText = oldText.replace( matches[i], '' );
646634 }
647635 }
648 - newText = oldText.replace( categoryRegex, newCategoryString );
 636+ newText = oldText.replace( categoryRegex, newCategoryWikitext );
649637
650 - return that.runHooks( newText, 'afterChange', category, categoryNew );
 638+ return ajaxcat.runHooks( newText, 'afterChange', oldCatName, catName );
651639 },
652 - actionSummary: mw.msg( 'ajax-edit-category-summary', category, categoryNew ),
653 - shortSummary: shortSummary,
 640+ dialogDescription: mw.message( 'ajax-edit-category-summary', oldCatName, catName ).escaped(),
 641+ editSummary: editSummary,
654642 doneFn: function( unsaved ) {
655643 // Remove input box & button
656644 $link.data( 'deleteButton' ).click();
657645
658646 // Update link text and href
659 - $link.show().text( categoryNew ).attr( 'href', catUrl( categoryNew ) );
 647+ $link.show().text( catName ).attr( 'href', catTitle.getUrl() );
660648 if ( unsaved ) {
661 - $link.data( 'origCat', category ).addClass( 'mw-changed-category' );
 649+ $link.data( 'origCat', oldCatName ).addClass( 'mw-changed-category' );
662650 }
663651 },
664652 $link: $link,
@@ -668,30 +656,36 @@
669657 /**
670658 * Checks the API whether the category in question is a redirect.
671659 * Also returns existance info (to color link red/blue)
672 - * @param string category.
673 - * @param function callback
 660+ * @param category {String} Name of category to resolve
 661+ * @param callback {Function} Called with 1 argument (mw.Title object)
674662 */
675663 resolveRedirects: function( category, callback ) {
676664 if ( !this.options.resolveRedirects ) {
677665 callback( category, true );
678666 return;
679667 }
680 - var queryVars = {
681 - action:'query',
682 - titles: new mw.Title( category, catNsId ).toString(),
683 - redirects: '',
684 - format: 'json'
685 - };
 668+ var catTitle = new mw.Title( category, catNsId ),
 669+ queryVars = {
 670+ action:'query',
 671+ titles: catTitle.toString(),
 672+ redirects: 1,
 673+ format: 'json'
 674+ };
686675
687 - $.get( mw.util.wikiScript( 'api' ), queryVars,
688 - function( reply ) {
689 - var redirect = reply.query.redirects;
690 - if ( redirect ) {
691 - category = new mw.Title( redirect[0].to )._name;
692 - }
693 - callback( category, !reply.query.pages[-1] );
694 - }, 'json'
695 - );
 676+ $.getJSON( mw.util.wikiScript( 'api' ), queryVars, function( json ) {
 677+ var redirect = json.query.redirects,
 678+ exists = !json.query.pages[-1];
 679+
 680+ // Register existence
 681+ mw.Title.exist.set( catTitle.toString(), exists );
 682+
 683+ if ( redirect ) {
 684+ catTitle = new mw.Title( redirect[0].to ).getMainText();
 685+ // Redirect existence as well (non-existant pages can't be redirects)
 686+ mw.Title.exist.set( catTitle.toString(), true );
 687+ }
 688+ callback( catTitle );
 689+ } );
696690 },
697691
698692 /**
@@ -704,10 +698,10 @@
705699 var deleteButton = createButton( 'icon-close', mw.msg( 'ajax-remove-category' ) ),
706700 editButton = createButton( 'icon-edit', mw.msg( 'ajax-edit-category' ) ),
707701 saveButton = createButton( 'icon-tick', mw.msg( 'ajax-confirm-save' ) ).hide(),
708 - that = this;
 702+ ajaxcat = this;
709703
710704 deleteButton.click( this.handleDeleteLink );
711 - editButton.click( that.createEditInterface );
 705+ editButton.click( ajaxcat.createEditInterface );
712706
713707 $element.after( deleteButton ).after( editButton );
714708
@@ -761,13 +755,20 @@
762756 /**
763757 * Check whether a passed category is present in the DOM.
764758 *
 759+ * @param newCat {String} Category name to be checked for.
765760 * @return {Boolean}
766761 */
767 - containsCat: function( cat ) {
768 - cat = $.ucFirst( cat );
769 - return this.getCats().filter( function() {
770 - return $.ucFirst( this ) === cat;
771 - } ).length !== 0;
 762+ containsCat: function( newCat ) {
 763+ newCat = $.ucFirst( newCat );
 764+ var match = false;
 765+ $.each( this.getCats(), function(i, cat) {
 766+ if ( $.ucFirst( cat ) === newCat ) {
 767+ match = true;
 768+ // Stop once we have a match
 769+ return false;
 770+ }
 771+ } );
 772+ return match;
772773 },
773774
774775 /**
@@ -779,22 +780,22 @@
780781 */
781782 handleCategoryDelete: function( $link, category ) {
782783 var categoryRegex = buildRegex( category, true ),
783 - that = this;
 784+ ajaxcat = this;
784785
785 - that.doConfirmEdit({
 786+ this.doConfirmEdit({
786787 modFn: function( oldText ) {
787 - var newText = that.runHooks( oldText, 'beforeDelete', category );
 788+ var newText = ajaxcat.runHooks( oldText, 'beforeDelete', category );
788789 newText = newText.replace( categoryRegex, '' );
789790
790791 if ( newText === oldText ) {
791 - that.showError( mw.msg( 'ajax-remove-category-error' ) );
 792+ ajaxcat.showError( mw.msg( 'ajax-remove-category-error' ) );
792793 return false;
793794 }
794795
795 - return that.runHooks( newText, 'afterDelete', category );
 796+ return ajaxcat.runHooks( newText, 'afterDelete', category );
796797 },
797 - actionSummary: mw.msg( 'ajax-remove-category-summary', category ),
798 - shortSummary: '-[[' + new mw.Title( category, catNsId ) + ']]',
 798+ dialogDescription: mw.message( 'ajax-remove-category-summary', category ).escaped(),
 799+ editSummary: '-[[' + new mw.Title( category, catNsId ) + ']]',
799800 doneFn: function( unsaved ) {
800801 if ( unsaved ) {
801802 $link.addClass( 'mw-removed-category' );
@@ -824,17 +825,18 @@
825826 this.removeStashItem( data.stashIndex );
826827 }
827828 if ( del ) {
828 - $link.parent.remove();
 829+ $link.parent().remove();
829830 return;
830831 }
831832 if ( data.origCat && !dontRestoreText ) {
832 - $link.text( data.origCat );
833 - $link.attr( 'href', catUrl( data.origCat ) );
 833+ var catTitle = new mw.Title( data.origCat, catNsId );
 834+ $link.text( catTitle.getMainText() );
 835+ $link.attr( 'href', catTitle.getUrl() );
834836 }
835837
836838 $link.removeData();
837839
838 - // Readd static.
 840+ // Read static.
839841 $link.data( {
840842 saveButton: data.saveButton,
841843 deleteButton: data.deleteButton,
@@ -860,7 +862,7 @@
861863 titles: page,
862864 rvprop: 'content|timestamp',
863865 format: 'json'
864 - }, that = this;
 866+ }, ajaxcat = this;
865867
866868 $.post(
867869 mw.util.wikiScript( 'api' ),
@@ -868,23 +870,23 @@
869871 function( reply ) {
870872 var infos = reply.query.pages;
871873 $.each( infos, function( pageid, data ) {
872 - var token = data.edittoken;
873 - var timestamp = data.revisions[0].timestamp;
874 - var oldText = data.revisions[0]['*'];
 874+ var token = data.edittoken,
 875+ timestamp = data.revisions[0].timestamp,
 876+ oldText = data.revisions[0]['*'],
 877+ nowikiKey = mw.user.generateId(), // Unique ID for nowiki replacement
 878+ nowikiFragments = []; // Nowiki fragments will be stored here during the changes
875879
876 - // Replace all nowiki and comments with unique keys
877 - var key = mw.user.generateId();
878 - var nowiki = [];
879 - oldText = replaceNowikis( oldText, key, nowiki );
 880+ // Replace all nowiki parts with unique keys..
 881+ oldText = replaceNowikis( oldText, nowikiKey, nowikiFragments );
880882
881 - // Then do the changes
 883+ // ..then apply the changes to the page text..
882884 var newText = fn( oldText );
883885 if ( newText === false ) {
884886 return;
885887 }
886888
887 - // And restore them back
888 - newText = restoreNowikis( newText, key, nowiki );
 889+ // ..and restore the nowiki parts back.
 890+ newText = restoreNowikis( newText, nowikiKey, nowikiFragments );
889891
890892 var postEditVars = {
891893 action: 'edit',
@@ -896,15 +898,20 @@
897899 format: 'json'
898900 };
899901
900 - $.post( mw.util.wikiScript( 'api' ), postEditVars, doneFn, 'json' )
901 - .error( function( xhr, text, error ) {
902 - that.showError( mw.msg( 'ajax-api-error', text, error ) );
 902+ $.post(
 903+ mw.util.wikiScript( 'api' ),
 904+ postEditVars,
 905+ doneFn,
 906+ 'json'
 907+ )
 908+ .error( function( xhr, text, error ) {
 909+ ajaxcat.showError( mw.msg( 'ajax-api-error', text, error ) );
903910 });
904911 } );
905912 },
906913 'json'
907914 ).error( function( xhr, text, error ) {
908 - that.showError( mw.msg( 'ajax-api-error', text, error ) );
 915+ ajaxcat.showError( mw.msg( 'ajax-api-error', text, error ) );
909916 } );
910917 },
911918
@@ -915,8 +922,8 @@
916923 *
917924 * @param props {Object}:
918925 * - modFn {Function} text-modifying function
919 - * - actionSummary {String} Changes done
920 - * - shortSummary {String} Changes, short version
 926+ * - dialogDescription {String} Changes done (HTML in the dialog)
 927+ * - editSummary {String} Changes done (text for edit summary)
921928 * - doneFn {Function} callback after everything is done
922929 * - $link {jQuery}
923930 * - action
@@ -930,7 +937,7 @@
931938 buttons: buttons,
932939 width: 450
933940 },
934 - that = this;
 941+ ajaxcat = this;
935942
936943 // Check whether to use multiEdit mode:
937944 if ( this.options.multiEdit && props.action !== 'all' ) {
@@ -938,17 +945,17 @@
939946 // Stash away
940947 props.$link
941948 .data( 'stashIndex', this.stash.fns.length )
942 - .data( 'summary', props.actionSummary );
 949+ .data( 'summary', props.dialogDescription );
943950
944 - this.stash.summaries.push( props.actionSummary );
945 - this.stash.shortSum.push( props.shortSummary );
 951+ this.stash.dialogDescriptions.push( props.dialogDescription );
 952+ this.stash.editSummaries.push( props.editSummary );
946953 this.stash.fns.push( props.modFn );
947954
948955 this.saveAllButton.show();
949956 this.cancelAllButton.show();
950957
951958 // Clear input field after action
952 - that.addContainer.find( '.mw-addcategory-input' ).val( '' );
 959+ ajaxcat.addContainer.find( '.mw-addcategory-input' ).val( '' );
953960
954961 // This only does visual changes, fire done and return.
955962 props.doneFn( true );
@@ -957,7 +964,7 @@
958965
959966 // Summary of the action to be taken
960967 summaryHolder = $( '<p>' )
961 - .html( '<strong>' + mw.msg( 'ajax-category-question' ) + '</strong><br/>' + props.actionSummary );
 968+ .html( '<strong>' + mw.msg( 'ajax-category-question' ) + '</strong><br/>' + props.dialogDescription );
962969
963970 // Reason textbox.
964971 reasonBox = $( '<input type="text" size="45"></input>' )
@@ -972,19 +979,19 @@
973980
974981 // Submit button
975982 submitFunction = function() {
976 - that.addProgressIndicator( dialog );
977 - that.doEdit(
 983+ ajaxcat.addProgressIndicator( dialog );
 984+ ajaxcat.doEdit(
978985 mw.config.get( 'wgPageName' ),
979986 props.modFn,
980 - props.shortSummary + ': ' + reasonBox.val(),
 987+ props.editSummary + ': ' + reasonBox.val(),
981988 function() {
982989 props.doneFn();
983990
984991 // Clear input field after successful edit
985 - that.addContainer.find( '.mw-addcategory-input' ).val( '' );
 992+ ajaxcat.addContainer.find( '.mw-addcategory-input' ).val( '' );
986993
987994 dialog.dialog( 'close' );
988 - that.removeProgressIndicator( dialog );
 995+ ajaxcat.removeProgressIndicator( dialog );
989996 }
990997 );
991998 };
@@ -1012,13 +1019,13 @@
10131020
10141021 try {
10151022 delete this.stash.fns[i];
1016 - delete this.stash.summaries[i];
 1023+ delete this.stash.dialogDescriptions[i];
10171024 } catch(e) {}
10181025
10191026 if ( $.isEmpty( this.stash.fns ) ) {
10201027 this.stash.fns = [];
1021 - this.stash.summaries = [];
1022 - this.stash.shortSum = [];
 1028+ this.stash.dialogDescriptions = [];
 1029+ this.stash.editSummaries = [];
10231030 this.saveAllButton.hide();
10241031 this.cancelAllButton.hide();
10251032 }
@@ -1034,14 +1041,14 @@
10351042 resetAll: function( del ) {
10361043 var $links = this.options.$container.find( this.options.categoryLinkSelector ),
10371044 $del = $([]),
1038 - that = this;
 1045+ ajaxcat = this;
10391046
10401047 if ( del ) {
10411048 $del = $links.filter( '.mw-removed-category' ).parent();
10421049 }
10431050
10441051 $links.each( function() {
1045 - that.resetCatLink( $( this ), false, del );
 1052+ ajaxcat.resetCatLink( $( this ), false, del );
10461053 } );
10471054
10481055 $del.remove();

Follow-up revisions

RevisionCommit summaryAuthorDate
r94270Solve undefined-message problem by removing it all together. I've moved the ....krinkle19:14, 11 August 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r93351AjaxCategories rewrite:...krinkle00:43, 28 July 2011

Comments

#Comment by Catrope (talk | contribs)   09:10, 12 August 2011
+				// Redirect existence as well (non-existant pages can't be redirects)
+				mw.Title.exist.set( catTitle.toString(), true );

That's wrong. You're setting the existence of the redirect target, not the redirect itself. Redirects themselves can't be nonexistent, but redirect targets sure can. The logic in the entire function is backwards anyway: exists will reflect whether the redirect target exists, in case of redirect resolution.

-		// Readd static.
+		// Read static.

I'm pretty sure that wasn't a typo and was supposed to say 'readd' (as in 'add again') or maybe 're-add', but not 'read'.

#Comment by Krinkle (talk | contribs)   11:36, 12 August 2011

Fixed in r94338.

#Comment by Krinkle (talk | contribs)   20:56, 3 October 2011

AjaxCategories moved out of core. Marking deferred for now.

Status & tagging log