Index: trunk/phase3/maintenance/language/messages.inc |
— | — | @@ -3479,22 +3479,21 @@ |
3480 | 3480 | 'ajax-add-category-submit', |
3481 | 3481 | 'ajax-confirm-ok', |
3482 | 3482 | 'ajax-confirm-title', |
3483 | | - 'ajax-confirm-prompt', |
3484 | 3483 | 'ajax-confirm-save', |
3485 | 3484 | 'ajax-confirm-save-all', |
3486 | 3485 | 'ajax-cancel', |
| 3486 | + 'ajax-cancel-all', |
3487 | 3487 | 'ajax-add-category-summary', |
3488 | 3488 | 'ajax-edit-category-summary', |
3489 | 3489 | 'ajax-remove-category-summary', |
3490 | | - 'ajax-add-category-question', |
3491 | | - 'ajax-edit-category-question', |
3492 | | - 'ajax-remove-category-question', |
3493 | | - 'ajax-confirm-actionsummary', |
| 3490 | + 'ajax-category-question', |
| 3491 | + 'ajax-category-and', |
3494 | 3492 | 'ajax-error-title', |
3495 | | - 'ajax-error-dismiss', |
3496 | 3493 | 'ajax-remove-category-error', |
3497 | 3494 | 'ajax-edit-category-error', |
3498 | 3495 | 'ajax-category-already-present', |
| 3496 | + 'ajax-category-hook-error', |
| 3497 | + 'ajax-api-error', |
3499 | 3498 | ), |
3500 | 3499 | |
3501 | 3500 | ); |
Index: trunk/phase3/languages/messages/MessagesQqq.php |
— | — | @@ -4238,17 +4238,22 @@ |
4239 | 4239 | 'sqlite-no-fts' => 'Shown on Special:Version, $1 is version', |
4240 | 4240 | |
4241 | 4241 | # Add categories per AJAX |
4242 | | -'ajax-remove-category' => 'Tooltip for link to remove a category from the page, displayed after each category at the foot of a page. Refers to the specific category. "Remove this category" is also correct.', |
| 4242 | +'ajax-remove-category' => 'Tooltip for link to remove a category from the page, displayed after each category at the foot of a page. |
| 4243 | +Refers to the specific category. "Remove this category" is also correct.', |
4243 | 4244 | 'ajax-edit-category' => 'Tooltip for the edit link displayed after each category at the foot of a page. Refers to the specific category. "Edit this category" is also correct.', |
4244 | 4245 | 'ajax-add-category-submit' => '{{Identical|Add}}', |
4245 | 4246 | 'ajax-confirm-ok' => '{{Identical|OK}}', |
4246 | | -'ajax-confirm-save' => 'Submit button |
4247 | | - |
4248 | | -{{Identical|Save}}', |
4249 | | -'ajax-edit-category-summary' => 'Automatic edit summary that can be copied to the summary box. $1 and $2 are both category names.', |
4250 | | -'ajax-confirm-actionsummary' => 'This message is followed by one of the following messages; {{msg-mw|ajax-add-category-summary}}, {{msg-mw|ajax-remove-category-summary}}, {{msg-mw|ajax-edit-category-summary}}.', |
| 4247 | +'ajax-confirm-title' => 'Title for a dialog box in which the user is asked for an edit summary', |
| 4248 | +'ajax-confirm-save' => 'Submit button {{Identical|Save}}', |
| 4249 | +'ajax-confirm-save-all' => 'Submit button to save all changes', |
| 4250 | +'ajax-add-category-summary' => 'See {{msg-mw|ajax-category-question}}. $1 is a category name. This is used inside a sentence, make sure that the case is correct.', |
| 4251 | +'ajax-edit-category-summary' => 'See {{msg-mw|ajax-category-question}}. $1 and $2 are both category names. This is used inside a sentence, make sure that the case is correct.', |
| 4252 | +'ajax-remove-category-summary' => 'See {{msg-mw|ajax-category-question}}. $1 is a category name. This is used inside a sentence, make sure that the case is correct.', |
| 4253 | +'ajax-category-question' => 'Question the user is asked before submit. $1 is filled with a list of one or more of the following messages; |
| 4254 | +{{msg-mw|ajax-add-category-summary}}, {{msg-mw|ajax-remove-category-summary}}, {{msg-mw|ajax-edit-category-summary}}. |
| 4255 | +The last of the inserted messages is prefaced by {{msg-mw|ajax-category-and}}. The result would be something like: |
| 4256 | +Why do you want to remove Category:A and add Category:C ?', |
| 4257 | +'ajax-category-and' => 'Used to join multiple list items. Eg. "Edit foo, move bar and add foobar"', |
4251 | 4258 | 'ajax-error-title' => '{{Identical|Error}}', |
4252 | | -'ajax-error-dismiss' => '{{Identical|OK}}', |
4253 | 4259 | 'ajax-category-already-present' => 'Error message. $1 is the category name', |
4254 | | - |
4255 | 4260 | ); |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -4607,24 +4607,21 @@ |
4608 | 4608 | 'ajax-add-category-submit' => 'Add', |
4609 | 4609 | 'ajax-confirm-ok' => 'OK', |
4610 | 4610 | 'ajax-confirm-title' => 'Confirm action', |
4611 | | -'ajax-confirm-prompt' => 'You can provide an edit summary below. |
4612 | | -Click "Save" to save your edit.', |
4613 | 4611 | 'ajax-confirm-save' => 'Save', |
4614 | 4612 | 'ajax-confirm-save-all' => 'Save all changes', |
4615 | | -'ajax-cancel' => 'Cancel edits', |
4616 | | -'ajax-add-category-summary' => 'Add category "$1"', |
4617 | | -'ajax-edit-category-summary' => 'Change category "$1" to "$2"', |
4618 | | -'ajax-remove-category-summary' => 'Remove category "$1"', |
4619 | | -'ajax-add-category-question' => 'Why do you want to add category "$1"?', |
4620 | | -'ajax-edit-category-question' => 'Why do you want to change category "$1" to "$2"?', |
4621 | | -'ajax-remove-category-question' => 'Why do you want to remove category "$1"?', |
4622 | | -'ajax-confirm-actionsummary' => 'Action to take:', |
| 4613 | +'ajax-cancel' => 'Cancel edit', |
| 4614 | +'ajax-cancel-all' => 'Cancel all changes', |
| 4615 | +'ajax-add-category-summary' => 'add category "$1"', |
| 4616 | +'ajax-edit-category-summary' => 'change category "$1" to "$2"', |
| 4617 | +'ajax-remove-category-summary' => 'remove category "$1"', |
| 4618 | +'ajax-category-question' => '<strong>Why</strong> do you want to $1?', |
| 4619 | +'ajax-category-and' => ' and ', |
4623 | 4620 | 'ajax-error-title' => 'Error', |
4624 | | -'ajax-error-dismiss' => 'OK', |
4625 | | -'ajax-remove-category-error' => 'It was not possible to remove this category. |
| 4621 | +'ajax-remove-category-error' => 'It was not possible to remove category "$1". |
4626 | 4622 | This usually occurs when the category has been added to the page in a template.', |
4627 | | -'ajax-edit-category-error' => 'It was not possible to edit this category. |
| 4623 | +'ajax-edit-category-error' => 'It was not possible to edit category "$1". |
4628 | 4624 | This usually occurs when the category has been added to the page in a template.', |
4629 | | -'ajax-category-already-present' => 'This page already belongs to the category $1', |
4630 | | - |
| 4625 | +'ajax-category-already-present' => 'This page already belongs to the category "$1"', |
| 4626 | +'ajax-category-hook-error' => 'A local function prevented the changes from being saved', |
| 4627 | +'ajax-api-error' => 'The API returned an error: $1: $2', |
4631 | 4628 | ); |
Index: trunk/phase3/resources/Resources.php |
— | — | @@ -501,23 +501,22 @@ |
502 | 502 | 'ajax-edit-category', |
503 | 503 | 'ajax-add-category-submit', |
504 | 504 | 'ajax-confirm-ok', |
505 | | - 'ajax-confirm-prompt', |
506 | 505 | 'ajax-confirm-title', |
507 | 506 | 'ajax-confirm-save', |
508 | 507 | 'ajax-confirm-save-all', |
509 | 508 | 'ajax-cancel', |
| 509 | + 'ajax-cancel-all', |
510 | 510 | 'ajax-add-category-summary', |
511 | | - 'ajax-remove-category-summary', |
512 | 511 | 'ajax-edit-category-summary', |
513 | | - 'ajax-add-category-question', |
514 | | - 'ajax-edit-category-question', |
515 | | - 'ajax-remove-category-question', |
516 | | - 'ajax-confirm-actionsummary', |
| 512 | + 'ajax-remove-category-summary', |
| 513 | + 'ajax-category-question', |
| 514 | + 'ajax-category-and', |
517 | 515 | 'ajax-error-title', |
518 | | - 'ajax-error-dismiss', |
519 | 516 | 'ajax-remove-category-error', |
520 | 517 | 'ajax-edit-category-error', |
521 | 518 | 'ajax-category-already-present', |
| 519 | + 'ajax-category-hook-error', |
| 520 | + 'ajax-api-error', |
522 | 521 | ), |
523 | 522 | ), |
524 | 523 | 'mediawiki.page.ajaxCategories.init' => array( |
Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js |
— | — | @@ -1,12 +1,13 @@ |
2 | | -// TODO |
3 | | -// |
4 | | -// * The edit summary should contain the added/removed category name too. |
5 | | -// Something like: "Category:Foo added. Reason" |
6 | | -// Requirement: Be able to get msg with lang option. |
7 | | -// * Handle uneditable cats. Needs serverside changes! |
8 | | -// * Handle normal redirects |
9 | | -// * Enter to submit |
10 | | - |
| 2 | +/** |
| 3 | + * mediaWiki.page.ajaxCategories |
| 4 | + * |
| 5 | + * @author Michael Dale, 2009 |
| 6 | + * @author Leo Koppelkamm, 2011 |
| 7 | + * @since 1.18 |
| 8 | + * |
| 9 | + * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces, wgUserGroups), |
| 10 | + * mw.util.wikiGetlink, mw.user.getId |
| 11 | + */ |
11 | 12 | ( function( $ ) { |
12 | 13 | |
13 | 14 | /* Local scope */ |
— | — | @@ -25,33 +26,16 @@ |
26 | 27 | * @param string category name. |
27 | 28 | * @return string Valid URL |
28 | 29 | */ |
29 | | - catLink = function( cat ) { |
| 30 | + catUrl = function( cat ) { |
30 | 31 | return mw.util.wikiGetlink( new mw.Title( cat, catNsId ) ); |
31 | 32 | }; |
32 | 33 | |
33 | | -mw.ajaxCategories = function( options ) { |
34 | | - //Save scope in shortcut |
35 | | - var aC = this; |
36 | | - |
37 | | - // TODO grab these out of option object. |
38 | | - |
39 | | - var catLinkWrapper = '<li/>'; |
40 | | - var $container = $( '.catlinks' ); |
41 | | - var $containerNormal = $( '#mw-normal-catlinks' ); |
42 | | - |
43 | | - var categoryLinkSelector = '#mw-normal-catlinks li a:not(.icon)'; |
44 | | - var _request; |
45 | | - |
46 | | - var _saveAllButton; |
47 | | - var _cancelAllButton; |
48 | | - var _multiEdit = $.inArray( 'user', mw.config.get( 'wgUserGroups' ) ) !== -1; |
49 | | - |
50 | 34 | /** |
51 | 35 | * Helper function for $.fn.suggestion |
52 | 36 | * |
53 | 37 | * @param string Query string. |
54 | 38 | */ |
55 | | - _fetchSuggestions = function( query ) { |
| 39 | + fetchSuggestions = function( query ) { |
56 | 40 | var _this = this; |
57 | 41 | // ignore bad characters, they will be stripped out |
58 | 42 | var catName = clean( $( this ).val() ); |
— | — | @@ -78,42 +62,188 @@ |
79 | 63 | $( _this ).suggestions( 'suggestions', titleArr ); |
80 | 64 | } |
81 | 65 | } ); |
82 | | - //TODO |
83 | 66 | _request = request; |
84 | 67 | }; |
| 68 | + |
| 69 | + /** |
| 70 | + * Replace <nowiki> and comments with unique keys |
| 71 | + */ |
| 72 | + replaceNowikis = function( text, id, array ) { |
| 73 | + var matches = text.match( /(<nowiki\>[\s\S]*?<\/nowiki>|<\!--[\s\S]*?--\>)/g ); |
| 74 | + for ( var i = 0; matches && i < matches.length; i++ ) { |
| 75 | + array[i] = matches[i]; |
| 76 | + text = text.replace( matches[i], id + i + '-' ); |
| 77 | + } |
| 78 | + return text; |
| 79 | + }; |
| 80 | + |
| 81 | + /** |
| 82 | + * Restore <nowiki> and comments from unique keys |
| 83 | + */ |
| 84 | + restoreNowikis = function( text, id, array ) { |
| 85 | + for ( var i = 0; i < array.length; i++ ) { |
| 86 | + text = text.replace( id + i + '-', array[i] ); |
| 87 | + } |
| 88 | + return text; |
| 89 | + }; |
| 90 | + |
| 91 | + /** |
| 92 | + * Makes regex string caseinsensitive. |
| 93 | + * Useful when 'i' flag can't be used. |
| 94 | + * Return stuff like [Ff][Oo][Oo] |
| 95 | + * @param string Regex string. |
| 96 | + * @return string Processed regex string |
| 97 | + */ |
| 98 | + makeCaseInsensitive = function( string ) { |
| 99 | + if ( $.inArray( 14, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) + 1 ) { |
| 100 | + return string; |
| 101 | + } |
| 102 | + var newString = ''; |
| 103 | + for ( var i=0; i < string.length; i++ ) { |
| 104 | + newString += '[' + string.charAt( i ).toUpperCase() + string.charAt( i ).toLowerCase() + ']'; |
| 105 | + } |
| 106 | + return newString; |
| 107 | + }; |
| 108 | + |
| 109 | + /** |
| 110 | + * Build a regex that matches legal invocations |
| 111 | + * of the passed category. |
| 112 | + * @param string category. |
| 113 | + * @param boolean Match one following linebreak as well? |
| 114 | + * @return Regex |
| 115 | + */ |
| 116 | + buildRegex = function( category, matchLineBreak ) { |
| 117 | + var categoryNSFragment = ''; |
| 118 | + $.each( mw.config.get( 'wgNamespaceIds' ), function( name, id ) { |
| 119 | + if ( id == 14 ) { |
| 120 | + // The parser accepts stuff like cATegORy, |
| 121 | + // we need to do the same |
| 122 | + // ( Well unless we have wgCaseSensitiveNamespaces, but that's being checked for ) |
| 123 | + categoryNSFragment += '|' + makeCaseInsensitive ( $.escapeRE( name ) ); |
| 124 | + } |
| 125 | + } ); |
| 126 | + categoryNSFragment = categoryNSFragment.substr( 1 ); // Remove leading pipe |
85 | 127 | |
| 128 | + // Build the regex |
| 129 | + var titleFragment = $.escapeRE( category ).replace( /( |_)/g, '[ _]' ); |
| 130 | + |
| 131 | + firstChar = titleFragment.charAt( 0 ); |
| 132 | + firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']'; |
| 133 | + titleFragment = firstChar + titleFragment.substr( 1 ); |
| 134 | + var categoryRegex = '\\[\\[(' + categoryNSFragment + '):' + '[ _]*' +titleFragment + '(\\|[^\\]]*)?\\]\\]'; |
| 135 | + if ( matchLineBreak ) { |
| 136 | + categoryRegex += '[ \\t\\r]*\\n?'; |
| 137 | + } |
| 138 | + return new RegExp( categoryRegex, 'g' ); |
| 139 | + }; |
| 140 | + |
| 141 | + |
| 142 | +mw.ajaxCategories = function( options ) { |
| 143 | + //Save scope in shortcut |
| 144 | + var that = this, _request, _saveAllButton, _cancelAllButton, _addContainer, defaults; |
| 145 | + |
| 146 | + defaults = { |
| 147 | + catLinkWrapper : '<li/>', |
| 148 | + $container : $( '.catlinks' ), |
| 149 | + $containerNormal : $( '#mw-normal-catlinks' ), |
| 150 | + categoryLinkSelector : 'li a:not(.icon)', |
| 151 | + multiEdit : $.inArray( 'user', mw.config.get( 'wgUserGroups' ) ) + 1, |
| 152 | + resolveRedirects : true |
| 153 | + }; |
| 154 | + // merge defaults and options, without modifying defaults */ |
| 155 | + options = $.extend( {}, defaults, options ); |
| 156 | + |
86 | 157 | /** |
87 | 158 | * Insert a newly added category into the DOM |
88 | 159 | * |
89 | 160 | * @param string category name. |
90 | 161 | * @return jQuery object |
91 | 162 | */ |
92 | | - _createCatDOM = function( cat ) { |
| 163 | + this.createCatLink = function( cat ) { |
93 | 164 | // User can implicitely state a sort key. |
94 | 165 | // Remove before display |
95 | | - cat = cat.replace(/\|.*/, ''); |
| 166 | + cat = cat.replace(/\|.*/, '' ); |
96 | 167 | |
97 | 168 | // strip out bad characters |
98 | 169 | cat = clean ( cat ); |
99 | 170 | |
100 | | - if ( $.isEmpty( cat ) || aC.containsCat( cat ) ) { |
| 171 | + if ( $.isEmpty( cat ) || that.containsCat( cat ) ) { |
101 | 172 | return; |
102 | 173 | } |
103 | 174 | |
104 | | - var $catLinkWrapper = $( catLinkWrapper ); |
| 175 | + var $catLinkWrapper = $( options.catLinkWrapper ); |
105 | 176 | var $anchor = $( '<a/>' ).append( cat ); |
106 | 177 | $catLinkWrapper.append( $anchor ); |
107 | | - $anchor.attr( { target: "_blank", href: catLink( cat ) } ); |
| 178 | + $anchor.attr( { target: "_blank", href: catUrl( cat ) } ); |
108 | 179 | |
109 | | - _createCatButtons( $anchor.get(0) ); |
| 180 | + _createCatButtons( $anchor ); |
110 | 181 | |
111 | 182 | return $anchor; |
112 | 183 | }; |
113 | 184 | |
114 | | - _makeSuggestionBox = function( prefill, callback, buttonVal ) { |
| 185 | + /** |
| 186 | + * Takes a category link element |
| 187 | + * and strips all data from it. |
| 188 | + * |
| 189 | + * @param jQuery object |
| 190 | + */ |
| 191 | + this.resetCatLink = function( $link, del, dontRestoreText ) { |
| 192 | + $link.removeClass( 'mw-removed-category mw-added-category mw-changed-category' ); |
| 193 | + var data = $link.data(); |
| 194 | + |
| 195 | + if ( typeof data.stashIndex == "number" ) { |
| 196 | + _removeStashItem( data.stashIndex ); |
| 197 | + } |
| 198 | + if ( del ) { |
| 199 | + $link.parent.remove(); |
| 200 | + return; |
| 201 | + } |
| 202 | + if ( data.origCat && !dontRestoreText ) { |
| 203 | + $link.text( data.origCat ); |
| 204 | + $link.attr( 'href', catUrl( data.origCat ) ); |
| 205 | + } |
| 206 | + |
| 207 | + $link.removeData(); |
| 208 | + |
| 209 | + //Readd static. |
| 210 | + $link.data({ |
| 211 | + saveButton : data.saveButton, |
| 212 | + deleteButton: data.deleteButton, |
| 213 | + editButton : data.editButton |
| 214 | + }); |
| 215 | + }; |
| 216 | + |
| 217 | + /** |
| 218 | + * Reset all data from the category links and the stash. |
| 219 | + * @param Boolean del Delete any category links with .mw-removed-category |
| 220 | + */ |
| 221 | + this.resetAll = function( del ) { |
| 222 | + var $links = options.$container.find( options.categoryLinkSelector ), $del = $(); |
| 223 | + if ( del ) { |
| 224 | + $del = $links.filter( '.mw-removed-category' ).parent(); |
| 225 | + } |
| 226 | + |
| 227 | + $links.each( function() { |
| 228 | + that.resetCatLink( $( this ), false, del ); |
| 229 | + }); |
| 230 | + |
| 231 | + $del.remove(); |
| 232 | + |
| 233 | + if ( !options.$container.find( '#mw-hidden-catlinks li' ).length ) { |
| 234 | + options.$container.find( '#mw-hidden-catlinks' ).remove(); |
| 235 | + } |
| 236 | + }; |
| 237 | + |
| 238 | + /** |
| 239 | + * Create a suggestion box for use in edit/add dialogs |
| 240 | + * @param str prefill Prefill input |
| 241 | + * @param function callback on submit |
| 242 | + * @param str buttonVal Button text |
| 243 | + */ |
| 244 | + this._makeSuggestionBox = function( prefill, callback, buttonVal ) { |
115 | 245 | // Create add category prompt |
116 | 246 | var promptContainer = $( '<div class="mw-addcategory-prompt"/>' ); |
117 | | - var promptTextbox = $( '<input type="text" size="45" class="mw-addcategory-input"/>' ); |
| 247 | + var promptTextbox = $( '<input type="text" size="30" class="mw-addcategory-input"/>' ); |
118 | 248 | if ( prefill !== '' ) { |
119 | 249 | promptTextbox.val( prefill ); |
120 | 250 | } |
— | — | @@ -121,9 +251,11 @@ |
122 | 252 | addButton.val( buttonVal ); |
123 | 253 | |
124 | 254 | addButton.click( callback ); |
125 | | - |
| 255 | + promptTextbox.keyup( function( e ) { |
| 256 | + if ( e.keyCode == 13 ) addButton.click(); |
| 257 | + }); |
126 | 258 | promptTextbox.suggestions( { |
127 | | - 'fetch':_fetchSuggestions, |
| 259 | + 'fetch': fetchSuggestions, |
128 | 260 | 'cancel': function() { |
129 | 261 | var req = _request; |
130 | 262 | // XMLHttpRequest.abort is unimplemented in IE6, also returns nonstandard value of "unknown" for typeof |
— | — | @@ -147,8 +279,8 @@ |
148 | 280 | * |
149 | 281 | * @return array Array of all categories |
150 | 282 | */ |
151 | | - _getCats = function() { |
152 | | - return $container.find( categoryLinkSelector ).map( function() { return $.trim( $( this ).text() ); } ); |
| 283 | + this.getCats = function() { |
| 284 | + return options.$container.find( options.categoryLinkSelector ).map( function() { return $.trim( $( this ).text() ); } ); |
153 | 285 | }; |
154 | 286 | |
155 | 287 | /** |
— | — | @@ -157,31 +289,32 @@ |
158 | 290 | * @return boolean True for exists |
159 | 291 | */ |
160 | 292 | this.containsCat = function( cat ) { |
161 | | - return _getCats().filter( function() { return $.ucFirst(this) == $.ucFirst(cat); } ).length !== 0; |
| 293 | + return that.getCats().filter( function() { return $.ucFirst( this ) == $.ucFirst( cat ); } ).length !== 0; |
162 | 294 | }; |
163 | 295 | |
164 | 296 | /** |
165 | 297 | * This gets called by all action buttons |
166 | 298 | * Displays a dialog to confirm the action |
167 | | - * Afterwords do the actual edit |
| 299 | + * Afterwards do the actual edit |
168 | 300 | * |
169 | 301 | * @param function fn text-modifying function |
170 | 302 | * @param string actionSummary Changes done |
| 303 | + * @param string shortSummary Changes, short version |
171 | 304 | * @param function fn doneFn callback after everything is done |
172 | 305 | * @return boolean True for exists |
173 | 306 | */ |
174 | | - _confirmEdit = function( fn, actionSummary, doneFn, $link, action ) { |
| 307 | + this._confirmEdit = function( fn, actionSummary, shortSummary, doneFn, $link, action ) { |
175 | 308 | // Check whether to use multiEdit mode |
176 | | - if ( _multiEdit && action != 'all' ) { |
| 309 | + if ( options.multiEdit && action != 'all' ) { |
177 | 310 | // Stash away |
178 | | - $link.data('stashIndex', _stash.fns.length ); |
| 311 | + $link.data( 'stashIndex', _stash.fns.length ); |
| 312 | + $link.data( 'summary', actionSummary ); |
179 | 313 | _stash.summaries.push( actionSummary ); |
| 314 | + _stash.shortSum.push( shortSummary ); |
180 | 315 | _stash.fns.push( fn ); |
181 | 316 | |
182 | | - |
183 | | - //TODO add Cancel button |
184 | 317 | _saveAllButton.show(); |
185 | | - //_cancelAllButton.show(); |
| 318 | + _cancelAllButton.show(); |
186 | 319 | |
187 | 320 | // This only does visual changes |
188 | 321 | doneFn( true ); |
— | — | @@ -193,17 +326,9 @@ |
194 | 327 | dialog.addClass( 'mw-ajax-confirm-dialog' ); |
195 | 328 | dialog.attr( 'title', mw.msg( 'ajax-confirm-title' ) ); |
196 | 329 | |
197 | | - // Intro text. |
198 | | - var confirmIntro = $( '<p/>' ); |
199 | | - confirmIntro.text( mw.msg( 'ajax-confirm-prompt' ) ); |
200 | | - dialog.append( confirmIntro ); |
201 | | - |
202 | 330 | // Summary of the action to be taken |
203 | 331 | var summaryHolder = $( '<p/>' ); |
204 | | - var summaryLabel = $( '<strong/>' ); |
205 | | - summaryLabel.text( mw.msg( 'ajax-confirm-actionsummary' ) + " " ); |
206 | | - summaryHolder.text( actionSummary ); |
207 | | - summaryHolder.prepend( summaryLabel ); |
| 332 | + summaryHolder.html( mw.msg( 'ajax-category-question', actionSummary ) ); |
208 | 333 | dialog.append( summaryHolder ); |
209 | 334 | |
210 | 335 | // Reason textbox. |
— | — | @@ -216,15 +341,15 @@ |
217 | 342 | submitButton.val( mw.msg( 'ajax-confirm-save' ) ); |
218 | 343 | |
219 | 344 | var submitFunction = function() { |
220 | | - _addProgressIndicator( dialog ); |
221 | | - _doEdit( |
| 345 | + that._addProgressIndicator( dialog ); |
| 346 | + that._doEdit( |
222 | 347 | mw.config.get( 'wgPageName' ), |
223 | 348 | fn, |
224 | | - reasonBox.val(), |
| 349 | + shortSummary + ': ' + reasonBox.val(), |
225 | 350 | function() { |
226 | 351 | doneFn(); |
227 | 352 | dialog.dialog( 'close' ); |
228 | | - _removeProgressIndicator( dialog ); |
| 353 | + that._removeProgressIndicator( dialog ); |
229 | 354 | } |
230 | 355 | ); |
231 | 356 | }; |
— | — | @@ -239,6 +364,11 @@ |
240 | 365 | |
241 | 366 | $( '#catlinks' ).prepend( dialog ); |
242 | 367 | dialog.dialog( dialogOptions ); |
| 368 | + |
| 369 | + // Close on enter |
| 370 | + dialog.keyup( function( e ) { |
| 371 | + if ( e.keyCode == 13 ) submitFunction(); |
| 372 | + }); |
243 | 373 | }; |
244 | 374 | |
245 | 375 | /** |
— | — | @@ -246,48 +376,60 @@ |
247 | 377 | * this is called when the user clicks "save all" |
248 | 378 | * Combines the summaries and edit functions |
249 | 379 | */ |
250 | | - _handleStashedCategories = function() { |
251 | | - // Save fns |
252 | | - fns = _stash.fns; |
| 380 | + this._handleStashedCategories = function() { |
| 381 | + var summary = '', fns = _stash.fns; |
253 | 382 | |
254 | | - //TODO Add spaces in msg |
255 | | - var summary = _stash.summaries.join(); |
256 | | - if ( summary == '' ) { |
| 383 | + // Remove "holes" in array |
| 384 | + summary = $.grep( _stash.summaries, function( n, i ) { |
| 385 | + return ( n ); |
| 386 | + }); |
| 387 | + if ( summary.length < 1 ) { |
| 388 | + // Nothing to do here. |
257 | 389 | _saveAllButton.hide(); |
| 390 | + _cancelAllButton.hide(); |
258 | 391 | return; |
| 392 | + } else if ( summary.length == 1 ) { |
| 393 | + summary = summary.pop(); |
| 394 | + } else { |
| 395 | + var lastSummary = summary.pop(); |
| 396 | + summary = summary.join( ', '); |
| 397 | + summary += mw.msg( 'ajax-category-and' ) + lastSummary; |
| 398 | + summary = summary.substring( 0, summary.length - 2 ); |
259 | 399 | } |
| 400 | + // Remove "holes" in array |
| 401 | + summaryShort = $.grep( _stash.shortSum, function( n,i ) { |
| 402 | + return ( n ); |
| 403 | + }); |
| 404 | + summaryShort = summaryShort.join( ', ' ); |
| 405 | + |
260 | 406 | var combinedFn = function( oldtext ) { |
261 | 407 | // Run the text through all action functions |
262 | 408 | newtext = oldtext; |
263 | 409 | for ( var i = 0; i < fns.length; i++ ) { |
264 | | - if ( $.isfunction( fns[i] ) ) { |
| 410 | + if ( $.isFunction( fns[i] ) ) { |
265 | 411 | newtext = fns[i]( newtext ); |
| 412 | + if ( newtext === false ) { |
| 413 | + return false; |
| 414 | + } |
266 | 415 | } |
267 | 416 | } |
268 | 417 | return newtext; |
269 | 418 | }; |
270 | | - var doneFn = _resetToActual; |
| 419 | + var doneFn = function() { that.resetAll( true ); }; |
271 | 420 | |
272 | | - _confirmEdit( combinedFn, summary, doneFn, '', 'all' ); |
| 421 | + that._confirmEdit( combinedFn, summary, shortSummary, doneFn, '', 'all' ); |
273 | 422 | }; |
274 | 423 | |
275 | | - _resetToActual = function() { |
276 | | - //Remove saveAllButton |
277 | | - _saveAllButton.hide(); |
278 | | - _cancelAllButton.hide(); |
279 | | - |
280 | | - // Clean stash |
281 | | - _stash.fns = []; |
282 | | - _stash.summaries = []; |
283 | | - |
284 | | - // TODO |
285 | | - $container.find('.mw-removed-category').parent().remove(); |
286 | | - // Any link with $link.css('text-decoration', 'line-through'); |
287 | | - // needs to be removed |
288 | | - |
289 | | - }; |
290 | | - |
291 | | - _doEdit = function( page, fn, summary, doneFn ) { |
| 424 | + /** |
| 425 | + * Do the actual edit. |
| 426 | + * Gets token & text from api, runs it through fn |
| 427 | + * and saves it with summary. |
| 428 | + * @param str page Pagename |
| 429 | + * @param function fn edit function |
| 430 | + * @param str summary |
| 431 | + * @param str doneFn Callback after all is done |
| 432 | + */ |
| 433 | + this._doEdit = function( page, fn, summary, doneFn ) { |
292 | 434 | // Get an edit token for the page. |
293 | 435 | var getTokenVars = { |
294 | 436 | 'action':'query', |
— | — | @@ -298,7 +440,7 @@ |
299 | 441 | 'format':'json' |
300 | 442 | }; |
301 | 443 | |
302 | | - $.get( mw.util.wikiScript( 'api' ), getTokenVars, |
| 444 | + $.post( mw.util.wikiScript( 'api' ), getTokenVars, |
303 | 445 | function( reply ) { |
304 | 446 | var infos = reply.query.pages; |
305 | 447 | $.each( |
— | — | @@ -308,9 +450,17 @@ |
309 | 451 | var timestamp = data.revisions[0].timestamp; |
310 | 452 | var oldText = data.revisions[0]['*']; |
311 | 453 | |
| 454 | + // Replace all nowiki and comments with unique keys |
| 455 | + var key = mw.user.generateId(); |
| 456 | + var nowiki = []; |
| 457 | + oldText = replaceNowikis( oldText, key, nowiki ); |
| 458 | + |
| 459 | + // Then do the changes |
312 | 460 | var newText = fn( oldText ); |
313 | | - |
314 | 461 | if ( newText === false ) return; |
| 462 | + |
| 463 | + // And restore them back |
| 464 | + newText = restoreNowikis( newText, key, nowiki ); |
315 | 465 | |
316 | 466 | var postEditVars = { |
317 | 467 | 'action':'edit', |
— | — | @@ -322,241 +472,245 @@ |
323 | 473 | 'format':'json' |
324 | 474 | }; |
325 | 475 | |
326 | | - $.post( mw.util.wikiScript( 'api' ), postEditVars, doneFn, 'json' ); |
| 476 | + $.post( mw.util.wikiScript( 'api' ), postEditVars, doneFn, 'json' ) |
| 477 | + .error( function( xhr, text, error ) { |
| 478 | + _showError( mw.msg( 'ajax-api-error', text, error ) ); |
| 479 | + }); |
327 | 480 | } |
328 | 481 | ); |
329 | 482 | } |
330 | | - , 'json' ); |
| 483 | + , 'json' ).error( function( xhr, text, error ) { |
| 484 | + _showError( mw.msg( 'ajax-api-error', text, error ) ); |
| 485 | + }); |
331 | 486 | }; |
332 | | - |
333 | 487 | /** |
334 | 488 | * Append spinner wheel to element |
335 | 489 | * @param DOMObject element. |
336 | 490 | */ |
337 | | - _addProgressIndicator = function( elem ) { |
338 | | - var indicator = $( '<div/>' ); |
339 | | - |
340 | | - indicator.addClass( 'mw-ajax-loader' ); |
341 | | - |
342 | | - elem.append( indicator ); |
| 491 | + this._addProgressIndicator = function( elem ) { |
| 492 | + elem.append( $( '<div/>' ).addClass( 'mw-ajax-loader' ) ); |
343 | 493 | }; |
344 | 494 | |
345 | 495 | /** |
346 | 496 | * Find and remove spinner wheel from inside element |
347 | 497 | * @param DOMObject parent element. |
348 | 498 | */ |
349 | | - _removeProgressIndicator = function( elem ) { |
| 499 | + this._removeProgressIndicator = function( elem ) { |
350 | 500 | elem.find( '.mw-ajax-loader' ).remove(); |
351 | 501 | }; |
352 | | - |
| 502 | + |
353 | 503 | /** |
354 | | - * Makes regex string caseinsensitive. |
355 | | - * Useful when 'i' flag can't be used. |
356 | | - * Return stuff like [Ff][Oo][Oo] |
357 | | - * @param string Regex string. |
358 | | - * @return string Processed regex string |
| 504 | + * Checks the API whether the category in question is a redirect. |
| 505 | + * Also returns existance info ( to color link red/blue ) |
| 506 | + * @param string category. |
| 507 | + * @param function callback |
359 | 508 | */ |
360 | | - _makeCaseInsensitive = function( string ) { |
361 | | - var newString = ''; |
362 | | - for (var i=0; i < string.length; i++) { |
363 | | - newString += '[' + string[i].toUpperCase() + string[i].toLowerCase() + ']'; |
364 | | - } |
365 | | - return newString; |
366 | | - }; |
367 | | - _buildRegex = function( category ) { |
368 | | - // Build a regex that matches legal invocations of that category. |
369 | | - var categoryNSFragment = ''; |
370 | | - $.each( mw.config.get( 'wgNamespaceIds' ), function( name, id ) { |
371 | | - if ( id == 14 ) { |
372 | | - // The parser accepts stuff like cATegORy, |
373 | | - // we need to do the same |
374 | | - categoryNSFragment += '|' + _makeCaseInsensitive ( $.escapeRE(name) ); |
375 | | - } |
376 | | - } ); |
377 | | - categoryNSFragment = categoryNSFragment.substr( 1 ); // Remove leading | |
378 | | - |
379 | | - // Build the regex |
380 | | - var titleFragment = $.escapeRE(category); |
381 | | - |
382 | | - firstChar = category.charAt( 0 ); |
383 | | - firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']'; |
384 | | - titleFragment = firstChar + category.substr( 1 ); |
385 | | - var categoryRegex = '\\[\\[(' + categoryNSFragment + '):' + titleFragment + '(\\|[^\\]]*)?\\]\\]'; |
386 | | - |
387 | | - return new RegExp( categoryRegex, 'g' ); |
388 | | - }; |
389 | | - |
390 | | - _handleEditLink = function( e ) { |
391 | | - e.preventDefault(); |
392 | | - var $this = $( this ); |
393 | | - var $link = $this.parent().find( 'a:not(.icon)' ); |
394 | | - var category = $link.text(); |
395 | | - |
396 | | - var $input = _makeSuggestionBox( category, |
397 | | - _handleCategoryEdit, |
398 | | - _multiEdit ? mw.msg( 'ajax-confirm-ok' ) : mw.msg( 'ajax-confirm-save' ) |
399 | | - ); |
400 | | - $link.after( $input ).hide(); |
401 | | - $link.data('editButton').hide(); |
402 | | - $link.data('deleteButton').unbind('click').click( function() { |
403 | | - $input.remove(); |
404 | | - $link.show(); |
405 | | - $link.data('editButton').show(); |
406 | | - $( this ).unbind('click').click( _handleDeleteLink ); |
407 | | - }); |
408 | | - }; |
409 | | - |
410 | | - _handleAddLink = function( e ) { |
411 | | - e.preventDefault(); |
412 | | - |
413 | | - $container.find( '#mw-normal-catlinks>.mw-addcategory-prompt' ).toggle(); |
414 | | - }; |
415 | | - |
416 | | - _handleDeleteLink = function( e ) { |
417 | | - var $this = $( this ); |
418 | | - var $link = $this.parent().find( 'a:not(.icon)' ); |
419 | | - var category = $link.text(); |
420 | | - |
421 | | - if ( $link.hasClass('mw-added-category') ) { |
422 | | - // We're just cancelling the addition |
423 | | - _removeStashItem ( $link ); |
424 | | - |
425 | | - $link.parent().remove(); |
| 509 | + this._resolveRedirects = function( category, callback ) { |
| 510 | + if ( !options.resolveRedirects ) { |
| 511 | + callback( category ); |
426 | 512 | return; |
427 | 513 | } |
| 514 | + var queryVars = { |
| 515 | + 'action':'query', |
| 516 | + 'titles': new mw.Title( category, catNsId ).toString(), |
| 517 | + 'redirects':'', |
| 518 | + 'format' : 'json' |
| 519 | + }; |
428 | 520 | |
429 | | - var categoryRegex = _buildRegex( category ); |
430 | | - |
431 | | - var summary = mw.msg( 'ajax-remove-category-summary', category ); |
432 | | - |
433 | | - _confirmEdit( |
434 | | - function( oldText ) { |
435 | | - newText = _runHooks ( oldText, 'beforeDelete' ); |
436 | | - //TODO Cleanup whitespace safely? |
437 | | - var newText = newText.replace( categoryRegex, '' ); |
438 | | - |
439 | | - if ( newText == oldText ) { |
440 | | - var error = mw.msg( 'ajax-remove-category-error' ); |
441 | | - _showError( error ); |
442 | | - _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) ); |
443 | | - $( '.mw-ajax-confirm-dialog' ).dialog( 'close' ); |
444 | | - return false; |
| 521 | + $.get( mw.util.wikiScript( 'api' ), queryVars, |
| 522 | + function( reply ) { |
| 523 | + var redirect = reply.query.redirects; |
| 524 | + if ( redirect ) { |
| 525 | + category = new mw.Title( redirect[0].to )._name; |
445 | 526 | } |
446 | | - |
447 | | - return newText; |
448 | | - }, |
449 | | - summary, |
450 | | - function( unsaved ) { |
451 | | - if ( unsaved ) { |
452 | | - //TODO Make revertable |
453 | | - $link.addClass('mw-removed-category'); |
454 | | - } else { |
455 | | - $this.parent().remove(); |
456 | | - } |
457 | | - }, |
458 | | - $link, |
459 | | - 'delete' |
460 | | - ); |
| 527 | + callback( category, !reply.query.pages[-1] ); |
| 528 | + } |
| 529 | + , 'json' ); |
461 | 530 | }; |
| 531 | + |
| 532 | + /** |
| 533 | + * Handle add category submit. Not to be called directly |
| 534 | + */ |
| 535 | + this._handleAddLink = function( e ) { |
| 536 | + var $this = $( this ), $link = $(); |
462 | 537 | |
463 | | - _handleCategoryAdd = function( e ) { |
464 | | - var $this = $( this ); |
465 | | - |
466 | 538 | // Grab category text |
467 | 539 | var category = $this.parent().find( '.mw-addcategory-input' ).val(); |
468 | 540 | category = $.ucFirst( category ); |
469 | 541 | |
470 | | - var $link = _createCatDOM( category ); |
471 | | - |
472 | | - if ( aC.containsCat(category) ) { |
| 542 | + // Resolve redirects |
| 543 | + that._resolveRedirects( category, function( resolvedCat, exists ) { |
| 544 | + that.handleCategoryAdd( $link, resolvedCat, false, exists ); |
| 545 | + } ); |
| 546 | + }; |
| 547 | + /** |
| 548 | + * Execute or queue an category add |
| 549 | + */ |
| 550 | + this.handleCategoryAdd = function( $link, category, noAppend, exists ) { |
| 551 | + if ( !$link.length ) { |
| 552 | + $link = that.createCatLink( category ); |
| 553 | + } |
| 554 | + // Mark red if missing |
| 555 | + $link.toggleClass( 'new', exists === false ); |
| 556 | + |
| 557 | + // Handle sortkey |
| 558 | + var arr = category.split( '|' ), sortkey = ''; |
| 559 | + |
| 560 | + if ( arr.length > 1 ) { |
| 561 | + category = arr.shift(); |
| 562 | + sortkey = '|' + arr.join( '|' ); |
| 563 | + if ( sortkey == '|' ) sortkey = ''; |
| 564 | + } |
| 565 | + |
| 566 | + //Replace underscores |
| 567 | + category = category.replace(/_/g, ' ' ); |
| 568 | + |
| 569 | + if ( that.containsCat( category ) ) { |
473 | 570 | _showError( mw.msg( 'ajax-category-already-present', category ) ); |
474 | 571 | return; |
475 | 572 | } |
476 | | - var appendText = "\n[[" + new mw.Title( category, catNsId ) + "]]\n"; |
| 573 | + var catFull = new mw.Title( category, catNsId ).toString().replace(/_/g, ' ' ); |
| 574 | + var appendText = "\n[[" + catFull + sortkey + "]]\n"; |
477 | 575 | var summary = mw.msg( 'ajax-add-category-summary', category ); |
478 | | - |
479 | | - _confirmEdit( |
| 576 | + var shortSummary = '+[[' + catFull + ']]'; |
| 577 | + that._confirmEdit( |
480 | 578 | function( oldText ) { |
481 | | - newText = _runHooks ( oldText, 'beforeAdd' ); |
482 | | - return newText + appendText; |
| 579 | + newText = _runHooks ( oldText, 'beforeAdd', category ); |
| 580 | + newText = newText + appendText; |
| 581 | + return _runHooks ( newText, 'afterAdd', category ); |
483 | 582 | }, |
484 | 583 | summary, |
| 584 | + shortSummary, |
485 | 585 | function( unsaved ) { |
486 | | - $container.find( '#mw-normal-catlinks>.mw-addcategory-prompt' ).toggle(); |
487 | | - $container.find( '#mw-normal-catlinks ul' ).append( $link.parent() ); |
| 586 | + if ( !noAppend ) { |
| 587 | + options.$container.find( '#mw-normal-catlinks>.mw-addcategory-prompt' ).children( 'input' ).hide(); |
| 588 | + options.$container.find( '#mw-normal-catlinks ul' ).append( $link.parent() ); |
| 589 | + } else { |
| 590 | + // Remove input box & button |
| 591 | + $link.data( 'deleteButton' ).click(); |
| 592 | + |
| 593 | + // Update link text and href |
| 594 | + $link.show().text( category ).attr( 'href', catUrl( category ) ); |
| 595 | + } |
488 | 596 | if ( unsaved ) { |
489 | 597 | $link.addClass( 'mw-added-category' ); |
490 | 598 | } |
| 599 | + $( '.mw-ajax-addcategory' ).click(); |
491 | 600 | }, |
492 | 601 | $link, |
493 | 602 | 'add' |
494 | 603 | ); |
495 | 604 | }; |
| 605 | + this._createEditInterface = function( e ) { |
| 606 | + var $this = $( this ), |
| 607 | + $link = $this.data( 'link' ), |
| 608 | + category = $link.text(); |
| 609 | + var $input = that._makeSuggestionBox( category, |
| 610 | + that._handleEditLink, |
| 611 | + options.multiEdit ? mw.msg( 'ajax-confirm-ok' ) : mw.msg( 'ajax-confirm-save' ) |
| 612 | + ); |
| 613 | + $link.after( $input ).hide(); |
| 614 | + $input.find( '.mw-addcategory-input' ).focus(); |
| 615 | + $link.data( 'editButton' ).hide(); |
| 616 | + $link.data( 'deleteButton' ).unbind( 'click' ).click( function() { |
| 617 | + $input.remove(); |
| 618 | + $link.show(); |
| 619 | + $link.data( 'editButton' ).show(); |
| 620 | + $( this ).unbind( 'click' ).click( that._handleDeleteLink ) |
| 621 | + .attr( 'title', mw.msg( 'ajax-remove-category' )); |
| 622 | + }).attr( 'title', mw.msg( 'ajax-cancel' )); |
| 623 | + }; |
| 624 | + |
| 625 | + /** |
| 626 | + * Handle edit category submit. Not to be called directly |
| 627 | + */ |
| 628 | + this._handleEditLink = function( e ) { |
| 629 | + var $this = $( this ), |
| 630 | + $link = $this.parent().parent().find( 'a:not(.icon)' ), |
| 631 | + categoryNew, sortkey = ''; |
| 632 | + |
| 633 | + // Grab category text |
| 634 | + categoryNew = $this.parent().find( '.mw-addcategory-input' ).val(); |
| 635 | + categoryNew = $.ucFirst( categoryNew.replace(/_/g, ' ' ) ); |
| 636 | + |
| 637 | + // Strip sortkey |
| 638 | + var arr = categoryNew.split( '|' ); |
| 639 | + if ( arr.length > 1 ) { |
| 640 | + categoryNew = arr.shift(); |
| 641 | + sortkey = '|' + arr.join( '|' ); |
| 642 | + } |
496 | 643 | |
497 | | - _handleCategoryEdit = function( e ) { |
498 | | - var $this = $( this ); |
| 644 | + // Grab text |
| 645 | + var added = $link.hasClass( 'mw-added-category' ); |
| 646 | + that.resetCatLink ( $link ); |
| 647 | + var category = $link.text(); |
499 | 648 | |
500 | | - // Grab category text |
501 | | - var categoryNew = $this.parent().find( '.mw-addcategory-input' ).val(); |
502 | | - categoryNew = $.ucFirst( categoryNew ); |
| 649 | + // Check for dupes ( exluding itself ) |
| 650 | + if ( category != categoryNew && that.containsCat( categoryNew ) ) { |
| 651 | + $link.data( 'deleteButton' ).click(); |
| 652 | + return; |
| 653 | + } |
503 | 654 | |
504 | | - var $link = $this.parent().parent().find( 'a:not(.icon)' ); |
505 | | - if ( $link.hasClass('mw-removed-category') ) { |
506 | | - _removeStashItem ( $link ); |
507 | | - $link.removeClass( 'mw-removed-category' ); |
| 655 | + // Resolve redirects |
| 656 | + that._resolveRedirects( categoryNew, function( resolvedCat, exists ) { |
| 657 | + that.handleCategoryEdit( $link, category, resolvedCat, sortkey, exists, added ); |
| 658 | + }); |
| 659 | + }; |
| 660 | + /** |
| 661 | + * Execute or queue an category edit |
| 662 | + */ |
| 663 | + this.handleCategoryEdit = function( $link, category, categoryNew, sortkeyNew, exists, added ) { |
| 664 | + // Category add needs to be handled differently |
| 665 | + if ( added ) { |
| 666 | + // Pass sortkey back |
| 667 | + that.handleCategoryAdd( $link, categoryNew + sortkeyNew, true ); |
| 668 | + return; |
508 | 669 | } |
509 | | - if ( $link.data( 'origCat' ) ) { |
510 | | - var category = $link.data( 'origCat' ); |
511 | | - _removeStashItem ( $link ); |
512 | | - } else { |
513 | | - var category = $link.text(); |
514 | | - } |
515 | | - |
516 | | - |
517 | 670 | // User didn't change anything. |
518 | | - if ( category == categoryNew ) { |
519 | | - $link.data('deleteButton').click(); |
| 671 | + if ( category == categoryNew + sortkeyNew ) { |
| 672 | + $link.data( 'deleteButton' ).click(); |
520 | 673 | return; |
521 | 674 | } |
522 | | - categoryRegex = _buildRegex( category ); |
523 | | - |
| 675 | + // Mark red if missing |
| 676 | + $link.toggleClass( 'new', exists === false ); |
| 677 | + |
| 678 | + categoryRegex = buildRegex( category ); |
| 679 | + |
524 | 680 | var summary = mw.msg( 'ajax-edit-category-summary', category, categoryNew ); |
525 | | - |
526 | | - _confirmEdit( |
| 681 | + var shortSummary = '[[' + new mw.Title( category, catNsId ) + ']] -> [[' + new mw.Title( categoryNew, catNsId ) + ']]'; |
| 682 | + that._confirmEdit( |
527 | 683 | function( oldText ) { |
528 | | - newText = _runHooks ( oldText, 'beforeChange' ); |
| 684 | + newText = _runHooks ( oldText, 'beforeChange', category, categoryNew ); |
529 | 685 | |
530 | 686 | var matches = newText.match( categoryRegex ); |
531 | 687 | |
532 | 688 | //Old cat wasn't found, likely to be transcluded |
533 | 689 | if ( !$.isArray( matches ) ) { |
534 | | - var error = mw.msg( 'ajax-edit-category-error' ); |
535 | | - _showError( error ); |
536 | | - _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) ); |
537 | | - $( '.mw-ajax-confirm-dialog' ).dialog( 'close' ); |
| 690 | + _showError( mw.msg( 'ajax-edit-category-error' ) ); |
538 | 691 | return false; |
539 | 692 | } |
540 | | - var sortkey = matches[0].replace( categoryRegex, '$2' ); |
541 | | - var newCategoryString = "[[" + new mw.Titel( categoryNew, catNsId ) + sortkey + ']]'; |
| 693 | + var sortkey = sortkeyNew || matches[0].replace( categoryRegex, '$2' ); |
| 694 | + var newCategoryString = "[[" + new mw.Title( categoryNew, catNsId ) + sortkey + ']]'; |
542 | 695 | |
543 | | - if (matches.length > 1) { |
| 696 | + if ( matches.length > 1 ) { |
544 | 697 | // The category is duplicated. |
545 | 698 | // Remove all but one match |
546 | | - for (var i = 1; i < matches.length; i++) { |
547 | | - oldText = oldText.replace( matches[i], ''); |
| 699 | + for ( var i = 1; i < matches.length; i++ ) { |
| 700 | + oldText = oldText.replace( matches[i], '' ); |
548 | 701 | } |
549 | 702 | } |
550 | 703 | var newText = oldText.replace( categoryRegex, newCategoryString ); |
551 | 704 | |
552 | | - return newText; |
| 705 | + return _runHooks ( newText, 'afterChange', category, categoryNew ); |
553 | 706 | }, |
554 | 707 | summary, |
| 708 | + shortSummary, |
555 | 709 | function( unsaved ) { |
556 | 710 | // Remove input box & button |
557 | | - $link.data('deleteButton').click(); |
| 711 | + $link.data( 'deleteButton' ).click(); |
558 | 712 | |
559 | 713 | // Update link text and href |
560 | | - $link.show().text( categoryNew ).attr( 'href', catLink( categoryNew ) ); |
| 714 | + $link.show().text( categoryNew ).attr( 'href', catUrl( categoryNew ) ); |
561 | 715 | if ( unsaved ) { |
562 | 716 | $link.data( 'origCat', category ).addClass( 'mw-changed-category' ); |
563 | 717 | } |
— | — | @@ -565,20 +719,79 @@ |
566 | 720 | 'edit' |
567 | 721 | ); |
568 | 722 | }; |
| 723 | + |
| 724 | + /** |
| 725 | + * Handle delete category submit. Not to be called directly |
| 726 | + */ |
| 727 | + this._handleDeleteLink = function() { |
| 728 | + var $this = $( this ), |
| 729 | + $link = $this.parent().find( 'a:not(.icon)' ), |
| 730 | + category = $link.text(); |
569 | 731 | |
| 732 | + if ( $link.is( '.mw-added-category, .mw-changed-category' ) ) { |
| 733 | + // We're just cancelling the addition or edit |
| 734 | + that.resetCatLink ( $link, $link.hasClass( 'mw-added-category' ) ); |
| 735 | + return; |
| 736 | + } else if ( $link.is( '.mw-removed-category' ) ) { |
| 737 | + // It's already removed... |
| 738 | + return; |
| 739 | + } |
| 740 | + that.handleCategoryDelete( $link, category ); |
| 741 | + }; |
| 742 | + |
570 | 743 | /** |
| 744 | + * Execute or queue an category delete |
| 745 | + */ |
| 746 | + this.handleCategoryDelete = function( $link, category ) { |
| 747 | + var categoryRegex = buildRegex( category, true ); |
| 748 | + |
| 749 | + var summary = mw.msg( 'ajax-remove-category-summary', category ); |
| 750 | + var shortSummary = '-[[' + new mw.Title( category, catNsId ) + ']]'; |
| 751 | + |
| 752 | + that._confirmEdit( |
| 753 | + function( oldText ) { |
| 754 | + newText = _runHooks ( oldText, 'beforeDelete', category ); |
| 755 | + var newText = newText.replace( categoryRegex, '' ); |
| 756 | + |
| 757 | + if ( newText == oldText ) { |
| 758 | + _showError( mw.msg( 'ajax-remove-category-error' ) ); |
| 759 | + return false; |
| 760 | + } |
| 761 | + |
| 762 | + return _runHooks ( newText, 'afterDelete', category ); |
| 763 | + }, |
| 764 | + summary, |
| 765 | + shortSummary, |
| 766 | + function( unsaved ) { |
| 767 | + if ( unsaved ) { |
| 768 | + $link.addClass( 'mw-removed-category' ); |
| 769 | + } else { |
| 770 | + $link.parent().remove(); |
| 771 | + } |
| 772 | + }, |
| 773 | + $link, |
| 774 | + 'delete' |
| 775 | + ); |
| 776 | + }; |
| 777 | + |
| 778 | + |
| 779 | + /** |
571 | 780 | * Open a dismissable error dialog |
572 | 781 | * |
573 | 782 | * @param string str The error description |
574 | 783 | */ |
575 | 784 | _showError = function( str ) { |
| 785 | + var oldDialog = $( '.mw-ajax-confirm-dialog' ); |
| 786 | + that._removeProgressIndicator( oldDialog ); |
| 787 | + oldDialog.dialog( 'close' ); |
| 788 | + |
576 | 789 | var dialog = $( '<div/>' ); |
577 | 790 | dialog.text( str ); |
578 | 791 | |
579 | 792 | mw.util.$content.append( dialog ); |
580 | 793 | |
581 | 794 | var buttons = { }; |
582 | | - buttons[mw.msg( 'ajax-error-dismiss' )] = function( e ) { |
| 795 | + buttons[mw.msg( 'ajax-confirm-ok' )] = function( e ) { |
583 | 796 | dialog.dialog( 'close' ); |
584 | 797 | }; |
585 | 798 | var dialogOptions = { |
— | — | @@ -588,6 +801,11 @@ |
589 | 802 | }; |
590 | 803 | |
591 | 804 | dialog.dialog( dialogOptions ); |
| 805 | + |
| 806 | + // Close on enter |
| 807 | + dialog.keyup( function( e ) { |
| 808 | + if ( e.keyCode == 13 ) dialog.dialog( 'close' ); |
| 809 | + }); |
592 | 810 | }; |
593 | 811 | |
594 | 812 | /** |
— | — | @@ -601,11 +819,12 @@ |
602 | 820 | * @return jQueryObject The button |
603 | 821 | */ |
604 | 822 | _createButton = function( icon, title, className, text ){ |
| 823 | + // We're adding a zero width space for IE7, it's got problems with empty nodes apparently |
605 | 824 | var $button = $( '<a>' ).addClass( className || '' ) |
606 | | - .attr('title', title); |
| 825 | + .attr( 'title', title ).html( '​' ); |
607 | 826 | |
608 | 827 | if ( text ) { |
609 | | - var $icon = $( '<a>' ).addClass( 'icon ' + icon ); |
| 828 | + var $icon = $( '<span>' ).addClass( 'icon ' + icon ).html( '​' ); |
610 | 829 | $button.addClass( 'icon-parent' ).append( $icon ).append( text ); |
611 | 830 | } else { |
612 | 831 | $button.addClass( 'icon ' + icon ); |
— | — | @@ -618,60 +837,64 @@ |
619 | 838 | * |
620 | 839 | * @param DOMElement element Anchor element, to which the buttons should be appended. |
621 | 840 | */ |
622 | | - _createCatButtons = function( element ) { |
| 841 | + _createCatButtons = function( $element ) { |
623 | 842 | // Create remove & edit buttons |
624 | | - var deleteButton = _createButton('icon-close', mw.msg( 'ajax-remove-category' ) ); |
625 | | - var editButton = _createButton('icon-edit', mw.msg( 'ajax-edit-category' ) ); |
| 843 | + var deleteButton = _createButton( 'icon-close', mw.msg( 'ajax-remove-category' ) ); |
| 844 | + var editButton = _createButton( 'icon-edit', mw.msg( 'ajax-edit-category' ) ); |
626 | 845 | |
627 | 846 | //Not yet used |
628 | | - var saveButton = _createButton('icon-tick', mw.msg( 'ajax-confirm-save' ) ).hide(); |
| 847 | + var saveButton = _createButton( 'icon-tick', mw.msg( 'ajax-confirm-save' ) ).hide(); |
629 | 848 | |
630 | | - deleteButton.click( _handleDeleteLink ); |
631 | | - editButton.click( _handleEditLink ); |
| 849 | + deleteButton.click( that._handleDeleteLink ); |
| 850 | + editButton.click( that._createEditInterface ); |
632 | 851 | |
633 | | - $( element ).after( deleteButton ).after( editButton ); |
| 852 | + $element.after( deleteButton ).after( editButton ); |
634 | 853 | |
635 | 854 | //Save references to all links and buttons |
636 | | - $( element ).data({ |
| 855 | + $element.data({ |
637 | 856 | saveButton : saveButton, |
638 | 857 | deleteButton: deleteButton, |
639 | 858 | editButton : editButton |
640 | 859 | }); |
| 860 | + editButton.data({ |
| 861 | + link : $element |
| 862 | + }); |
641 | 863 | }; |
| 864 | + |
| 865 | + /** |
| 866 | + * Create the UI |
| 867 | + */ |
642 | 868 | this.setup = function() { |
643 | 869 | // Could be set by gadgets like HotCat etc. |
644 | | - if ( mw.config.get('disableAJAXCategories') ) { |
645 | | - return; |
| 870 | + if ( mw.config.get( 'disableAJAXCategories' ) ) { |
| 871 | + return false; |
646 | 872 | } |
647 | 873 | // Only do it for articles. |
648 | 874 | if ( !mw.config.get( 'wgIsArticle' ) ) return; |
649 | 875 | |
650 | | - // Unhide hidden category holders. |
651 | | - $('#mw-hidden-catlinks').show(); |
652 | | - |
653 | | - var $li = $('<li>', { |
654 | | - 'class' : 'mw-ajax-addcategory-holder' |
655 | | - }); |
656 | | - // $containerNormal.find('ul').append( $li) |
657 | 876 | // Create [Add Category] link |
658 | | - var addLink = _createButton('icon-add', |
| 877 | + var addLink = _createButton( 'icon-add', |
659 | 878 | mw.msg( 'ajax-add-category' ), |
660 | 879 | 'mw-ajax-addcategory', |
661 | 880 | mw.msg( 'ajax-add-category' ) |
662 | 881 | ); |
663 | | - addLink.click( _handleAddLink ); |
664 | | - $containerNormal.append( addLink ); |
| 882 | + addLink.click( function() { |
| 883 | + $( this ).nextAll().toggle().filter( '.mw-addcategory-input' ).focus(); |
| 884 | + }); |
| 885 | + |
665 | 886 | |
666 | 887 | // Create add category prompt |
667 | | - var promptContainer = _makeSuggestionBox( '', _handleCategoryAdd, mw.msg( 'ajax-add-category-submit' ) ); |
668 | | - promptContainer.hide(); |
| 888 | + _addContainer = that._makeSuggestionBox( '', that._handleAddLink, mw.msg( 'ajax-add-category-submit' ) ); |
| 889 | + _addContainer.children().hide(); |
669 | 890 | |
| 891 | + _addContainer.prepend( addLink ); |
| 892 | + |
670 | 893 | // Create edit & delete link for each category. |
671 | 894 | $( '#catlinks li a' ).each( function() { |
672 | | - _createCatButtons( this ); |
| 895 | + _createCatButtons( $( this ) ); |
673 | 896 | }); |
674 | 897 | |
675 | | - $containerNormal.append( promptContainer ); |
| 898 | + options.$containerNormal.append( _addContainer ); |
676 | 899 | |
677 | 900 | //TODO Make more clickable |
678 | 901 | _saveAllButton = _createButton( 'icon-tick', |
— | — | @@ -679,53 +902,78 @@ |
680 | 903 | '', |
681 | 904 | mw.msg( 'ajax-confirm-save-all' ) |
682 | 905 | ); |
683 | | - _cancelAllButton = _createButton( 'icon-tick', |
684 | | - mw.msg( 'ajax-confirm-save-all' ), |
| 906 | + _cancelAllButton = _createButton( 'icon-close', |
| 907 | + mw.msg( 'ajax-cancel-all' ), |
685 | 908 | '', |
686 | | - mw.msg( 'ajax-confirm-save-all' ) |
| 909 | + mw.msg( 'ajax-cancel-all' ) |
687 | 910 | ); |
688 | | - _saveAllButton.click( _handleStashedCategories ).hide(); |
689 | | - _cancelAllButton.hide(); |
690 | | - //TODO wrap in div display:inline-block |
691 | | - $containerNormal.append( _saveAllButton ).append( _cancelAllButton ); |
| 911 | + _saveAllButton.click( that._handleStashedCategories ).hide(); |
| 912 | + _cancelAllButton.click( function() { that.resetAll( false ); } ).hide(); |
| 913 | + options.$containerNormal.append( _saveAllButton ).append( _cancelAllButton ); |
| 914 | + options.$container.append( _addContainer ); |
692 | 915 | }; |
693 | 916 | |
694 | 917 | _stash = { |
695 | 918 | summaries : [], |
| 919 | + shortSum : [], |
696 | 920 | fns : [] |
697 | 921 | }; |
698 | | - _removeStashItem = function( $link ) { |
699 | | - var i = $link.data( 'stashIndex' ); |
| 922 | + _removeStashItem = function( i ) { |
| 923 | + if ( typeof i != "number" ) { |
| 924 | + i = i.data( 'stashIndex' ); |
| 925 | + } |
700 | 926 | delete _stash.fns[i]; |
701 | 927 | delete _stash.summaries[i]; |
702 | | - } |
| 928 | + if ( $.isEmpty( _stash.fns ) ) { |
| 929 | + _stash.fns = []; |
| 930 | + _stash.summaries = []; |
| 931 | + _stash.shortSum = []; |
| 932 | + _saveAllButton.hide(); |
| 933 | + _cancelAllButton.hide(); |
| 934 | + } |
| 935 | + }; |
703 | 936 | _hooks = { |
704 | 937 | beforeAdd : [], |
705 | 938 | beforeChange : [], |
706 | | - beforeDelete : [] |
| 939 | + beforeDelete : [], |
| 940 | + afterAdd : [], |
| 941 | + afterChange : [], |
| 942 | + afterDelete : [] |
707 | 943 | }; |
708 | | - _runHooks = function( oldtext, type ) { |
| 944 | + _runHooks = function( oldtext, type, category, categoryNew ) { |
709 | 945 | // No hooks registered |
710 | | - if ( !_hooks[type] ) { |
| 946 | + if ( !_hooks[type] ) { |
711 | 947 | return oldtext; |
712 | 948 | } else { |
713 | | - for (var i = 0; i < _hooks[type].length; i++) { |
714 | | - oldtext = _hooks[type][i]( oldtext ); |
| 949 | + for ( var i = 0; i < _hooks[type].length; i++ ) { |
| 950 | + oldtext = _hooks[type][i]( oldtext, category, categoryNew ); |
| 951 | + if ( oldtext === false ) { |
| 952 | + _showError( mw.msg( 'ajax-category-hook-error', category ) ); |
| 953 | + return; |
| 954 | + } |
715 | 955 | } |
716 | 956 | return oldtext; |
717 | 957 | } |
718 | 958 | }; |
719 | 959 | /** |
720 | 960 | * Add hooks |
721 | | - * Currently available: beforeAdd, beforeChange, beforeDelete |
| 961 | + * Currently available: beforeAdd, beforeChange, beforeDelete, |
| 962 | + * afterAdd, afterChange, afterDelete |
| 963 | + * If the hook function returns false, all changes are aborted. |
722 | 964 | * |
723 | 965 | * @param string type Type of hook to add |
724 | | - * @param function fn Hook function. This function is the old text passed |
725 | | - * and it needs to return the modified text |
| 966 | + * @param function fn Hook function. The following vars are passed to it: |
| 967 | + * 1. oldtext: The wikitext before the hook |
| 968 | + * 2. category: The deleted, added, or changed category |
| 969 | + * 3. (only for beforeChange/afterChange): newcategory |
726 | 970 | */ |
727 | 971 | this.addHook = function( type, fn ) { |
728 | | - if ( !_hooks[type] ) return; |
729 | | - else hooks[type].push( fn ); |
| 972 | + if ( !_hooks[type] || !$.isFunction( fn ) ) { |
| 973 | + return; |
| 974 | + } |
| 975 | + else { |
| 976 | + hooks[type].push( fn ); |
| 977 | + } |
730 | 978 | }; |
731 | 979 | }; |
732 | 980 | |
Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.css |
— | — | @@ -17,11 +17,16 @@ |
18 | 18 | #catlinks:hover .icon { |
19 | 19 | opacity: 1; |
20 | 20 | } |
21 | | - |
| 21 | +#catlinks ul { |
| 22 | + margin-right: 2em; |
| 23 | +} |
| 24 | +.mw-ajax-addcategory-holder { |
| 25 | + display: inline-block; |
| 26 | +} |
22 | 27 | .mw-ajax-addcategory { |
23 | | - padding-left: 30px; |
24 | 28 | margin-right: 1em; |
25 | 29 | cursor: pointer; |
| 30 | + display:inline-block; |
26 | 31 | } |
27 | 32 | #catlinks .icon { |
28 | 33 | cursor: pointer; |
— | — | @@ -29,9 +34,11 @@ |
30 | 35 | margin: 0; |
31 | 36 | background: url('images/AJAXCategorySprite.png') 0 0 no-repeat; |
32 | 37 | opacity: 0.5; |
| 38 | + |
33 | 39 | } |
34 | 40 | #catlinks .icon-parent { |
35 | 41 | cursor: pointer; |
| 42 | + margin-right: 1em; |
36 | 43 | } |
37 | 44 | #catlinks .icon-parent:hover .icon { |
38 | 45 | background-position-y: -16px; |
— | — | @@ -61,4 +68,4 @@ |
62 | 69 | } |
63 | 70 | #catlinks .icon-add:hover { |
64 | 71 | background-position: -64px -16px; |
65 | | -} |
\ No newline at end of file |
| 72 | +} |