r96252 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r96251‎ | r96252 | r96253 >
Date:20:32, 4 September 2011
Author:reedy
Status:ok
Tags:
Comment:
Delete uppercased files
Modified paths:
  • /trunk/extensions/InlineCategorizer/modules/ext.InlineCategorizer.core.css (deleted) (history)
  • /trunk/extensions/InlineCategorizer/modules/ext.InlineCategorizer.core.js (deleted) (history)
  • /trunk/extensions/InlineCategorizer/modules/ext.InlineCategorizer.init.js (deleted) (history)

Diff [purge]

Index: trunk/extensions/InlineCategorizer/modules/ext.InlineCategorizer.init.js
@@ -1,9 +0,0 @@
2 -/**
3 - * Initialize an instance of InlineCategorizer into mw.page
4 - */
5 -mw.page.inlineCategorizer = new mw.InlineCategorizer();
6 -jQuery( document ).ready( function(){
7 - // Separate function for call to prevent jQuery
8 - // from executing it in the document context.
9 - mw.page.inlineCategorizer.setup();
10 -} );
Index: trunk/extensions/InlineCategorizer/modules/ext.InlineCategorizer.core.css
@@ -1,70 +0,0 @@
2 -.mw-addcategory-prompt {
3 - display: inline;
4 -}
5 -
6 -.mw-addcategory-prompt input {
7 - margin-left: 0.5em;
8 - margin-right: 0.5em;
9 -}
10 -
11 -.mw-remove-category {
12 - padding: 2px 8px;
13 - display: inline;
14 -}
15 -.mw-removed-category {
16 - text-decoration: line-through;
17 -}
18 -
19 -#catlinks:hover .icon {
20 - opacity: 1;
21 -}
22 -#catlinks ul {
23 - margin-right: 2em;
24 -}
25 -
26 -.mw-ajax-addcategory-holder {
27 - display: inline-block;
28 -}
29 -.mw-ajax-addcategory {
30 - margin-right: 1em;
31 - cursor: pointer;
32 - display: inline-block;
33 -}
34 -
35 -#catlinks .icon {
36 - cursor: pointer;
37 - padding: 1px 8px;
38 - margin: 0;
39 - background-position: 0 0;
40 - background-repeat: no-repeat;
41 - opacity: 0.5;
42 -
43 -}
44 -#catlinks .icon-parent {
45 - cursor: pointer;
46 - margin-right: 1em;
47 -}
48 -#catlinks .icon-close {
49 - /* @embed */ background-image: url(images/ajaxcat-close.png);
50 -}
51 -#catlinks .icon-edit {
52 - /* @embed */ background-image: url(images/ajaxcat-edit.png);
53 -}
54 -#catlinks .icon-tick {
55 - /* @embed */ background-image: url(images/ajaxcat-tick.png);
56 -}
57 -#catlinks .icon-add {
58 - /* @embed */ background-image: url(images/ajaxcat-add.png);
59 -}
60 -#catlinks .icon-close:hover {
61 - /* @embed */ background-image: url(images/ajaxcat-close-hover.png);
62 -}
63 -#catlinks .icon-edit:hover {
64 - /* @embed */ background-image: url(images/ajaxcat-edit-hover.png);
65 -}
66 -#catlinks .icon-tick:hover {
67 - /* @embed */ background-image: url(images/ajaxcat-tick-hover.png);
68 -}
69 -#catlinks .icon-add:hover {
70 - /* @embed */ background-image: url(images/ajaxcat-add-hover.png);
71 -}
Index: trunk/extensions/InlineCategorizer/modules/ext.InlineCategorizer.core.js
@@ -1,1152 +0,0 @@
2 -/**
3 - * The core of InlineCategorizer
4 - *
5 - * @author Michael Dale, 2009
6 - * @author Leo Koppelkamm, 2011
7 - * @author Timo Tijhof, 2011
8 - *
9 - * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds,
10 - * wgCaseSensitiveNamespaces, wgUserGroups), mw.util.wikiGetlink
11 - */
12 -( function( $ ) {
13 -
14 - /* Local scope */
15 -
16 - var catNsId = mw.config.get( 'wgNamespaceIds' ).category,
17 - defaultOptions = {
18 - catLinkWrapper: '<li>',
19 - $container: $( '.catlinks' ),
20 - $containerNormal: $( '#mw-normal-catlinks' ),
21 - categoryLinkSelector: 'li a:not(.icon)',
22 - multiEdit: $.inArray( 'user', mw.config.get( 'wgUserGroups' ) ) !== -1,
23 - resolveRedirects: true
24 - },
25 - isCatNsSensitive = $.inArray( 14, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1;
26 -
27 - /**
28 - * @return {String}
29 - */
30 - function clean( s ) {
31 - if ( typeof s === 'string' ) {
32 - return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '' );
33 - }
34 - return '';
35 - }
36 -
37 - /**
38 - * Generates a random id out of 62 alpha-numeric characters.
39 - *
40 - * @param {Number} Length of id (optional, defaults to 32)
41 - * @return {String}
42 - */
43 - function generateRandomId( idLength ) {
44 - idLength = typeof idLength === 'number' ? idLength : 32;
45 - var seed = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
46 - id = '';
47 - for ( var r, i = 0; i < idLength; i++ ) {
48 - r = Math.floor( Math.random() * seed.length );
49 - id += seed.substring( r, r + 1 );
50 - }
51 - return id;
52 - }
53 -
54 - /**
55 - * Helper function for $.fn.suggestions
56 - *
57 - * @context {jQuery}
58 - * @param value {String} Textbox value.
59 - */
60 - function fetchSuggestions( value ) {
61 - var request,
62 - $el = this,
63 - catName = clean( value );
64 -
65 - request = $.ajax( {
66 - url: mw.util.wikiScript( 'api' ),
67 - data: {
68 - action: 'query',
69 - list: 'allpages',
70 - apnamespace: catNsId,
71 - apprefix: catName,
72 - format: 'json'
73 - },
74 - dataType: 'json',
75 - success: function( data ) {
76 - // Process data.query.allpages into an array of titles
77 - var pages = data.query.allpages,
78 - titleArr = $.map( pages, function( page ) {
79 - return new mw.Title( page.title ).getMainText();
80 - } );
81 -
82 - $el.suggestions( 'suggestions', titleArr );
83 - }
84 - } );
85 - $el.data( 'suggestions-request', request );
86 - }
87 -
88 - /**
89 - * Replace <nowiki> and comments with unique keys in the page text.
90 - *
91 - * @param text {String}
92 - * @param id {String} Unique key for this nowiki replacement layer call.
93 - * @param keys {Array} Array where fragments will be stored in.
94 - * @return {String}
95 - */
96 - function replaceNowikis( text, id, keys ) {
97 - var matches = text.match( /(<nowiki\>[\s\S]*?<\/nowiki>|<\!--[\s\S]*?--\>)/g );
98 - for ( var i = 0; matches && i < matches.length; i++ ) {
99 - keys[i] = matches[i];
100 - text = text.replace( matches[i], '' + id + '-' + i );
101 - }
102 - return text;
103 - }
104 -
105 - /**
106 - * Restore <nowiki> and comments from unique keys in the page text.
107 - *
108 - * @param text {String}
109 - * @param id {String} Unique key of the layer to be restored, as passed to replaceNowikis().
110 - * @param keys {Array} Array where fragements should be fetched from.
111 - * @return {String}
112 - */
113 - function restoreNowikis( text, id, keys ) {
114 - for ( var i = 0; i < keys.length; i++ ) {
115 - text = text.replace( '' + id + '-' + i, keys[i] );
116 - }
117 - return text;
118 - }
119 -
120 - /**
121 - * Makes regex string caseinsensitive.
122 - * Useful when 'i' flag can't be used.
123 - * Return stuff like [Ff][Oo][Oo]
124 - *
125 - * @param string {String} Regex string
126 - * @return {String} Processed regex string
127 - */
128 - function makeCaseInsensitive( string ) {
129 - var newString = '';
130 - for ( var i = 0; i < string.length; i++ ) {
131 - newString += '[' + string.charAt( i ).toUpperCase() + string.charAt( i ).toLowerCase() + ']';
132 - }
133 - return newString;
134 - }
135 -
136 - /**
137 - * Build a regex that matches legal invocations of the passed category.
138 - * @param category {String}
139 - * @param matchLineBreak {Boolean} Match one following linebreak as well?
140 - * @return {RegExp}
141 - */
142 - function buildRegex( category, matchLineBreak ) {
143 - var categoryRegex, categoryNSFragment,
144 - titleFragment = $.escapeRE( category ).replace( /( |_)/g, '[ _]' ),
145 - firstChar = titleFragment.charAt( 0 );
146 -
147 - // Filter out all names for category namespace
148 - categoryNSFragment = $.map( mw.config.get( 'wgNamespaceIds' ), function( id, name ) {
149 - if ( id === catNsId ) {
150 - name = $.escapeRE( name );
151 - return !isCatNsSensitive ? makeCaseInsensitive( name ) : name;
152 - }
153 - // Otherwise don't include in categoryNSFragment
154 - return null;
155 - } ).join( '|' );
156 -
157 - firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']';
158 - titleFragment = firstChar + titleFragment.substr( 1 );
159 - categoryRegex = '\\[\\[(' + categoryNSFragment + ')' + '[ _]*' + ':' + '[ _]*' + titleFragment + '[ _]*' + '(\\|[^\\]]*)?\\]\\]';
160 - if ( matchLineBreak ) {
161 - categoryRegex += '[ \\t\\r]*\\n?';
162 - }
163 - return new RegExp( categoryRegex, 'g' );
164 - }
165 -
166 - /**
167 - * Manufacture iconed button, with or without text.
168 - *
169 - * @param icon {String} The icon class.
170 - * @param title {String} Title attribute.
171 - * @param className {String} (optional) Additional classes to be added to the button.
172 - * @param text {String} (optional) Text label of button.
173 - * @return {jQuery} The button.
174 - */
175 - function createButton( icon, title, className, text ){
176 - // We're adding a zero width space for IE7, it's got problems with empty nodes apparently
177 - var $button = $( '<a>' )
178 - .addClass( className || '' )
179 - .attr( 'title', title )
180 - .html( '&#8203;' );
181 -
182 - if ( text ) {
183 - var $icon = $( '<span>' ).addClass( 'icon ' + icon ).html( '&#8203;' );
184 - $button.addClass( 'icon-parent' ).append( $icon ).append( mw.html.escape( text ) );
185 - } else {
186 - $button.addClass( 'icon ' + icon );
187 - }
188 - return $button;
189 - }
190 -
191 -/**
192 - * mw.InlineCategorizer
193 - *
194 - * @constructor
195 - * @param options {Object}
196 - */
197 -mw.InlineCategorizer = function( options ) {
198 -
199 - this.options = options = $.extend( defaultOptions, options );
200 -
201 - // Save scope in shortcut
202 - var ajaxcat = this;
203 -
204 - // Elements tied to this instance
205 - this.saveAllButton = null;
206 - this.cancelAllButton = null;
207 - this.addContainer = null;
208 -
209 - this.request = null;
210 -
211 - // Stash and hooks
212 - this.stash = {
213 - dialogDescriptions: [],
214 - editSummaries: [],
215 - fns: []
216 - };
217 - this.hooks = {
218 - beforeAdd: [],
219 - beforeChange: [],
220 - beforeDelete: [],
221 - afterAdd: [],
222 - afterChange: [],
223 - afterDelete: []
224 - };
225 -
226 - /* Event handlers */
227 -
228 - /**
229 - * Handle add category submit. Not to be called directly.
230 - *
231 - * @context Element
232 - * @param e {jQuery Event}
233 - */
234 - this.handleAddLink = function( e ) {
235 - var $el = $( this ),
236 - $link = $([]),
237 - categoryText = $.ucFirst( $el.parent().find( '.mw-addcategory-input' ).val() || '' );
238 -
239 - // Resolve redirects
240 - ajaxcat.resolveRedirects( categoryText, function( resolvedCatTitle ) {
241 - ajaxcat.handleCategoryAdd( $link, resolvedCatTitle, '', false );
242 - } );
243 - };
244 -
245 - /**
246 - * @context Element
247 - * @param e {jQuery Event}
248 - */
249 - this.createEditInterface = function( e ) {
250 - var $el = $( this ),
251 - $link = $el.data( 'link' ),
252 - category = $link.text(),
253 - $input = ajaxcat.makeSuggestionBox( category,
254 - ajaxcat.handleEditLink,
255 - ajaxcat.options.multiEdit ? mw.msg( 'inlinecategorizer-confirm-ok' ) : mw.msg( 'inlinecategorizer-confirm-save' )
256 - );
257 -
258 - $link.after( $input ).hide();
259 -
260 - $input.find( '.mw-addcategory-input' ).focus();
261 -
262 - // Get the editButton associated with this category link,
263 - // and hide it.
264 - $link.data( 'editButton' ).hide();
265 -
266 - // Get the deleteButton associated with this category link,
267 - $link.data( 'deleteButton' )
268 - // (re)set click handler
269 - .unbind( 'click' )
270 - .click( function() {
271 - // When the delete button is clicked:
272 - // - Remove the suggestion box
273 - // - Show the link and it's edit button
274 - // - (re)set the click handler again
275 - $input.remove();
276 - $link.show().data( 'editButton' ).show();
277 - $( this )
278 - .unbind( 'click' )
279 - .click( ajaxcat.handleDeleteLink )
280 - .attr( 'title', mw.msg( 'inlinecategorizer-remove-category' ) );
281 - })
282 - .attr( 'title', mw.msg( 'inlinecategorizer-cancel' ) );
283 - };
284 -
285 - /**
286 - * Handle edit category submit. Not to be called directly.
287 - *
288 - * @context Element
289 - * @param e {jQuery Event}
290 - */
291 - this.handleEditLink = function( e ) {
292 - var input, category, sortkey, categoryOld,
293 - $el = $( this ),
294 - $link = $el.parent().parent().find( 'a:not(.icon)' );
295 -
296 - // Grab category text
297 - input = $el.parent().find( '.mw-addcategory-input' ).val();
298 -
299 - // Split categoryname and sortkey
300 - var arr = input.split( '|', 2 );
301 - category = arr[0];
302 - sortkey = arr[1]; // Is usually undefined, ie. if there was no '|' in the input.
303 -
304 - // Grab text
305 - var isAdded = $link.hasClass( 'mw-added-category' );
306 - ajaxcat.resetCatLink( $link );
307 - categoryOld = $link.text();
308 -
309 - // If something changed and the new cat is already on the page, delete it.
310 - if ( categoryOld !== category && ajaxcat.containsCat( category ) ) {
311 - $link.data( 'deleteButton' ).click();
312 - return;
313 - }
314 -
315 - // Resolve redirects
316 - ajaxcat.resolveRedirects( category, function( resolvedCatTitle ) {
317 - ajaxcat.handleCategoryEdit( $link, categoryOld, resolvedCatTitle, sortkey, isAdded );
318 - });
319 - };
320 -
321 - /**
322 - * Handle delete category submit. Not to be called directly.
323 - *
324 - * @context Element
325 - * @param e {jQuery Event}
326 - */
327 - this.handleDeleteLink = function( e ) {
328 - var $el = $( this ),
329 - $link = $el.parent().find( 'a:not(.icon)' ),
330 - category = $link.text();
331 -
332 - if ( $link.is( '.mw-added-category, .mw-changed-category' ) ) {
333 - // We're just cancelling the addition or edit
334 - ajaxcat.resetCatLink( $link, $link.hasClass( 'mw-added-category' ) );
335 - return;
336 - } else if ( $link.is( '.mw-removed-category' ) ) {
337 - // It's already removed...
338 - return;
339 - }
340 - ajaxcat.handleCategoryDelete( $link, category );
341 - };
342 -
343 - /**
344 - * When multiEdit mode is enabled,
345 - * this is called when the user clicks "save all"
346 - * Combines the dialogDescriptions and edit functions.
347 - *
348 - * @context Element
349 - * @return ?
350 - */
351 - this.handleStashedCategories = function() {
352 -
353 - // Remove "holes" in array
354 - var dialogDescriptions = $.grep( ajaxcat.stash.dialogDescriptions, function( n, i ) {
355 - return n;
356 - } );
357 -
358 - if ( dialogDescriptions.length < 1 ) {
359 - // Nothing to do here.
360 - ajaxcat.saveAllButton.hide();
361 - ajaxcat.cancelAllButton.hide();
362 - return;
363 - } else {
364 - dialogDescriptions = dialogDescriptions.join( '<br/>' );
365 - }
366 -
367 - // Remove "holes" in array
368 - var summaryShort = $.grep( ajaxcat.stash.editSummaries, function( n,i ) {
369 - return n;
370 - } );
371 - summaryShort = summaryShort.join( ', ' );
372 -
373 - var fns = ajaxcat.stash.fns;
374 -
375 - ajaxcat.doConfirmEdit( {
376 - modFn: function( oldtext ) {
377 - // Run the text through all action functions
378 - var newtext = oldtext;
379 - for ( var i = 0; i < fns.length; i++ ) {
380 - if ( $.isFunction( fns[i] ) ) {
381 - newtext = fns[i]( newtext );
382 - if ( newtext === false ) {
383 - return false;
384 - }
385 - }
386 - }
387 - return newtext;
388 - },
389 - dialogDescription: dialogDescriptions,
390 - editSummary: summaryShort,
391 - doneFn: function() {
392 - ajaxcat.resetAll( true );
393 - },
394 - $link: null,
395 - action: 'all'
396 - } );
397 - };
398 -};
399 -
400 -/* Public methods */
401 -
402 -mw.InlineCategorizer.prototype = {
403 - /**
404 - * Create the UI
405 - */
406 - setup: function() {
407 - // Only do it for articles.
408 - if ( !mw.config.get( 'wgIsArticle' ) ) {
409 - return;
410 - }
411 - var options = this.options,
412 - ajaxcat = this,
413 - // Create [Add Category] link
414 - $addLink = createButton( 'icon-add',
415 - mw.msg( 'inlinecategorizer-add-category' ),
416 - 'mw-ajax-addcategory',
417 - mw.msg( 'inlinecategorizer-add-category' )
418 - ).click( function() {
419 - $( this ).nextAll().toggle().filter( '.mw-addcategory-input' ).focus();
420 - });
421 -
422 - // Create add category prompt
423 - this.addContainer = this.makeSuggestionBox( '', this.handleAddLink, mw.msg( 'inlinecategorizer-add-category-submit' ) );
424 - this.addContainer.children().hide();
425 - this.addContainer.prepend( $addLink );
426 -
427 - // Create edit & delete link for each category.
428 - $( '#catlinks' ).find( 'li a' ).each( function() {
429 - ajaxcat.createCatButtons( $( this ) );
430 - });
431 -
432 - options.$containerNormal.append( this.addContainer );
433 -
434 - // @todo Make more clickable
435 - this.saveAllButton = createButton( 'icon-tick',
436 - mw.msg( 'inlinecategorizer-confirm-save-all' ),
437 - '',
438 - mw.msg( 'inlinecategorizer-confirm-save-all' )
439 - );
440 - this.cancelAllButton = createButton( 'icon-close',
441 - mw.msg( 'inlinecategorizer-cancel-all' ),
442 - '',
443 - mw.msg( 'inlinecategorizer-cancel-all' )
444 - );
445 - this.saveAllButton.click( this.handleStashedCategories ).hide();
446 - this.cancelAllButton.click( function() {
447 - ajaxcat.resetAll( false );
448 - } ).hide();
449 - options.$containerNormal.append( this.saveAllButton ).append( this.cancelAllButton );
450 - options.$container.append( this.addContainer );
451 - },
452 -
453 - /**
454 - * Insert a newly added category into the DOM.
455 - *
456 - * @param catTitle {mw.Title} Category title for which a link should be created.
457 - * @return {jQuery}
458 - */
459 - createCatLink: function( catTitle ) {
460 - var catName = catTitle.getMainText(),
461 - $catLinkWrapper = $( this.options.catLinkWrapper ),
462 - $anchor = $( '<a>' )
463 - .text( catName )
464 - .attr( {
465 - target: '_blank',
466 - href: catTitle.getUrl()
467 - } );
468 -
469 - $catLinkWrapper.append( $anchor );
470 -
471 - this.createCatButtons( $anchor );
472 -
473 - return $anchor;
474 - },
475 -
476 - /**
477 - * Create a suggestion box for use in edit/add dialogs
478 - * @param prefill {String} Prefill input
479 - * @param callback {Function} Called on submit
480 - * @param buttonVal {String} Button text
481 - */
482 - makeSuggestionBox: function( prefill, callback, buttonVal ) {
483 - // Create add category prompt
484 - var $promptContainer = $( '<div class="mw-addcategory-prompt"></div>' ),
485 - $promptTextbox = $( '<input type="text" size="30" class="mw-addcategory-input"></input>' ),
486 - $addButton = $( '<input type="button" class="mw-addcategory-button"></input>' ),
487 - ajaxcat = this;
488 -
489 - if ( prefill !== '' ) {
490 - $promptTextbox.val( prefill );
491 - }
492 -
493 - $addButton
494 - .val( buttonVal )
495 - .click( callback );
496 -
497 - $promptTextbox
498 - .keyup( function( e ) {
499 - if ( e.keyCode === 13 ) {
500 - $addButton.click();
501 - }
502 - } )
503 - .suggestions( {
504 - fetch: fetchSuggestions,
505 - cancel: function() {
506 - var req = this.data( 'suggestions-request' );
507 - // XMLHttpRequest.abort is unimplemented in IE6, also returns nonstandard value of 'unknown' for typeof
508 - if ( req && typeof req.abort !== 'unknown' && typeof req.abort !== 'undefined' && req.abort ) {
509 - req.abort();
510 - }
511 - }
512 - } )
513 - .suggestions();
514 -
515 - $promptContainer
516 - .append( $promptTextbox )
517 - .append( $addButton );
518 -
519 - return $promptContainer;
520 - },
521 -
522 - /**
523 - * Execute or queue a category addition.
524 - *
525 - * @param $link {jQuery} Anchor tag of category link inside #catlinks.
526 - * @param catTitle {mw.Title} Instance of mw.Title of the category to be added.
527 - * @param catSortkey {String} sort key (optional)
528 - * @param noAppend
529 - * @return {mw.inlineCategorizer}
530 - */
531 - handleCategoryAdd: function( $link, catTitle, catSortkey, noAppend ) {
532 - var ajaxcat = this,
533 - // Suffix is wikitext between '[[Category:Foo' and ']]'.
534 - suffix = catSortkey ? '|' + catSortkey : '',
535 - catName = catTitle.getMainText(),
536 - catFull = catTitle.toText();
537 -
538 - if ( this.containsCat( catName ) ) {
539 - this.showError( mw.msg( 'inlinecategorizer-category-already-present', catName ) );
540 - return this;
541 - }
542 -
543 - if ( !$link.length ) {
544 - $link = this.createCatLink( catTitle );
545 - }
546 -
547 - // Mark red if missing
548 - $link[(catTitle.exists() === false ? 'addClass' : 'removeClass')]( 'new' );
549 -
550 - this.doConfirmEdit( {
551 - modFn: function( oldText ) {
552 - var newText = ajaxcat.runHooks( oldText, 'beforeAdd', catName );
553 - newText = newText + "\n[[" + catFull + suffix + "]]\n";
554 - return ajaxcat.runHooks( newText, 'afterAdd', catName );
555 - },
556 - dialogDescription: mw.message( 'inlinecategorizer-add-category-summary', catName ).escaped(),
557 - editSummary: '+[[' + catFull + ']]',
558 - doneFn: function( unsaved ) {
559 - if ( !noAppend ) {
560 - ajaxcat.options.$container
561 - .find( '#mw-normal-catlinks > .mw-addcategory-prompt' ).children( 'input' ).hide();
562 - ajaxcat.options.$container
563 - .find( '#mw-normal-catlinks ul' ).append( $link.parent() );
564 - } else {
565 - // Remove input box & button
566 - $link.data( 'deleteButton' ).click();
567 -
568 - // Update link text and href
569 - $link.show().text( catName ).attr( 'href', catTitle.getUrl() );
570 - }
571 - if ( unsaved ) {
572 - $link.addClass( 'mw-added-category' );
573 - }
574 - $( '.mw-ajax-addcategory' ).click();
575 - },
576 - $link: $link,
577 - action: 'add'
578 - } );
579 - return this;
580 - },
581 -
582 - /**
583 - * Execute or queue a category edit.
584 - *
585 - * @param $link {jQuery} Anchor tag of category link in #catlinks.
586 - * @param oldCatName {String} Name of category before edit
587 - * @param catTitle {mw.Title} Instance of mw.Title for new category
588 - * @param catSortkey {String} Sort key of new category link (optional)
589 - * @param isAdded {Boolean} True if this is a new link, false if it changed an existing one
590 - */
591 - handleCategoryEdit: function( $link, oldCatName, catTitle, catSortkey, isAdded ) {
592 - var ajaxcat = this,
593 - catName = catTitle.getMainText();
594 -
595 - // Category add needs to be handled differently
596 - if ( isAdded ) {
597 - // Pass sortkey back
598 - this.handleCategoryAdd( $link, catTitle, catSortkey, true );
599 - return;
600 - }
601 -
602 - // User didn't change anything, trigger delete
603 - // @todo Document why it's deleted.
604 - if ( oldCatName === catName ) {
605 - $link.data( 'deleteButton' ).click();
606 - return;
607 - }
608 -
609 - // Mark red if missing
610 - $link[(catTitle.exists() === false ? 'addClass' : 'removeClass')]( 'new' );
611 -
612 - var categoryRegex = buildRegex( oldCatName ),
613 - editSummary = '[[' + new mw.Title( oldCatName, catNsId ).toText() + ']] -> [[' + catTitle.toText() + ']]';
614 -
615 - ajaxcat.doConfirmEdit({
616 - modFn: function( oldText ) {
617 - var newText = ajaxcat.runHooks( oldText, 'beforeChange', oldCatName, catName ),
618 - matches = newText.match( categoryRegex );
619 -
620 - // Old cat wasn't found, likely to be transcluded
621 - if ( !$.isArray( matches ) ) {
622 - ajaxcat.showError( mw.msg( 'inlinecategorizer-edit-category-error' ) );
623 - return false;
624 - }
625 -
626 - var suffix = catSortkey ? '|' + catSortkey : matches[0].replace( categoryRegex, '$2' ),
627 - newCategoryWikitext = '[[' + catTitle + suffix + ']]';
628 -
629 - if ( matches.length > 1 ) {
630 - // The category is duplicated. Remove all but one match
631 - for ( var i = 1; i < matches.length; i++ ) {
632 - oldText = oldText.replace( matches[i], '' );
633 - }
634 - }
635 - newText = oldText.replace( categoryRegex, newCategoryWikitext );
636 -
637 - return ajaxcat.runHooks( newText, 'afterChange', oldCatName, catName );
638 - },
639 - dialogDescription: mw.message( 'inlinecategorizer-edit-category-summary', oldCatName, catName ).escaped(),
640 - editSummary: editSummary,
641 - doneFn: function( unsaved ) {
642 - // Remove input box & button
643 - $link.data( 'deleteButton' ).click();
644 -
645 - // Update link text and href
646 - $link.show().text( catName ).attr( 'href', catTitle.getUrl() );
647 - if ( unsaved ) {
648 - $link.data( 'origCat', oldCatName ).addClass( 'mw-changed-category' );
649 - }
650 - },
651 - $link: $link,
652 - action: 'edit'
653 - });
654 - },
655 -
656 - /**
657 - * Checks the API whether the category in question is a redirect.
658 - * Also returns existance info (to color link red/blue)
659 - * @param category {String} Name of category to resolve
660 - * @param callback {Function} Called with 1 argument (mw.Title object)
661 - */
662 - resolveRedirects: function( category, callback ) {
663 - if ( !this.options.resolveRedirects ) {
664 - callback( category, true );
665 - return;
666 - }
667 - var catTitle = new mw.Title( category, catNsId ),
668 - queryVars = {
669 - action:'query',
670 - titles: catTitle.toString(),
671 - redirects: 1,
672 - format: 'json'
673 - };
674 -
675 - $.getJSON( mw.util.wikiScript( 'api' ), queryVars, function( json ) {
676 - var redirect = json.query.redirects,
677 - exists = !json.query.pages[-1];
678 -
679 - // If it's a redirect 'exists' is for the target, not the origin
680 - if ( redirect ) {
681 - // Register existance of redirect origin as well,
682 - // a non-existent page can't be a redirect.
683 - mw.Title.exist.set( catTitle.toString(), true );
684 -
685 - // Override title with the redirect target
686 - catTitle = new mw.Title( redirect[0].to ).getMainText();
687 - }
688 -
689 - // Register existence
690 - mw.Title.exist.set( catTitle.toString(), exists );
691 -
692 - callback( catTitle );
693 - } );
694 - },
695 -
696 - /**
697 - * Append edit and remove buttons to a given category link
698 - *
699 - * @param DOMElement element Anchor element, to which the buttons should be appended.
700 - * @return {mw.inlineCategorizer}
701 - */
702 - createCatButtons: function( $element ) {
703 - var deleteButton = createButton( 'icon-close', mw.msg( 'inlinecategorizer-remove-category' ) ),
704 - editButton = createButton( 'icon-edit', mw.msg( 'inlinecategorizer-edit-category' ) ),
705 - saveButton = createButton( 'icon-tick', mw.msg( 'inlinecategorizer-confirm-save' ) ).hide(),
706 - ajaxcat = this;
707 -
708 - deleteButton.click( this.handleDeleteLink );
709 - editButton.click( ajaxcat.createEditInterface );
710 -
711 - $element.after( deleteButton ).after( editButton );
712 -
713 - // Save references to all links and buttons
714 - $element.data( {
715 - deleteButton: deleteButton,
716 - editButton: editButton,
717 - saveButton: saveButton
718 - } );
719 - editButton.data( {
720 - link: $element
721 - } );
722 - return this;
723 - },
724 -
725 - /**
726 - * Append spinner wheel to element.
727 - * @param $el {jQuery}
728 - * @return {mw.inlineCategorizer}
729 - */
730 - addProgressIndicator: function( $el ) {
731 - $el.append( $( '<div>' ).addClass( 'mw-ajax-loader' ) );
732 - return this;
733 - },
734 -
735 - /**
736 - * Find and remove spinner wheel from inside element.
737 - * @param $el {jQuery}
738 - * @return {mw.inlineCategorizer}
739 - */
740 - removeProgressIndicator: function( $el ) {
741 - $el.find( '.mw-ajax-loader' ).remove();
742 - return this;
743 - },
744 -
745 - /**
746 - * Parse the DOM $container and build a list of
747 - * present categories.
748 - *
749 - * @return {Array} All categories.
750 - */
751 - getCats: function() {
752 - var cats = this.options.$container
753 - .find( this.options.categoryLinkSelector )
754 - .map( function() {
755 - return $.trim( $( this ).text() );
756 - } );
757 - return cats;
758 - },
759 -
760 - /**
761 - * Check whether a passed category is present in the DOM.
762 - *
763 - * @param newCat {String} Category name to be checked for.
764 - * @return {Boolean}
765 - */
766 - containsCat: function( newCat ) {
767 - newCat = $.ucFirst( newCat );
768 - var match = false;
769 - $.each( this.getCats(), function(i, cat) {
770 - if ( $.ucFirst( cat ) === newCat ) {
771 - match = true;
772 - // Stop once we have a match
773 - return false;
774 - }
775 - } );
776 - return match;
777 - },
778 -
779 - /**
780 - * Execute or queue a category delete.
781 - *
782 - * @param $link {jQuery}
783 - * @param category
784 - * @return ?
785 - */
786 - handleCategoryDelete: function( $link, category ) {
787 - var categoryRegex = buildRegex( category, true ),
788 - ajaxcat = this;
789 -
790 - this.doConfirmEdit({
791 - modFn: function( oldText ) {
792 - var newText = ajaxcat.runHooks( oldText, 'beforeDelete', category );
793 - newText = newText.replace( categoryRegex, '' );
794 -
795 - if ( newText === oldText ) {
796 - ajaxcat.showError( mw.msg( 'inlinecategorizer-remove-category-error' ) );
797 - return false;
798 - }
799 -
800 - return ajaxcat.runHooks( newText, 'afterDelete', category );
801 - },
802 - dialogDescription: mw.message( 'inlinecategorizer-remove-category-summary', category ).escaped(),
803 - editSummary: '-[[' + new mw.Title( category, catNsId ) + ']]',
804 - doneFn: function( unsaved ) {
805 - if ( unsaved ) {
806 - $link.addClass( 'mw-removed-category' );
807 - } else {
808 - $link.parent().remove();
809 - }
810 - },
811 - $link: $link,
812 - action: 'delete'
813 - });
814 - },
815 -
816 - /**
817 - * Takes a category link element
818 - * and strips all data from it.
819 - *
820 - * @param $link {jQuery}
821 - * @param del {Boolean}
822 - * @param dontRestoreText {Boolean}
823 - * @return ?
824 - */
825 - resetCatLink: function( $link, del, dontRestoreText ) {
826 - $link.removeClass( 'mw-removed-category mw-added-category mw-changed-category' );
827 - var data = $link.data();
828 -
829 - if ( typeof data.stashIndex === 'number' ) {
830 - this.removeStashItem( data.stashIndex );
831 - }
832 - if ( del ) {
833 - $link.parent().remove();
834 - return;
835 - }
836 - if ( data.origCat && !dontRestoreText ) {
837 - var catTitle = new mw.Title( data.origCat, catNsId );
838 - $link.text( catTitle.getMainText() );
839 - $link.attr( 'href', catTitle.getUrl() );
840 - }
841 -
842 - $link.removeData();
843 -
844 - // Re-add data
845 - $link.data( {
846 - saveButton: data.saveButton,
847 - deleteButton: data.deleteButton,
848 - editButton: data.editButton
849 - } );
850 - },
851 -
852 - /**
853 - * Do the actual edit.
854 - * Gets token & text from api, runs it through fn
855 - * and saves it with summary.
856 - * @param page {String} Pagename
857 - * @param fn {Function} edit function
858 - * @param summary {String}
859 - * @param doneFn {String} Callback after all is done
860 - */
861 - doEdit: function( page, fn, summary, doneFn ) {
862 - // Get an edit token for the page.
863 - var getTokenVars = {
864 - action: 'query',
865 - prop: 'info|revisions',
866 - intoken: 'edit',
867 - titles: page,
868 - rvprop: 'content|timestamp',
869 - format: 'json'
870 - }, ajaxcat = this;
871 -
872 - $.post(
873 - mw.util.wikiScript( 'api' ),
874 - getTokenVars,
875 - function( json ) {
876 - if ( 'error' in json ) {
877 - ajaxcat.showError( mw.msg( 'inlinecategorizer-api-error', json.error.code, json.error.info ) );
878 - return;
879 - } else if ( json.query && json.query.pages ) {
880 - var infos = json.query.pages;
881 - } else {
882 - ajaxcat.showError( mw.msg( 'inlinecategorizer-api-unknown-error' ) );
883 - return;
884 - }
885 -
886 - $.each( infos, function( pageid, data ) {
887 - var token = data.edittoken,
888 - timestamp = data.revisions[0].timestamp,
889 - oldText = data.revisions[0]['*'],
890 - nowikiKey = generateRandomId(), // Unique ID for nowiki replacement
891 - nowikiFragments = []; // Nowiki fragments will be stored here during the changes
892 -
893 - // Replace all nowiki parts with unique keys..
894 - oldText = replaceNowikis( oldText, nowikiKey, nowikiFragments );
895 -
896 - // ..then apply the changes to the page text..
897 - var newText = fn( oldText );
898 - if ( newText === false ) {
899 - return;
900 - }
901 -
902 - // ..and restore the nowiki parts back.
903 - newText = restoreNowikis( newText, nowikiKey, nowikiFragments );
904 -
905 - var postEditVars = {
906 - action: 'edit',
907 - title: page,
908 - text: newText,
909 - summary: summary,
910 - token: token,
911 - basetimestamp: timestamp,
912 - format: 'json'
913 - };
914 -
915 - $.post(
916 - mw.util.wikiScript( 'api' ),
917 - postEditVars,
918 - doneFn,
919 - 'json'
920 - )
921 - .error( function( xhr, text, error ) {
922 - ajaxcat.showError( mw.msg( 'inlinecategorizer-api-error', text, error ) );
923 - });
924 - } );
925 - },
926 - 'json'
927 - ).error( function( xhr, text, error ) {
928 - ajaxcat.showError( mw.msg( 'inlinecategorizer-api-error', text, error ) );
929 - } );
930 - },
931 -
932 - /**
933 - * This gets called by all action buttons
934 - * Displays a dialog to confirm the action
935 - * Afterwards do the actual edit.
936 - *
937 - * @param props {Object}:
938 - * - modFn {Function} text-modifying function
939 - * - dialogDescription {String} Changes done (HTML for in the dialog, escape before hand if needed)
940 - * - editSummary {String} Changes done (text for the edit summary)
941 - * - doneFn {Function} callback after everything is done
942 - * - $link {jQuery}
943 - * - action
944 - * @return {mw.inlineCategorizer}
945 - */
946 - doConfirmEdit: function( props ) {
947 - var summaryHolder, reasonBox, dialog, submitFunction,
948 - buttons = {},
949 - dialogOptions = {
950 - AutoOpen: true,
951 - buttons: buttons,
952 - width: 450
953 - },
954 - ajaxcat = this;
955 -
956 - // Check whether to use multiEdit mode:
957 - if ( this.options.multiEdit && props.action !== 'all' ) {
958 -
959 - // Stash away
960 - props.$link
961 - .data( 'stashIndex', this.stash.fns.length )
962 - .data( 'summary', props.dialogDescription );
963 -
964 - this.stash.dialogDescriptions.push( props.dialogDescription );
965 - this.stash.editSummaries.push( props.editSummary );
966 - this.stash.fns.push( props.modFn );
967 -
968 - this.saveAllButton.show();
969 - this.cancelAllButton.show();
970 -
971 - // Clear input field after action
972 - ajaxcat.addContainer.find( '.mw-addcategory-input' ).val( '' );
973 -
974 - // This only does visual changes, fire done and return.
975 - props.doneFn( true );
976 - return this;
977 - }
978 -
979 - // Summary of the action to be taken
980 - summaryHolder = $( '<p>' )
981 - .html( '<strong>' + mw.message( 'inlinecategorizer-category-question' ).escaped() + '</strong><br/>' + props.dialogDescription );
982 -
983 - // Reason textbox.
984 - reasonBox = $( '<input type="text" size="45"></input>' )
985 - .addClass( 'mw-ajax-confirm-reason' );
986 -
987 - // Produce a confirmation dialog
988 - dialog = $( '<div>' )
989 - .addClass( 'mw-ajax-confirm-dialog' )
990 - .attr( 'title', mw.msg( 'inlinecategorizer-confirm-title' ) )
991 - .append( summaryHolder )
992 - .append( reasonBox );
993 -
994 - // Submit button
995 - submitFunction = function() {
996 - ajaxcat.addProgressIndicator( dialog );
997 - ajaxcat.doEdit(
998 - mw.config.get( 'wgPageName' ),
999 - props.modFn,
1000 - props.editSummary + ': ' + reasonBox.val(),
1001 - function() {
1002 - props.doneFn();
1003 -
1004 - // Clear input field after successful edit
1005 - ajaxcat.addContainer.find( '.mw-addcategory-input' ).val( '' );
1006 -
1007 - dialog.dialog( 'close' );
1008 - ajaxcat.removeProgressIndicator( dialog );
1009 - }
1010 - );
1011 - };
1012 -
1013 - buttons[mw.msg( 'inlinecategorizer-confirm-save' )] = submitFunction;
1014 -
1015 - dialog.dialog( dialogOptions ).keyup( function( e ) {
1016 - // Close on enter
1017 - if ( e.keyCode === 13 ) {
1018 - submitFunction();
1019 - }
1020 - } );
1021 -
1022 - return this;
1023 - },
1024 -
1025 - /**
1026 - * @param index {Number|jQuery} Stash index or jQuery object of stash item.
1027 - * @return {mw.inlineCategorizer}
1028 - */
1029 - removeStashItem: function( i ) {
1030 - if ( typeof i !== 'number' ) {
1031 - i = i.data( 'stashIndex' );
1032 - }
1033 -
1034 - try {
1035 - delete this.stash.fns[i];
1036 - delete this.stash.dialogDescriptions[i];
1037 - } catch(e) {}
1038 -
1039 - if ( $.isEmpty( this.stash.fns ) ) {
1040 - this.stash.fns = [];
1041 - this.stash.dialogDescriptions = [];
1042 - this.stash.editSummaries = [];
1043 - this.saveAllButton.hide();
1044 - this.cancelAllButton.hide();
1045 - }
1046 - return this;
1047 - },
1048 -
1049 - /**
1050 - * Reset all data from the category links and the stash.
1051 - *
1052 - * @param del {Boolean} Delete any category links with .mw-removed-category
1053 - * @return {mw.inlineCategorizer}
1054 - */
1055 - resetAll: function( del ) {
1056 - var $links = this.options.$container.find( this.options.categoryLinkSelector ),
1057 - $del = $([]),
1058 - ajaxcat = this;
1059 -
1060 - if ( del ) {
1061 - $del = $links.filter( '.mw-removed-category' ).parent();
1062 - }
1063 -
1064 - $links.each( function() {
1065 - ajaxcat.resetCatLink( $( this ), false, del );
1066 - } );
1067 -
1068 - $del.remove();
1069 -
1070 - this.options.$container.find( '#mw-hidden-catlinks' ).remove();
1071 -
1072 - return this;
1073 - },
1074 -
1075 - /**
1076 - * Add hooks
1077 - * Currently available: beforeAdd, beforeChange, beforeDelete,
1078 - * afterAdd, afterChange, afterDelete
1079 - * If the hook function returns false, all changes are aborted.
1080 - *
1081 - * @param string type Type of hook to add
1082 - * @param function fn Hook function. The following vars are passed to it:
1083 - * 1. oldtext: The wikitext before the hook
1084 - * 2. category: The deleted, added, or changed category
1085 - * 3. (only for beforeChange/afterChange): newcategory
1086 - */
1087 - addHook: function( type, fn ) {
1088 - if ( !this.hooks[type] || !$.isFunction( fn ) ) {
1089 - return;
1090 - }
1091 - else {
1092 - this.hooks[type].push( fn );
1093 - }
1094 - },
1095 -
1096 -
1097 - /**
1098 - * Open a dismissable error dialog
1099 - *
1100 - * @param string str The error description
1101 - */
1102 - showError: function( str ) {
1103 - var oldDialog = $( '.mw-ajax-confirm-dialog' ),
1104 - buttons = {},
1105 - dialogOptions = {
1106 - buttons: buttons,
1107 - AutoOpen: true,
1108 - title: mw.msg( 'inlinecategorizer-error-title' )
1109 - };
1110 -
1111 - this.removeProgressIndicator( oldDialog );
1112 - oldDialog.dialog( 'close' );
1113 -
1114 - var dialog = $( '<div>' ).text( str );
1115 -
1116 - mw.util.$content.append( dialog );
1117 -
1118 - buttons[mw.msg( 'inlinecategorizer-confirm-ok' )] = function( e ) {
1119 - dialog.dialog( 'close' );
1120 - };
1121 -
1122 - dialog.dialog( dialogOptions ).keyup( function( e ) {
1123 - if ( e.keyCode === 13 ) {
1124 - dialog.dialog( 'close' );
1125 - }
1126 - } );
1127 - },
1128 -
1129 - /**
1130 - * @param oldtext
1131 - * @param type
1132 - * @param category
1133 - * @param categoryNew
1134 - * @return oldtext
1135 - */
1136 - runHooks: function( oldtext, type, category, categoryNew ) {
1137 - // No hooks registered
1138 - if ( !this.hooks[type] ) {
1139 - return oldtext;
1140 - } else {
1141 - for ( var i = 0; i < this.hooks[type].length; i++ ) {
1142 - oldtext = this.hooks[type][i]( oldtext, category, categoryNew );
1143 - if ( oldtext === false ) {
1144 - this.showError( mw.msg( 'inlinecategorizer-category-hook-error', category ) );
1145 - return;
1146 - }
1147 - }
1148 - return oldtext;
1149 - }
1150 - }
1151 -};
1152 -
1153 -} )( jQuery );

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r96250Split ajaxCategories away from core for now, into an extension:...krinkle19:35, 4 September 2011

Status & tagging log