Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js |
— | — | @@ -21,25 +21,20 @@ |
22 | 22 | categoryLinkSelector: 'li a:not(.icon)', |
23 | 23 | multiEdit: $.inArray( 'user', mw.config.get( 'wgUserGroups' ) ) !== -1, |
24 | 24 | resolveRedirects: true |
25 | | - }; |
| 25 | + }, |
| 26 | + isCatNsSensitive = $.inArray( 14, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1; |
26 | 27 | |
| 28 | + /** |
| 29 | + * @return {String} |
| 30 | + */ |
27 | 31 | function clean( s ) { |
28 | | - if ( s !== undefined ) { |
| 32 | + if ( typeof s === 'string' ) { |
29 | 33 | return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '' ); |
30 | 34 | } |
| 35 | + return ''; |
31 | 36 | } |
32 | 37 | |
33 | 38 | /** |
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 | | - /** |
44 | 39 | * Helper function for $.fn.suggestions |
45 | 40 | * |
46 | 41 | * @context {jQuery} |
— | — | @@ -62,15 +57,11 @@ |
63 | 58 | dataType: 'json', |
64 | 59 | success: function( data ) { |
65 | 60 | // 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 | + } ); |
69 | 65 | |
70 | | - $.each( pages, function( i, page ) { |
71 | | - title = page.title.split( ':', 2 )[1]; |
72 | | - titleArr.push( title ); |
73 | | - } ); |
74 | | - |
75 | 66 | $el.suggestions( 'suggestions', titleArr ); |
76 | 67 | } |
77 | 68 | } ); |
— | — | @@ -78,32 +69,33 @@ |
79 | 70 | } |
80 | 71 | |
81 | 72 | /** |
82 | | - * Replace <nowiki> and comments with unique keys |
| 73 | + * Replace <nowiki> and comments with unique keys in the page text. |
83 | 74 | * |
84 | 75 | * @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. |
87 | 78 | * @return {String} |
88 | 79 | */ |
89 | 80 | function replaceNowikis( text, id, keys ) { |
90 | 81 | var matches = text.match( /(<nowiki\>[\s\S]*?<\/nowiki>|<\!--[\s\S]*?--\>)/g ); |
91 | 82 | for ( var i = 0; matches && i < matches.length; i++ ) { |
92 | 83 | keys[i] = matches[i]; |
93 | | - text = text.replace( matches[i], id + i + '-' ); |
| 84 | + text = text.replace( matches[i], '' + id + '-' + i ); |
94 | 85 | } |
95 | 86 | return text; |
96 | 87 | } |
97 | 88 | |
98 | 89 | /** |
99 | | - * Restore <nowiki> and comments from unique keys |
| 90 | + * Restore <nowiki> and comments from unique keys in the page text. |
| 91 | + * |
100 | 92 | * @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. |
103 | 95 | * @return {String} |
104 | 96 | */ |
105 | 97 | function restoreNowikis( text, id, keys ) { |
106 | 98 | for ( var i = 0; i < keys.length; i++ ) { |
107 | | - text = text.replace( id + i + '-', keys[i] ); |
| 99 | + text = text.replace( '' + id + '-' + i, keys[i] ); |
108 | 100 | } |
109 | 101 | return text; |
110 | 102 | } |
— | — | @@ -118,9 +110,6 @@ |
119 | 111 | */ |
120 | 112 | function makeCaseInsensitive( string ) { |
121 | 113 | var newString = ''; |
122 | | - if ( $.inArray( 14, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) { |
123 | | - return string; |
124 | | - } |
125 | 114 | for ( var i = 0; i < string.length; i++ ) { |
126 | 115 | newString += '[' + string.charAt( i ).toUpperCase() + string.charAt( i ).toLowerCase() + ']'; |
127 | 116 | } |
— | — | @@ -141,13 +130,16 @@ |
142 | 131 | // Filter out all names for category namespace |
143 | 132 | categoryNSFragment = $.map( mw.config.get( 'wgNamespaceIds' ), function( id, name ) { |
144 | 133 | if ( id === catNsId ) { |
145 | | - return makeCaseInsensitive( $.escapeRE( name ) ); |
| 134 | + name = $.escapeRE( name ); |
| 135 | + return !isCatNsSensitive ? makeCaseInsensitive( name ) : name; |
146 | 136 | } |
| 137 | + // Otherwise don't include in categoryNSFragment |
| 138 | + return null; |
147 | 139 | } ).join( '|' ); |
148 | 140 | |
149 | 141 | firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']'; |
150 | 142 | titleFragment = firstChar + titleFragment.substr( 1 ); |
151 | | - categoryRegex = '\\[\\[(' + categoryNSFragment + '):' + '[ _]*' + titleFragment + '(\\|[^\\]]*)?\\]\\]'; |
| 143 | + categoryRegex = '\\[\\[(' + categoryNSFragment + ')' + '[ _]*' + ':' + '[ _]*' + titleFragment + '[ _]*' + '(\\|[^\\]]*)?\\]\\]'; |
152 | 144 | if ( matchLineBreak ) { |
153 | 145 | categoryRegex += '[ \\t\\r]*\\n?'; |
154 | 146 | } |
— | — | @@ -160,7 +152,7 @@ |
161 | 153 | * @param icon {String} The icon class. |
162 | 154 | * @param title {String} Title attribute. |
163 | 155 | * @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. |
165 | 157 | * @return {jQuery} The button. |
166 | 158 | */ |
167 | 159 | function createButton( icon, title, className, text ){ |
— | — | @@ -172,7 +164,7 @@ |
173 | 165 | |
174 | 166 | if ( text ) { |
175 | 167 | var $icon = $( '<span>' ).addClass( 'icon ' + icon ).html( '​' ); |
176 | | - $button.addClass( 'icon-parent' ).append( $icon ).append( text ); |
| 168 | + $button.addClass( 'icon-parent' ).append( $icon ).append( mw.html.escape( text ) ); |
177 | 169 | } else { |
178 | 170 | $button.addClass( 'icon ' + icon ); |
179 | 171 | } |
— | — | @@ -188,7 +180,7 @@ |
189 | 181 | this.options = options = $.extend( defaultOptions, options ); |
190 | 182 | |
191 | 183 | // Save scope in shortcut |
192 | | - var that = this; |
| 184 | + var ajaxcat = this; |
193 | 185 | |
194 | 186 | // Elements tied to this instance |
195 | 187 | this.saveAllButton = null; |
— | — | @@ -199,8 +191,8 @@ |
200 | 192 | |
201 | 193 | // Stash and hooks |
202 | 194 | this.stash = { |
203 | | - summaries: [], |
204 | | - shortSum: [], |
| 195 | + dialogDescriptions: [], |
| 196 | + editSummaries: [], |
205 | 197 | fns: [] |
206 | 198 | }; |
207 | 199 | this.hooks = { |
— | — | @@ -226,8 +218,8 @@ |
227 | 219 | categoryText = $.ucFirst( $el.parent().find( '.mw-addcategory-input' ).val() || '' ); |
228 | 220 | |
229 | 221 | // 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 ); |
232 | 224 | } ); |
233 | 225 | }; |
234 | 226 | |
— | — | @@ -239,25 +231,33 @@ |
240 | 232 | var $el = $( this ), |
241 | 233 | $link = $el.data( 'link' ), |
242 | 234 | 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' ) |
246 | 238 | ); |
247 | 239 | |
248 | 240 | $link.after( $input ).hide(); |
249 | 241 | |
250 | 242 | $input.find( '.mw-addcategory-input' ).focus(); |
251 | 243 | |
| 244 | + // Get the editButton associated with this category link, |
| 245 | + // and hide it. |
252 | 246 | $link.data( 'editButton' ).hide(); |
253 | 247 | |
| 248 | + // Get the deleteButton associated with this category link, |
254 | 249 | $link.data( 'deleteButton' ) |
| 250 | + // (re)set click handler |
255 | 251 | .unbind( 'click' ) |
256 | 252 | .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 |
257 | 257 | $input.remove(); |
258 | 258 | $link.show().data( 'editButton' ).show(); |
259 | 259 | $( this ) |
260 | 260 | .unbind( 'click' ) |
261 | | - .click( that.handleDeleteLink ) |
| 261 | + .click( ajaxcat.handleDeleteLink ) |
262 | 262 | .attr( 'title', mw.msg( 'ajax-remove-category' ) ); |
263 | 263 | }) |
264 | 264 | .attr( 'title', mw.msg( 'ajax-cancel' ) ); |
— | — | @@ -270,36 +270,35 @@ |
271 | 271 | * @param e {jQuery Event} |
272 | 272 | */ |
273 | 273 | this.handleEditLink = function( e ) { |
274 | | - var categoryNew, |
| 274 | + var input, category, categoryOld, |
| 275 | + sortkey = '', // Wikitext for between '[[Category:Foo' and ']]'. |
275 | 276 | $el = $( this ), |
276 | | - $link = $el.parent().parent().find( 'a:not(.icon)' ), |
277 | | - sortkey = ''; |
| 277 | + $link = $el.parent().parent().find( 'a:not(.icon)' ); |
278 | 278 | |
279 | 279 | // 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(); |
282 | 281 | |
283 | 282 | // Strip sortkey |
284 | | - var arr = categoryNew.split( '|' ); |
| 283 | + var arr = input.split( '|', 2 ); |
285 | 284 | if ( arr.length > 1 ) { |
286 | | - categoryNew = arr.shift(); |
287 | | - sortkey = '|' + arr.join( '|' ); |
| 285 | + category = arr[0]; |
| 286 | + sortkey = arr[1]; |
288 | 287 | } |
289 | 288 | |
290 | 289 | // 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(); |
294 | 293 | |
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 ) ) { |
297 | 296 | $link.data( 'deleteButton' ).click(); |
298 | 297 | return; |
299 | 298 | } |
300 | 299 | |
301 | 300 | // 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 ); |
304 | 303 | }); |
305 | 304 | }; |
306 | 305 | |
— | — | @@ -316,19 +315,19 @@ |
317 | 316 | |
318 | 317 | if ( $link.is( '.mw-added-category, .mw-changed-category' ) ) { |
319 | 318 | // 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' ) ); |
321 | 320 | return; |
322 | 321 | } else if ( $link.is( '.mw-removed-category' ) ) { |
323 | 322 | // It's already removed... |
324 | 323 | return; |
325 | 324 | } |
326 | | - that.handleCategoryDelete( $link, category ); |
| 325 | + ajaxcat.handleCategoryDelete( $link, category ); |
327 | 326 | }; |
328 | 327 | |
329 | 328 | /** |
330 | 329 | * When multiEdit mode is enabled, |
331 | 330 | * this is called when the user clicks "save all" |
332 | | - * Combines the summaries and edit functions. |
| 331 | + * Combines the dialogDescriptions and edit functions. |
333 | 332 | * |
334 | 333 | * @context Element |
335 | 334 | * @return ? |
— | — | @@ -336,28 +335,28 @@ |
337 | 336 | this.handleStashedCategories = function() { |
338 | 337 | |
339 | 338 | // Remove "holes" in array |
340 | | - var summary = $.grep( that.stash.summaries, function( n, i ) { |
| 339 | + var dialogDescriptions = $.grep( ajaxcat.stash.dialogDescriptions, function( n, i ) { |
341 | 340 | return n; |
342 | 341 | } ); |
343 | 342 | |
344 | | - if ( summary.length < 1 ) { |
| 343 | + if ( dialogDescriptions.length < 1 ) { |
345 | 344 | // Nothing to do here. |
346 | | - that.saveAllButton.hide(); |
347 | | - that.cancelAllButton.hide(); |
| 345 | + ajaxcat.saveAllButton.hide(); |
| 346 | + ajaxcat.cancelAllButton.hide(); |
348 | 347 | return; |
349 | 348 | } else { |
350 | | - summary = summary.join( '<br/>' ); |
| 349 | + dialogDescriptions = dialogDescriptions.join( '<br/>' ); |
351 | 350 | } |
352 | 351 | |
353 | 352 | // Remove "holes" in array |
354 | | - var summaryShort = $.grep( that.stash.shortSum, function( n,i ) { |
| 353 | + var summaryShort = $.grep( ajaxcat.stash.editSummaries, function( n,i ) { |
355 | 354 | return n; |
356 | 355 | } ); |
357 | 356 | summaryShort = summaryShort.join( ', ' ); |
358 | 357 | |
359 | | - var fns = that.stash.fns; |
| 358 | + var fns = ajaxcat.stash.fns; |
360 | 359 | |
361 | | - that.doConfirmEdit( { |
| 360 | + ajaxcat.doConfirmEdit( { |
362 | 361 | modFn: function( oldtext ) { |
363 | 362 | // Run the text through all action functions |
364 | 363 | var newtext = oldtext; |
— | — | @@ -371,10 +370,10 @@ |
372 | 371 | } |
373 | 372 | return newtext; |
374 | 373 | }, |
375 | | - actionSummary: summary, |
376 | | - shortSummary: summaryShort, |
| 374 | + dialogDescription: dialogDescriptions, |
| 375 | + editSummary: summaryShort, |
377 | 376 | doneFn: function() { |
378 | | - that.resetAll( true ); |
| 377 | + ajaxcat.resetAll( true ); |
379 | 378 | }, |
380 | 379 | $link: null, |
381 | 380 | action: 'all' |
— | — | @@ -398,7 +397,7 @@ |
399 | 398 | return; |
400 | 399 | } |
401 | 400 | var options = this.options, |
402 | | - that = this, |
| 401 | + ajaxcat = this, |
403 | 402 | // Create [Add Category] link |
404 | 403 | $addLink = createButton( 'icon-add', |
405 | 404 | mw.msg( 'ajax-add-category' ), |
— | — | @@ -414,8 +413,8 @@ |
415 | 414 | this.addContainer.prepend( $addLink ); |
416 | 415 | |
417 | 416 | // 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 ) ); |
420 | 419 | }); |
421 | 420 | |
422 | 421 | options.$containerNormal.append( this.addContainer ); |
— | — | @@ -433,7 +432,7 @@ |
434 | 433 | ); |
435 | 434 | this.saveAllButton.click( this.handleStashedCategories ).hide(); |
436 | 435 | this.cancelAllButton.click( function() { |
437 | | - that.resetAll( false ); |
| 436 | + ajaxcat.resetAll( false ); |
438 | 437 | } ).hide(); |
439 | 438 | options.$containerNormal.append( this.saveAllButton ).append( this.cancelAllButton ); |
440 | 439 | options.$container.append( this.addContainer ); |
— | — | @@ -442,25 +441,22 @@ |
443 | 442 | /** |
444 | 443 | * Insert a newly added category into the DOM. |
445 | 444 | * |
446 | | - * @param cat {String} Category name. |
| 445 | + * @param catTitle {mw.Title} Category title for which a link should be created. |
447 | 446 | * @return {jQuery} |
448 | 447 | */ |
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(); |
454 | 450 | |
455 | | - if ( $.isEmpty( cat ) || this.containsCat( cat ) ) { |
| 451 | + if ( this.containsCat( catName ) ) { |
456 | 452 | return; |
457 | 453 | } |
458 | 454 | |
459 | 455 | var $catLinkWrapper = $( this.options.catLinkWrapper ), |
460 | 456 | $anchor = $( '<a>' ) |
461 | | - .append( cat ) |
| 457 | + .text( catName ) |
462 | 458 | .attr( { |
463 | 459 | target: '_blank', |
464 | | - href: catUrl( cat ) |
| 460 | + href: catTitle.getUrl() |
465 | 461 | } ); |
466 | 462 | |
467 | 463 | $catLinkWrapper.append( $anchor ); |
— | — | @@ -481,7 +477,7 @@ |
482 | 478 | var $promptContainer = $( '<div class="mw-addcategory-prompt"></div>' ), |
483 | 479 | $promptTextbox = $( '<input type="text" size="30" class="mw-addcategory-input"></input>' ), |
484 | 480 | $addButton = $( '<input type="button" class="mw-addcategory-button"></input>' ), |
485 | | - that = this; |
| 481 | + ajaxcat = this; |
486 | 482 | |
487 | 483 | if ( prefill !== '' ) { |
488 | 484 | $promptTextbox.val( prefill ); |
— | — | @@ -517,33 +513,27 @@ |
518 | 514 | }, |
519 | 515 | |
520 | 516 | /** |
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) |
524 | 522 | * @param noAppend |
525 | | - * @param exists |
526 | 523 | * @return {mw.ajaxCategories} |
527 | 524 | */ |
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(); |
533 | 531 | |
534 | | - if ( arr.length > 1 ) { |
535 | | - category = arr.shift(); |
536 | | - sortkey = '|' + arr.join( '|' ); |
537 | | - if ( sortkey === '|' ) { |
538 | | - sortkey = ''; |
539 | | - } |
540 | | - } |
541 | | - |
542 | 532 | if ( !$link.length ) { |
543 | | - $link = this.createCatLink( category ); |
| 533 | + $link = this.createCatLink( catTitle ); |
544 | 534 | } |
545 | 535 | |
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 ) ); |
548 | 538 | return this; |
549 | 539 | } |
550 | 540 | |
— | — | @@ -555,32 +545,28 @@ |
556 | 546 | } |
557 | 547 | |
558 | 548 | // Mark red if missing |
559 | | - $link.toggleClass( 'new', exists !== true ); |
| 549 | + $link[(catTitle.exists() === false ? 'addClass' : 'removeClass')]( 'new' ); |
560 | 550 | |
561 | | - // Replace underscores |
562 | | - category = category.replace( /_/g, ' ' ); |
563 | | - var catFull = new mw.Title( category, catNsId ).toString().replace( /_/g, ' ' ); |
564 | | - |
565 | 551 | this.doConfirmEdit( { |
566 | 552 | 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 ); |
570 | 556 | }, |
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 + ']]', |
573 | 559 | doneFn: function( unsaved ) { |
574 | 560 | if ( !noAppend ) { |
575 | | - that.options.$container |
| 561 | + ajaxcat.options.$container |
576 | 562 | .find( '#mw-normal-catlinks > .mw-addcategory-prompt' ).children( 'input' ).hide(); |
577 | | - that.options.$container |
| 563 | + ajaxcat.options.$container |
578 | 564 | .find( '#mw-normal-catlinks ul' ).append( $link.parent() ); |
579 | 565 | } else { |
580 | 566 | // Remove input box & button |
581 | 567 | $link.data( 'deleteButton' ).click(); |
582 | 568 | |
583 | 569 | // Update link text and href |
584 | | - $link.show().text( category ).attr( 'href', catUrl( category ) ); |
| 570 | + $link.show().text( catName ).attr( 'href', catTitle.getUrl() ); |
585 | 571 | } |
586 | 572 | if ( unsaved ) { |
587 | 573 | $link.addClass( 'mw-added-category' ); |
— | — | @@ -595,69 +581,71 @@ |
596 | 582 | |
597 | 583 | /** |
598 | 584 | * 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 |
605 | 591 | */ |
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(); |
608 | 595 | |
609 | 596 | // Category add needs to be handled differently |
610 | | - if ( added ) { |
| 597 | + if ( isAdded ) { |
611 | 598 | // Pass sortkey back |
612 | | - that.handleCategoryAdd( $link, categoryNew + sortkeyNew, true ); |
| 599 | + this.handleCategoryAdd( $link, catTitle, catSortkey, true ); |
613 | 600 | return; |
614 | 601 | } |
615 | 602 | |
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 ) { |
618 | 606 | $link.data( 'deleteButton' ).click(); |
619 | 607 | return; |
620 | 608 | } |
621 | 609 | |
622 | 610 | // Mark red if missing |
623 | | - $link[(exists === false ? 'addClass' : 'removeClass')]( 'new' ); |
| 611 | + $link[(catTitle.exists() === false ? 'addClass' : 'removeClass')]( 'new' ); |
624 | 612 | |
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({ |
628 | 617 | modFn: function( oldText ) { |
629 | | - var sortkey, newCategoryString, |
630 | | - newText = that.runHooks( oldText, 'beforeChange', category, categoryNew ), |
| 618 | + var newText = ajaxcat.runHooks( oldText, 'beforeChange', oldCatName, catName ), |
631 | 619 | matches = newText.match( categoryRegex ); |
632 | 620 | |
633 | | - //Old cat wasn't found, likely to be transcluded |
| 621 | + // Old cat wasn't found, likely to be transcluded |
634 | 622 | if ( !$.isArray( matches ) ) { |
635 | | - that.showError( mw.msg( 'ajax-edit-category-error' ) ); |
| 623 | + ajaxcat.showError( mw.msg( 'ajax-edit-category-error' ) ); |
636 | 624 | return false; |
637 | 625 | } |
638 | | - sortkey = sortkeyNew || matches[0].replace( categoryRegex, '$2' ); |
639 | | - newCategoryString = '[[' + new mw.Title( categoryNew, catNsId ) + sortkey + ']]'; |
640 | 626 | |
| 627 | + var suffix = catSortkey ? '|' + catSortkey : matches[0].replace( categoryRegex, '$2' ), |
| 628 | + newCategoryWikitext = '[[' + catTitle + suffix + ']]'; |
| 629 | + |
641 | 630 | 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 |
644 | 632 | for ( var i = 1; i < matches.length; i++ ) { |
645 | 633 | oldText = oldText.replace( matches[i], '' ); |
646 | 634 | } |
647 | 635 | } |
648 | | - newText = oldText.replace( categoryRegex, newCategoryString ); |
| 636 | + newText = oldText.replace( categoryRegex, newCategoryWikitext ); |
649 | 637 | |
650 | | - return that.runHooks( newText, 'afterChange', category, categoryNew ); |
| 638 | + return ajaxcat.runHooks( newText, 'afterChange', oldCatName, catName ); |
651 | 639 | }, |
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, |
654 | 642 | doneFn: function( unsaved ) { |
655 | 643 | // Remove input box & button |
656 | 644 | $link.data( 'deleteButton' ).click(); |
657 | 645 | |
658 | 646 | // Update link text and href |
659 | | - $link.show().text( categoryNew ).attr( 'href', catUrl( categoryNew ) ); |
| 647 | + $link.show().text( catName ).attr( 'href', catTitle.getUrl() ); |
660 | 648 | if ( unsaved ) { |
661 | | - $link.data( 'origCat', category ).addClass( 'mw-changed-category' ); |
| 649 | + $link.data( 'origCat', oldCatName ).addClass( 'mw-changed-category' ); |
662 | 650 | } |
663 | 651 | }, |
664 | 652 | $link: $link, |
— | — | @@ -668,30 +656,36 @@ |
669 | 657 | /** |
670 | 658 | * Checks the API whether the category in question is a redirect. |
671 | 659 | * 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) |
674 | 662 | */ |
675 | 663 | resolveRedirects: function( category, callback ) { |
676 | 664 | if ( !this.options.resolveRedirects ) { |
677 | 665 | callback( category, true ); |
678 | 666 | return; |
679 | 667 | } |
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 | + }; |
686 | 675 | |
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 | + } ); |
696 | 690 | }, |
697 | 691 | |
698 | 692 | /** |
— | — | @@ -704,10 +698,10 @@ |
705 | 699 | var deleteButton = createButton( 'icon-close', mw.msg( 'ajax-remove-category' ) ), |
706 | 700 | editButton = createButton( 'icon-edit', mw.msg( 'ajax-edit-category' ) ), |
707 | 701 | saveButton = createButton( 'icon-tick', mw.msg( 'ajax-confirm-save' ) ).hide(), |
708 | | - that = this; |
| 702 | + ajaxcat = this; |
709 | 703 | |
710 | 704 | deleteButton.click( this.handleDeleteLink ); |
711 | | - editButton.click( that.createEditInterface ); |
| 705 | + editButton.click( ajaxcat.createEditInterface ); |
712 | 706 | |
713 | 707 | $element.after( deleteButton ).after( editButton ); |
714 | 708 | |
— | — | @@ -761,13 +755,20 @@ |
762 | 756 | /** |
763 | 757 | * Check whether a passed category is present in the DOM. |
764 | 758 | * |
| 759 | + * @param newCat {String} Category name to be checked for. |
765 | 760 | * @return {Boolean} |
766 | 761 | */ |
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; |
772 | 773 | }, |
773 | 774 | |
774 | 775 | /** |
— | — | @@ -779,22 +780,22 @@ |
780 | 781 | */ |
781 | 782 | handleCategoryDelete: function( $link, category ) { |
782 | 783 | var categoryRegex = buildRegex( category, true ), |
783 | | - that = this; |
| 784 | + ajaxcat = this; |
784 | 785 | |
785 | | - that.doConfirmEdit({ |
| 786 | + this.doConfirmEdit({ |
786 | 787 | modFn: function( oldText ) { |
787 | | - var newText = that.runHooks( oldText, 'beforeDelete', category ); |
| 788 | + var newText = ajaxcat.runHooks( oldText, 'beforeDelete', category ); |
788 | 789 | newText = newText.replace( categoryRegex, '' ); |
789 | 790 | |
790 | 791 | if ( newText === oldText ) { |
791 | | - that.showError( mw.msg( 'ajax-remove-category-error' ) ); |
| 792 | + ajaxcat.showError( mw.msg( 'ajax-remove-category-error' ) ); |
792 | 793 | return false; |
793 | 794 | } |
794 | 795 | |
795 | | - return that.runHooks( newText, 'afterDelete', category ); |
| 796 | + return ajaxcat.runHooks( newText, 'afterDelete', category ); |
796 | 797 | }, |
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 ) + ']]', |
799 | 800 | doneFn: function( unsaved ) { |
800 | 801 | if ( unsaved ) { |
801 | 802 | $link.addClass( 'mw-removed-category' ); |
— | — | @@ -824,17 +825,18 @@ |
825 | 826 | this.removeStashItem( data.stashIndex ); |
826 | 827 | } |
827 | 828 | if ( del ) { |
828 | | - $link.parent.remove(); |
| 829 | + $link.parent().remove(); |
829 | 830 | return; |
830 | 831 | } |
831 | 832 | 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() ); |
834 | 836 | } |
835 | 837 | |
836 | 838 | $link.removeData(); |
837 | 839 | |
838 | | - // Readd static. |
| 840 | + // Read static. |
839 | 841 | $link.data( { |
840 | 842 | saveButton: data.saveButton, |
841 | 843 | deleteButton: data.deleteButton, |
— | — | @@ -860,7 +862,7 @@ |
861 | 863 | titles: page, |
862 | 864 | rvprop: 'content|timestamp', |
863 | 865 | format: 'json' |
864 | | - }, that = this; |
| 866 | + }, ajaxcat = this; |
865 | 867 | |
866 | 868 | $.post( |
867 | 869 | mw.util.wikiScript( 'api' ), |
— | — | @@ -868,23 +870,23 @@ |
869 | 871 | function( reply ) { |
870 | 872 | var infos = reply.query.pages; |
871 | 873 | $.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 |
875 | 879 | |
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 ); |
880 | 882 | |
881 | | - // Then do the changes |
| 883 | + // ..then apply the changes to the page text.. |
882 | 884 | var newText = fn( oldText ); |
883 | 885 | if ( newText === false ) { |
884 | 886 | return; |
885 | 887 | } |
886 | 888 | |
887 | | - // And restore them back |
888 | | - newText = restoreNowikis( newText, key, nowiki ); |
| 889 | + // ..and restore the nowiki parts back. |
| 890 | + newText = restoreNowikis( newText, nowikiKey, nowikiFragments ); |
889 | 891 | |
890 | 892 | var postEditVars = { |
891 | 893 | action: 'edit', |
— | — | @@ -896,15 +898,20 @@ |
897 | 899 | format: 'json' |
898 | 900 | }; |
899 | 901 | |
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 ) ); |
903 | 910 | }); |
904 | 911 | } ); |
905 | 912 | }, |
906 | 913 | 'json' |
907 | 914 | ).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 ) ); |
909 | 916 | } ); |
910 | 917 | }, |
911 | 918 | |
— | — | @@ -915,8 +922,8 @@ |
916 | 923 | * |
917 | 924 | * @param props {Object}: |
918 | 925 | * - 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) |
921 | 928 | * - doneFn {Function} callback after everything is done |
922 | 929 | * - $link {jQuery} |
923 | 930 | * - action |
— | — | @@ -930,7 +937,7 @@ |
931 | 938 | buttons: buttons, |
932 | 939 | width: 450 |
933 | 940 | }, |
934 | | - that = this; |
| 941 | + ajaxcat = this; |
935 | 942 | |
936 | 943 | // Check whether to use multiEdit mode: |
937 | 944 | if ( this.options.multiEdit && props.action !== 'all' ) { |
— | — | @@ -938,17 +945,17 @@ |
939 | 946 | // Stash away |
940 | 947 | props.$link |
941 | 948 | .data( 'stashIndex', this.stash.fns.length ) |
942 | | - .data( 'summary', props.actionSummary ); |
| 949 | + .data( 'summary', props.dialogDescription ); |
943 | 950 | |
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 ); |
946 | 953 | this.stash.fns.push( props.modFn ); |
947 | 954 | |
948 | 955 | this.saveAllButton.show(); |
949 | 956 | this.cancelAllButton.show(); |
950 | 957 | |
951 | 958 | // Clear input field after action |
952 | | - that.addContainer.find( '.mw-addcategory-input' ).val( '' ); |
| 959 | + ajaxcat.addContainer.find( '.mw-addcategory-input' ).val( '' ); |
953 | 960 | |
954 | 961 | // This only does visual changes, fire done and return. |
955 | 962 | props.doneFn( true ); |
— | — | @@ -957,7 +964,7 @@ |
958 | 965 | |
959 | 966 | // Summary of the action to be taken |
960 | 967 | 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 ); |
962 | 969 | |
963 | 970 | // Reason textbox. |
964 | 971 | reasonBox = $( '<input type="text" size="45"></input>' ) |
— | — | @@ -972,19 +979,19 @@ |
973 | 980 | |
974 | 981 | // Submit button |
975 | 982 | submitFunction = function() { |
976 | | - that.addProgressIndicator( dialog ); |
977 | | - that.doEdit( |
| 983 | + ajaxcat.addProgressIndicator( dialog ); |
| 984 | + ajaxcat.doEdit( |
978 | 985 | mw.config.get( 'wgPageName' ), |
979 | 986 | props.modFn, |
980 | | - props.shortSummary + ': ' + reasonBox.val(), |
| 987 | + props.editSummary + ': ' + reasonBox.val(), |
981 | 988 | function() { |
982 | 989 | props.doneFn(); |
983 | 990 | |
984 | 991 | // Clear input field after successful edit |
985 | | - that.addContainer.find( '.mw-addcategory-input' ).val( '' ); |
| 992 | + ajaxcat.addContainer.find( '.mw-addcategory-input' ).val( '' ); |
986 | 993 | |
987 | 994 | dialog.dialog( 'close' ); |
988 | | - that.removeProgressIndicator( dialog ); |
| 995 | + ajaxcat.removeProgressIndicator( dialog ); |
989 | 996 | } |
990 | 997 | ); |
991 | 998 | }; |
— | — | @@ -1012,13 +1019,13 @@ |
1013 | 1020 | |
1014 | 1021 | try { |
1015 | 1022 | delete this.stash.fns[i]; |
1016 | | - delete this.stash.summaries[i]; |
| 1023 | + delete this.stash.dialogDescriptions[i]; |
1017 | 1024 | } catch(e) {} |
1018 | 1025 | |
1019 | 1026 | if ( $.isEmpty( this.stash.fns ) ) { |
1020 | 1027 | this.stash.fns = []; |
1021 | | - this.stash.summaries = []; |
1022 | | - this.stash.shortSum = []; |
| 1028 | + this.stash.dialogDescriptions = []; |
| 1029 | + this.stash.editSummaries = []; |
1023 | 1030 | this.saveAllButton.hide(); |
1024 | 1031 | this.cancelAllButton.hide(); |
1025 | 1032 | } |
— | — | @@ -1034,14 +1041,14 @@ |
1035 | 1042 | resetAll: function( del ) { |
1036 | 1043 | var $links = this.options.$container.find( this.options.categoryLinkSelector ), |
1037 | 1044 | $del = $([]), |
1038 | | - that = this; |
| 1045 | + ajaxcat = this; |
1039 | 1046 | |
1040 | 1047 | if ( del ) { |
1041 | 1048 | $del = $links.filter( '.mw-removed-category' ).parent(); |
1042 | 1049 | } |
1043 | 1050 | |
1044 | 1051 | $links.each( function() { |
1045 | | - that.resetCatLink( $( this ), false, del ); |
| 1052 | + ajaxcat.resetCatLink( $( this ), false, del ); |
1046 | 1053 | } ); |
1047 | 1054 | |
1048 | 1055 | $del.remove(); |