Index: trunk/phase3/maintenance/language/messages.inc |
— | — | @@ -3459,6 +3459,24 @@ |
3460 | 3460 | 'unwatch' => array( |
3461 | 3461 | 'confirm-unwatch-button', |
3462 | 3462 | ), |
| 3463 | + 'ajax-category' => array( |
| 3464 | + 'ajax-add-category', |
| 3465 | + 'ajax-remove-category', |
| 3466 | + 'ajax-edit-category', |
| 3467 | + 'ajax-add-category-submit', |
| 3468 | + 'ajax-confirm-title', |
| 3469 | + 'ajax-confirm-prompt', |
| 3470 | + 'ajax-confirm-save', |
| 3471 | + 'ajax-add-category-summary', |
| 3472 | + 'ajax-edit-category-summary', |
| 3473 | + 'ajax-remove-category-summary', |
| 3474 | + 'ajax-confirm-actionsummary', |
| 3475 | + 'ajax-error-title', |
| 3476 | + 'ajax-error-dismiss', |
| 3477 | + 'ajax-remove-category-error', |
| 3478 | + 'ajax-edit-category-error', |
| 3479 | + ), |
| 3480 | + |
3463 | 3481 | ); |
3464 | 3482 | |
3465 | 3483 | /** Comments for each block */ |
— | — | @@ -3687,4 +3705,5 @@ |
3688 | 3706 | 'db-error-messages' => 'Database error messages', |
3689 | 3707 | 'html-forms' => 'HTML forms', |
3690 | 3708 | 'sqlite' => 'SQLite database support', |
| 3709 | + 'ajax-category' => 'Add categories per AJAX', |
3691 | 3710 | ); |
Index: trunk/phase3/skins/common/shared.css |
— | — | @@ -583,32 +583,6 @@ |
584 | 584 | word-wrap: break-word; |
585 | 585 | } |
586 | 586 | |
587 | | -#mw-addcategory-prompt { |
588 | | - display: inline; |
589 | | - margin-left: 1em; |
590 | | -} |
591 | | - |
592 | | -#mw-addcategory-prompt input { |
593 | | - margin-left: 0.5em; |
594 | | - margin-right: 0.5em; |
595 | | -} |
596 | | - |
597 | | -.mw-remove-category { |
598 | | - padding: 8px; |
599 | | - /* @embed */ |
600 | | - background-image: url(images/remove.png); |
601 | | - background-position: center center; |
602 | | - background-repeat: no-repeat; |
603 | | -} |
604 | | - |
605 | | -.mw-ajax-addcategory { |
606 | | - padding-left: 20px; |
607 | | - /* @embed */ |
608 | | - background-image: url(images/add.png); |
609 | | - background-position: left center; |
610 | | - background-repeat: no-repeat; |
611 | | -} |
612 | | - |
613 | 587 | .mw-ajax-loader { |
614 | 588 | /* @embed */ |
615 | 589 | background-image: url(images/ajax-loader.gif); |
Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -2340,8 +2340,8 @@ |
2341 | 2341 | * Add the default ResourceLoader modules to this object |
2342 | 2342 | */ |
2343 | 2343 | private function addDefaultModules() { |
2344 | | - global $wgIncludeLegacyJavaScript, |
2345 | | - $wgUseAjax, $wgAjaxWatch, $wgEnableMWSuggest; |
| 2344 | + global $wgIncludeLegacyJavaScript, $wgUseAjax, |
| 2345 | + $wgAjaxWatch, $wgEnableMWSuggest, $wgUseAJAXCategories; |
2346 | 2346 | |
2347 | 2347 | // Add base resources |
2348 | 2348 | $this->addModules( array( |
— | — | @@ -2369,9 +2369,19 @@ |
2370 | 2370 | } |
2371 | 2371 | } |
2372 | 2372 | |
2373 | | - if( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) { |
| 2373 | + if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) { |
2374 | 2374 | $this->addModules( 'mediawiki.action.view.rightClickEdit' ); |
2375 | 2375 | } |
| 2376 | + |
| 2377 | + if ( $wgUseAJAXCategories ) { |
| 2378 | + global $wgAJAXCategoriesNamespaces; |
| 2379 | + |
| 2380 | + $title = $this->getTitle(); |
| 2381 | + |
| 2382 | + if( empty( $wgAJAXCategoriesNamespaces ) || in_array( $title->getNamespace(), $wgAJAXCategoriesNamespaces ) ) { |
| 2383 | + $this->addModules( 'mediawiki.page.ajaxCategories' ); |
| 2384 | + } |
| 2385 | + } |
2376 | 2386 | } |
2377 | 2387 | |
2378 | 2388 | /** |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -5501,6 +5501,19 @@ |
5502 | 5502 | $wgDBtestpassword = ''; |
5503 | 5503 | |
5504 | 5504 | /** |
| 5505 | + * Whether or not to use the AJAX categories system. |
| 5506 | + */ |
| 5507 | +$wgUseAJAXCategories = false; |
| 5508 | + |
| 5509 | +/** |
| 5510 | + * Only enable AJAXCategories on configured namespaces. Default is all. |
| 5511 | + * |
| 5512 | + * Example: |
| 5513 | + * $wgAJAXCategoriesNamespaces = array( NS_MAIN, NS_PROJECT ); |
| 5514 | + */ |
| 5515 | +$wgAJAXCategoriesNamespaces = array(); |
| 5516 | + |
| 5517 | +/** |
5505 | 5518 | * For really cool vim folding this needs to be at the end: |
5506 | 5519 | * vim: foldmarker=@{,@} foldmethod=marker |
5507 | 5520 | * @} |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -4586,4 +4586,24 @@ |
4587 | 4587 | 'sqlite-has-fts' => '$1 with full-text search support', |
4588 | 4588 | 'sqlite-no-fts' => '$1 without full-text search support', |
4589 | 4589 | |
| 4590 | + |
| 4591 | +# Add categories per AJAX |
| 4592 | +'ajax-add-category' => 'Add category', |
| 4593 | +'ajax-remove-category' => 'Remove category', |
| 4594 | +'ajax-edit-category' => 'Edit category', |
| 4595 | +'ajax-add-category-submit' => 'Add', |
| 4596 | +'ajax-confirm-title' => 'Confirm action', |
| 4597 | +'ajax-confirm-prompt' => 'You can provide an edit summary below. |
| 4598 | +Click "Save" to save your edit.', |
| 4599 | +'ajax-confirm-save' => 'Save', |
| 4600 | +'ajax-add-category-summary' => 'Add category "$1"', |
| 4601 | +'ajax-edit-category-summary' => 'Change category "$1" to "$2"', |
| 4602 | +'ajax-remove-category-summary' => 'Remove category "$1"', |
| 4603 | +'ajax-confirm-actionsummary' => 'Action to take:', |
| 4604 | +'ajax-error-title' => 'Error', |
| 4605 | +'ajax-error-dismiss' => 'OK', |
| 4606 | +'ajax-remove-category-error' => 'It was not possible to remove this category. |
| 4607 | +This usually occurs when the category has been added to the page in a template.', |
| 4608 | +'ajax-edit-category-error' => 'It was not possible to edit this category. |
| 4609 | +This usually occurs when the category has been added to the page in a template.', |
4590 | 4610 | ); |
Index: trunk/phase3/resources/Resources.php |
— | — | @@ -483,6 +483,31 @@ |
484 | 484 | 'jquery.ui.autocomplete', |
485 | 485 | ), |
486 | 486 | ), |
| 487 | + 'mediawiki.page.ajaxCategories' => array( |
| 488 | + 'scripts' => 'resources/mediawiki.page/mediawiki.page.ajaxCategories.js', |
| 489 | + 'styles' => 'resources/mediawiki.page/mediawiki.page.ajaxCategories.css', |
| 490 | + 'dependencies' => array( |
| 491 | + 'jquery.suggestions', |
| 492 | + 'jquery.ui.dialog', |
| 493 | + ), |
| 494 | + 'messages' => array( |
| 495 | + 'ajax-add-category', |
| 496 | + 'ajax-remove-category', |
| 497 | + 'ajax-edit-category', |
| 498 | + 'ajax-add-category-submit', |
| 499 | + 'ajax-confirm-prompt', |
| 500 | + 'ajax-confirm-title', |
| 501 | + 'ajax-confirm-save', |
| 502 | + 'ajax-add-category-summary', |
| 503 | + 'ajax-remove-category-summary', |
| 504 | + 'ajax-edit-category-summary', |
| 505 | + 'ajax-confirm-actionsummary', |
| 506 | + 'ajax-error-title', |
| 507 | + 'ajax-error-dismiss', |
| 508 | + 'ajax-remove-category-error', |
| 509 | + 'ajax-edit-category-error', |
| 510 | + ), |
| 511 | + ), |
487 | 512 | 'mediawiki.libs.jpegmeta' => array( |
488 | 513 | 'scripts' => 'resources/mediawiki.libs/mediawiki.libs.jpegmeta.js', |
489 | 514 | ), |
Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js |
— | — | @@ -1,50 +1,42 @@ |
2 | | -loadGM( { |
3 | | - "ajax-add-category" : "[Add Category]", |
4 | | - "ajax-add-category-submit" : "[Add]", |
5 | | - "ajax-confirm-prompt" : "[Confirmation Text]", |
6 | | - "ajax-confirm-title" : "[Confirmation Title]", |
7 | | - "ajax-confirm-save" : "[Save]", |
8 | | - "ajax-add-category-summary" : "[Add category $1]", |
9 | | - "ajax-remove-category-summary" : "[Remove category $2]", |
10 | | - "ajax-confirm-actionsummary" : "[Summary]", |
11 | | - "ajax-error-title" : "Error", |
12 | | - "ajax-error-dismiss" : "OK", |
13 | | - "ajax-remove-category-error" : "[RemoveErr]" |
14 | | -} ); |
| 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 | +// * Add Hooks for change, delete, add |
| 9 | +// * Add Hooks for soft redirect |
| 10 | +// * Handle normal redirects |
| 11 | +// * api.php / api.php5 |
| 12 | +// * Simple / MultiEditMode |
15 | 13 | |
16 | | -var ajaxCategories = { |
17 | | - handleAddLink : function( e ) { |
18 | | - e.preventDefault(); |
| 14 | +( function( $, mw ) { |
| 15 | + var catLinkWrapper = '<li/>' |
| 16 | + var $container = $( '.catlinks' ); |
| 17 | + |
| 18 | + var categoryLinkSelector = '#mw-normal-catlinks li a'; |
| 19 | + var _request; |
| 20 | + |
| 21 | + var _catElements = {}; |
| 22 | + var _otherElements = {}; |
19 | 23 | |
20 | | - // Make sure the suggestion plugin is loaded. Load everything else while we're at it |
21 | | - mvJsLoader.doLoad( |
22 | | - ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'], |
23 | | - function() { |
24 | | - $j( '#mw-addcategory-prompt' ).toggle(); |
| 24 | + var namespaceIds = mw.config.get( 'wgNamespaceIds' ) |
| 25 | + var categoryNamespaceId = namespaceIds['category']; |
| 26 | + var categoryNamespace = mw.config.get( 'wgFormattedNamespaces' )[categoryNamespaceId]; |
| 27 | + var wgScriptPath = mw.config.get( 'wgScriptPath' ); |
25 | 28 | |
26 | | - $j( '#mw-addcategory-input' ).suggestions( { |
27 | | - 'fetch':ajaxCategories.fetchSuggestions, |
28 | | - 'cancel': function() { |
29 | | - var req = ajaxCategories.request; |
30 | | - if ( req.abort ) |
31 | | - req.abort(); |
32 | | - } |
33 | | - } ); |
34 | | - |
35 | | - $j( '#mw-addcategory-input' ).suggestions(); |
36 | | - } |
37 | | - ); |
38 | | - }, |
39 | | - |
40 | | - fetchSuggestions : function( query ) { |
41 | | - var that = this; |
42 | | - var request = $j.ajax( { |
| 29 | + function _fetchSuggestions ( query ) { |
| 30 | + //SYNCED |
| 31 | + var _this = this; |
| 32 | + // ignore bad characters, they will be stripped out |
| 33 | + var catName = _stripIllegals( $( this ).val() ); |
| 34 | + var request = $.ajax( { |
43 | 35 | url: wgScriptPath + '/api.php', |
44 | 36 | data: { |
45 | 37 | 'action': 'query', |
46 | 38 | 'list': 'allpages', |
47 | | - 'apnamespace': 14, |
48 | | - 'apprefix': $j( this ).val(), |
| 39 | + 'apnamespace': categoryNamespaceId, |
| 40 | + 'apprefix': catName, |
49 | 41 | 'format': 'json' |
50 | 42 | }, |
51 | 43 | dataType: 'json', |
— | — | @@ -53,94 +45,149 @@ |
54 | 46 | var pages = data.query.allpages; |
55 | 47 | var titleArr = []; |
56 | 48 | |
57 | | - $j.each( pages, function( i, page ) { |
| 49 | + $.each( pages, function( i, page ) { |
58 | 50 | var title = page.title.split( ':', 2 )[1]; |
59 | 51 | titleArr.push( title ); |
60 | 52 | } ); |
61 | 53 | |
62 | | - $j( that ).suggestions( 'suggestions', titleArr ); |
| 54 | + $( _this ).suggestions( 'suggestions', titleArr ); |
63 | 55 | } |
64 | 56 | } ); |
| 57 | + //TODO |
| 58 | + _request = request; |
| 59 | + } |
65 | 60 | |
66 | | - ajaxCategories.request = request; |
67 | | - }, |
| 61 | + function _stripIllegals( cat ) { |
| 62 | + return cat.replace( /[\x00-\x1f\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f]+/g, '' ); |
| 63 | + } |
| 64 | + |
| 65 | + function _insertCatDOM( cat, isHidden ) { |
| 66 | + // User can implicitely state a sort key. |
| 67 | + // Remove before display |
| 68 | + cat = cat.replace(/\|.*/, ''); |
68 | 69 | |
69 | | - reloadCategoryList : function( response ) { |
70 | | - var holder = $j( '<div/>' ); |
| 70 | + // strip out bad characters |
| 71 | + cat = _stripIllegals ( cat ); |
71 | 72 | |
72 | | - holder.load( |
73 | | - window.location.href + ' .catlinks', |
74 | | - function() { |
75 | | - $j( '.catlinks' ).replaceWith( holder.find( '.catlinks' ) ); |
76 | | - ajaxCategories.setupAJAXCategories(); |
77 | | - ajaxCategories.removeProgressIndicator( $j( '.catlinks' ) ); |
| 73 | + if ( $.isEmpty( cat ) || _containsCat( cat ) ) { |
| 74 | + return; |
| 75 | + } |
| 76 | + |
| 77 | + var $catLinkWrapper = $( catLinkWrapper ); |
| 78 | + var $anchor = $( '<a/>' ).append( cat ); |
| 79 | + $catLinkWrapper.append( $anchor ); |
| 80 | + $anchor.attr( { target: "_blank", href: _catLink( cat ) } ); |
| 81 | + if ( isHidden ) { |
| 82 | + $container.find( '#mw-hidden-catlinks ul' ).append( $catLinkWrapper ); |
| 83 | + } else { |
| 84 | + $container.find( '#mw-normal-catlinks ul' ).append( $catLinkWrapper ); |
| 85 | + } |
| 86 | + _createCatButtons( $anchor.get(0) ); |
| 87 | + } |
| 88 | + |
| 89 | + function _makeSuggestionBox( prefill, callback, buttonVal ) { |
| 90 | + // Create add category prompt |
| 91 | + var promptContainer = $( '<div class="mw-addcategory-prompt"/>' ); |
| 92 | + var promptTextbox = $( '<input type="text" size="45" class="mw-addcategory-input"/>' ); |
| 93 | + if ( prefill !== '' ) { |
| 94 | + promptTextbox.val( prefill ); |
| 95 | + } |
| 96 | + var addButton = $( '<input type="button" class="mw-addcategory-button"/>' ); |
| 97 | + addButton.val( buttonVal ); |
| 98 | + |
| 99 | + addButton.click( callback ); |
| 100 | + |
| 101 | + promptTextbox.suggestions( { |
| 102 | + 'fetch':_fetchSuggestions, |
| 103 | + 'cancel': function() { |
| 104 | + var req = _request; |
| 105 | + // XMLHttpRequest.abort is unimplemented in IE6, also returns nonstandard value of "unknown" for typeof |
| 106 | + if ( req && ( typeof req.abort !== 'unknown' ) && ( typeof req.abort !== 'undefined' ) && req.abort ) { |
| 107 | + req.abort(); |
| 108 | + } |
78 | 109 | } |
79 | | - ); |
80 | | - }, |
| 110 | + } ); |
81 | 111 | |
82 | | - confirmEdit : function( page, fn, actionSummary, doneFn ) { |
83 | | - // Load jQuery UI |
84 | | - mvJsLoader.doLoad( |
85 | | - ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'], |
86 | | - function() { |
87 | | - // Produce a confirmation dialog |
| 112 | + promptTextbox.suggestions(); |
88 | 113 | |
89 | | - var dialog = $j( '<div/>' ); |
| 114 | + promptContainer.append( promptTextbox ); |
| 115 | + promptContainer.append( addButton ); |
90 | 116 | |
91 | | - dialog.addClass( 'mw-ajax-confirm-dialog' ); |
92 | | - dialog.attr( 'title', gM( 'ajax-confirm-title' ) ); |
| 117 | + return promptContainer; |
| 118 | + } |
| 119 | + |
| 120 | + // Create a valid link to the category. |
| 121 | + function _catLink ( cat ) { |
| 122 | + //SYNCED |
| 123 | + return mw.util.wikiGetlink( categoryNamespace + ':' + $.ucFirst( cat ) ); |
| 124 | + } |
| 125 | + |
| 126 | + function _getCats() { |
| 127 | + return $container.find( categoryLinkSelector ).map( function() { return $.trim( $( this ).text() ); } ); |
| 128 | + } |
93 | 129 | |
94 | | - // Intro text. |
95 | | - var confirmIntro = $j( '<p/>' ); |
96 | | - confirmIntro.text( gM( 'ajax-confirm-prompt' ) ); |
97 | | - dialog.append( confirmIntro ); |
| 130 | + function _containsCat( cat ) { |
| 131 | + //TODO: SYNC |
| 132 | + return _getCats().filter( function() { return $.ucFirst(this) == $.ucFirst(cat); } ).length !== 0; |
| 133 | + } |
| 134 | + |
| 135 | + function _confirmEdit ( page, fn, actionSummary, doneFn ) { |
98 | 136 | |
99 | | - // Summary of the action to be taken |
100 | | - var summaryHolder = $j( '<p/>' ); |
101 | | - var summaryLabel = $j( '<strong/>' ); |
102 | | - summaryLabel.text( gM( 'ajax-confirm-actionsummary' ) + " " ); |
103 | | - summaryHolder.text( actionSummary ); |
104 | | - summaryHolder.prepend( summaryLabel ); |
105 | | - dialog.append( summaryHolder ); |
| 137 | + // Produce a confirmation dialog |
| 138 | + var dialog = $( '<div/>' ); |
106 | 139 | |
107 | | - // Reason textbox. |
108 | | - var reasonBox = $j( '<input type="text" size="45" />' ); |
109 | | - reasonBox.addClass( 'mw-ajax-confirm-reason' ); |
110 | | - dialog.append( reasonBox ); |
| 140 | + dialog.addClass( 'mw-ajax-confirm-dialog' ); |
| 141 | + dialog.attr( 'title', mw.msg( 'ajax-confirm-title' ) ); |
111 | 142 | |
112 | | - // Submit button |
113 | | - var submitButton = $j( '<input type="button"/>' ); |
114 | | - submitButton.val( gM( 'ajax-confirm-save' ) ); |
| 143 | + // Intro text. |
| 144 | + var confirmIntro = $( '<p/>' ); |
| 145 | + confirmIntro.text( mw.msg( 'ajax-confirm-prompt' ) ); |
| 146 | + dialog.append( confirmIntro ); |
115 | 147 | |
116 | | - var submitFunction = function() { |
117 | | - ajaxCategories.addProgressIndicator( dialog ); |
118 | | - ajaxCategories.doEdit( |
119 | | - page, |
120 | | - fn, |
121 | | - reasonBox.val(), |
122 | | - function() { |
123 | | - doneFn(); |
124 | | - dialog.dialog( 'close' ); |
125 | | - ajaxCategories.removeProgressIndicator( dialog ); |
126 | | - } |
127 | | - ); |
128 | | - }; |
| 148 | + // Summary of the action to be taken |
| 149 | + var summaryHolder = $( '<p/>' ); |
| 150 | + var summaryLabel = $( '<strong/>' ); |
| 151 | + summaryLabel.text( mw.msg( 'ajax-confirm-actionsummary' ) + " " ); |
| 152 | + summaryHolder.text( actionSummary ); |
| 153 | + summaryHolder.prepend( summaryLabel ); |
| 154 | + dialog.append( summaryHolder ); |
129 | 155 | |
130 | | - var buttons = { }; |
131 | | - buttons[gM( 'ajax-confirm-save' )] = submitFunction; |
132 | | - var dialogOptions = { |
133 | | - 'AutoOpen' : true, |
134 | | - 'buttons' : buttons, |
135 | | - 'width' : 450 |
136 | | - }; |
| 156 | + // Reason textbox. |
| 157 | + var reasonBox = $( '<input type="text" size="45" />' ); |
| 158 | + reasonBox.addClass( 'mw-ajax-confirm-reason' ); |
| 159 | + dialog.append( reasonBox ); |
137 | 160 | |
138 | | - $j( '#catlinks' ).prepend( dialog ); |
139 | | - dialog.dialog( dialogOptions ); |
140 | | - } |
141 | | - ); |
142 | | - }, |
| 161 | + // Submit button |
| 162 | + var submitButton = $( '<input type="button"/>' ); |
| 163 | + submitButton.val( mw.msg( 'ajax-confirm-save' ) ); |
143 | 164 | |
144 | | - doEdit : function( page, fn, summary, doneFn ) { |
| 165 | + var submitFunction = function() { |
| 166 | + _addProgressIndicator( dialog ); |
| 167 | + _doEdit( |
| 168 | + page, |
| 169 | + fn, |
| 170 | + reasonBox.val(), |
| 171 | + function() { |
| 172 | + doneFn(); |
| 173 | + dialog.dialog( 'close' ); |
| 174 | + _removeProgressIndicator( dialog ); |
| 175 | + } |
| 176 | + ); |
| 177 | + }; |
| 178 | + |
| 179 | + var buttons = { }; |
| 180 | + buttons[mw.msg( 'ajax-confirm-save' )] = submitFunction; |
| 181 | + var dialogOptions = { |
| 182 | + 'AutoOpen' : true, |
| 183 | + 'buttons' : buttons, |
| 184 | + 'width' : 450 |
| 185 | + }; |
| 186 | + |
| 187 | + $( '#catlinks' ).prepend( dialog ); |
| 188 | + dialog.dialog( dialogOptions ); |
| 189 | + } |
| 190 | + |
| 191 | + function _doEdit ( page, fn, summary, doneFn ) { |
145 | 192 | // Get an edit token for the page. |
146 | 193 | var getTokenVars = { |
147 | 194 | 'action':'query', |
— | — | @@ -151,10 +198,10 @@ |
152 | 199 | 'format':'json' |
153 | 200 | }; |
154 | 201 | |
155 | | - $j.get( wgScriptPath + '/api.php', getTokenVars, |
| 202 | + $.get( wgScriptPath + '/api.php', getTokenVars, |
156 | 203 | function( reply ) { |
157 | 204 | var infos = reply.query.pages; |
158 | | - $j.each( |
| 205 | + $.each( |
159 | 206 | infos, |
160 | 207 | function( pageid, data ) { |
161 | 208 | var token = data.edittoken; |
— | — | @@ -175,152 +222,294 @@ |
176 | 223 | 'format':'json' |
177 | 224 | }; |
178 | 225 | |
179 | | - $j.post( wgScriptPath + '/api.php', postEditVars, doneFn, 'json' ); |
| 226 | + $.post( wgScriptPath + '/api.php', postEditVars, doneFn, 'json' ); |
180 | 227 | } |
181 | 228 | ); |
182 | 229 | } |
183 | 230 | , 'json' ); |
184 | | - }, |
| 231 | + } |
185 | 232 | |
186 | | - addProgressIndicator : function( elem ) { |
187 | | - var indicator = $j( '<div/>' ); |
| 233 | + function _addProgressIndicator ( elem ) { |
| 234 | + var indicator = $( '<div/>' ); |
188 | 235 | |
189 | 236 | indicator.addClass( 'mw-ajax-loader' ); |
190 | 237 | |
191 | 238 | elem.append( indicator ); |
192 | | - }, |
| 239 | + } |
193 | 240 | |
194 | | - removeProgressIndicator : function( elem ) { |
| 241 | + function _removeProgressIndicator ( elem ) { |
195 | 242 | elem.find( '.mw-ajax-loader' ).remove(); |
196 | | - }, |
197 | | - |
198 | | - handleCategoryAdd : function( e ) { |
199 | | - // Grab category text |
200 | | - var category = $j( '#mw-addcategory-input' ).val(); |
201 | | - var appendText = "\n[[" + wgFormattedNamespaces[14] + ":" + category + "]]\n"; |
202 | | - var summary = gM( 'ajax-add-category-summary', category ); |
203 | | - |
204 | | - ajaxCategories.confirmEdit( |
205 | | - wgPageName, |
206 | | - function( oldText ) { return oldText + appendText }, |
207 | | - summary, |
208 | | - ajaxCategories.reloadCategoryList |
209 | | - ); |
210 | | - }, |
211 | | - |
212 | | - handleDeleteLink : function( e ) { |
213 | | - e.preventDefault(); |
214 | | - |
215 | | - var category = $j( this ).parent().find( 'a' ).text(); |
216 | | - |
| 243 | + } |
| 244 | + |
| 245 | + function _makeCaseInsensitiv( string ) { |
| 246 | + var newString = ''; |
| 247 | + for (var i=0; i < string.length; i++) { |
| 248 | + newString += '[' + string[i].toUpperCase() + string[i].toLowerCase() + ']'; |
| 249 | + }; |
| 250 | + return newString; |
| 251 | + } |
| 252 | + function _buildRegex ( category ) { |
217 | 253 | // Build a regex that matches legal invocations of that category. |
218 | | - |
219 | | - // In theory I should escape the aliases, but there's no JS function for it |
220 | | - // Shouldn't have any real impact, can't be exploited or anything, so we'll |
221 | | - // leave it for now. |
222 | 254 | var categoryNSFragment = ''; |
223 | | - $j.each( wgNamespaceIds, function( name, id ) { |
| 255 | + $.each( namespaceIds, function( name, id ) { |
224 | 256 | if ( id == 14 ) { |
225 | | - // Allow the first character to be any case |
226 | | - var firstChar = name.charAt( 0 ); |
227 | | - firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']'; |
228 | | - categoryNSFragment += '|' + firstChar + name.substr( 1 ); |
| 257 | + // The parser accepts stuff like cATegORy, |
| 258 | + // we need to do the same |
| 259 | + categoryNSFragment += '|' + _makeCaseInsensitiv ( $.escapeRE(name) ); |
229 | 260 | } |
230 | 261 | } ); |
231 | 262 | categoryNSFragment = categoryNSFragment.substr( 1 ); // Remove leading | |
232 | | - |
| 263 | + |
233 | 264 | // Build the regex |
234 | | - var titleFragment = category; |
| 265 | + var titleFragment = $.escapeRE(category); |
235 | 266 | |
236 | 267 | firstChar = category.charAt( 0 ); |
237 | 268 | firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']'; |
238 | 269 | titleFragment = firstChar + category.substr( 1 ); |
239 | | - var categoryRegex = '\\[\\[' + categoryNSFragment + ':' + titleFragment + '(\\|[^\\]]*)?\\]\\]'; |
240 | | - categoryRegex = new RegExp( categoryRegex, 'g' ); |
| 270 | + var categoryRegex = '\\[\\[(' + categoryNSFragment + '):' + titleFragment + '(\\|[^\\]]*)?\\]\\]'; |
241 | 271 | |
242 | | - var summary = gM( 'ajax-remove-category-summary', category ); |
| 272 | + return new RegExp( categoryRegex, 'g' ); |
| 273 | + } |
| 274 | + |
| 275 | + function _handleEditLink ( e ) { |
| 276 | + e.preventDefault(); |
| 277 | + var $this = $( this ); |
| 278 | + var $link = $this.parent().find( 'a:not(.icon)' ); |
| 279 | + var category = $link.text(); |
| 280 | + |
| 281 | + var $input = _makeSuggestionBox( category, _handleCategoryEdit, mw.msg( 'ajax-confirm-save' ) ); |
| 282 | + $link.after( $input ).hide(); |
| 283 | + _catElements[category].editButton.hide(); |
| 284 | + _catElements[category].deleteButton.unbind('click').click( function() { |
| 285 | + $input.remove(); |
| 286 | + $link.show(); |
| 287 | + _catElements[category].editButton.show(); |
| 288 | + $( this ).unbind('click').click( _handleDeleteLink ); |
| 289 | + }); |
| 290 | + } |
| 291 | + |
| 292 | + function _handleAddLink ( e ) { |
| 293 | + e.preventDefault(); |
243 | 294 | |
244 | | - ajaxCategories.confirmEdit( |
245 | | - wgPageName, |
| 295 | + $container.find( '#mw-normal-catlinks>.mw-addcategory-prompt' ).toggle(); |
| 296 | + } |
| 297 | + |
| 298 | + function _handleDeleteLink ( e ) { |
| 299 | + e.preventDefault(); |
| 300 | + |
| 301 | + var $this = $( this ); |
| 302 | + var $link = $this.parent().find( 'a:not(.icon)' ); |
| 303 | + var category = $link.text(); |
| 304 | + |
| 305 | + categoryRegex = _buildRegex( category ); |
| 306 | + |
| 307 | + var summary = mw.msg( 'ajax-remove-category-summary', category ); |
| 308 | + |
| 309 | + _confirmEdit( |
| 310 | + mw.config.get('wgPageName'), |
246 | 311 | function( oldText ) { |
| 312 | + //TODO Cleanup whitespace safely? |
247 | 313 | var newText = oldText.replace( categoryRegex, '' ); |
248 | 314 | |
249 | 315 | if ( newText == oldText ) { |
250 | | - var error = gM( 'ajax-remove-category-error' ); |
251 | | - ajaxCategories.showError( error ); |
252 | | - ajaxCategories.removeProgressIndicator( $j( '.mw-ajax-confirm-dialog' ) ); |
253 | | - $j( '.mw-ajax-confirm-dialog' ).dialog( 'close' ); |
| 316 | + var error = mw.msg( 'ajax-remove-category-error' ); |
| 317 | + _showError( error ); |
| 318 | + _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) ); |
| 319 | + $( '.mw-ajax-confirm-dialog' ).dialog( 'close' ); |
254 | 320 | return false; |
255 | 321 | } |
256 | 322 | |
257 | 323 | return newText; |
258 | 324 | }, |
259 | | - summary, ajaxCategories.reloadCategoryList |
| 325 | + summary, |
| 326 | + function() { |
| 327 | + $this.parent().remove(); |
| 328 | + } |
260 | 329 | ); |
261 | | - }, |
| 330 | + } |
262 | 331 | |
263 | | - showError : function( str ) { |
264 | | - var dialog = $j( '<div/>' ); |
| 332 | + function _handleCategoryAdd ( e ) { |
| 333 | + // Grab category text |
| 334 | + var category = $( this ).parent().find( '.mw-addcategory-input' ).val(); |
| 335 | + category = $.ucFirst( category ); |
| 336 | + |
| 337 | + if ( _containsCat(category) ) { |
| 338 | + // TODO add info alert |
| 339 | + return; |
| 340 | + } |
| 341 | + var appendText = "\n[[" + categoryNamespace + ":" + category + "]]\n"; |
| 342 | + var summary = mw.msg( 'ajax-add-category-summary', category ); |
| 343 | + |
| 344 | + _confirmEdit( |
| 345 | + mw.config.get( 'wgPageName' ), |
| 346 | + function( oldText ) { return oldText + appendText }, |
| 347 | + summary, |
| 348 | + function() { |
| 349 | + _insertCatDOM( category, false ); |
| 350 | + } |
| 351 | + ); |
| 352 | + } |
| 353 | + |
| 354 | + function _handleCategoryEdit ( e ) { |
| 355 | + e.preventDefault(); |
| 356 | + |
| 357 | + // Grab category text |
| 358 | + var categoryNew = $( this ).parent().find( '.mw-addcategory-input' ).val(); |
| 359 | + categoryNew = $.ucFirst( categoryNew ); |
| 360 | + |
| 361 | + var $this = $( this ); |
| 362 | + var $link = $this.parent().parent().find( 'a:not(.icon)' ); |
| 363 | + var category = $link.text(); |
| 364 | + |
| 365 | + // User didn't change anything. Just close the box |
| 366 | + if ( category == categoryNew ) { |
| 367 | + $this.parent().remove(); |
| 368 | + $link.show(); |
| 369 | + return; |
| 370 | + } |
| 371 | + categoryRegex = _buildRegex( category ); |
| 372 | + |
| 373 | + var summary = mw.msg( 'ajax-edit-category-summary', category, categoryNew ); |
| 374 | + |
| 375 | + _confirmEdit( |
| 376 | + mw.config.get( 'wgPageName' ), |
| 377 | + function( oldText ) { |
| 378 | + var matches = oldText.match( categoryRegex ); |
| 379 | + |
| 380 | + //Old cat wasn't found, likely to be transcluded |
| 381 | + if ( !$.isArray( matches ) ) { |
| 382 | + var error = mw.msg( 'ajax-edit-category-error' ); |
| 383 | + _showError( error ); |
| 384 | + _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) ); |
| 385 | + $( '.mw-ajax-confirm-dialog' ).dialog( 'close' ); |
| 386 | + return false; |
| 387 | + } |
| 388 | + var sortkey = matches[0].replace( categoryRegex, '$2' ); |
| 389 | + var newCategoryString = "[[" + categoryNamespace + ":" + categoryNew + sortkey + ']]'; |
| 390 | + |
| 391 | + if (matches.length > 1) { |
| 392 | + // The category is duplicated. |
| 393 | + // Remove all but one match |
| 394 | + for (var i = 1; i < matches.length; i++) { |
| 395 | + oldText = oldText.replace( matches[i], ''); |
| 396 | + } |
| 397 | + } |
| 398 | + var newText = oldText.replace( categoryRegex, newCategoryString ); |
| 399 | + |
| 400 | + return newText; |
| 401 | + }, |
| 402 | + summary, |
| 403 | + function() { |
| 404 | + // Remove input box & button |
| 405 | + $this.parent().remove(); |
| 406 | + |
| 407 | + // Update link text and href |
| 408 | + $link.show().text( categoryNew ).attr( 'href', _catLink( categoryNew ) ); |
| 409 | + } |
| 410 | + ); |
| 411 | + } |
| 412 | + function _showError ( str ) { |
| 413 | + var dialog = $( '<div/>' ); |
265 | 414 | dialog.text( str ); |
266 | 415 | |
267 | | - $j( '#bodyContent' ).append( dialog ); |
| 416 | + $( '#bodyContent' ).append( dialog ); |
268 | 417 | |
269 | 418 | var buttons = { }; |
270 | | - buttons[gM( 'ajax-error-dismiss' )] = function( e ) { |
| 419 | + buttons[mw.msg( 'ajax-error-dismiss' )] = function( e ) { |
271 | 420 | dialog.dialog( 'close' ); |
272 | 421 | }; |
273 | 422 | var dialogOptions = { |
274 | 423 | 'buttons' : buttons, |
275 | 424 | 'AutoOpen' : true, |
276 | | - 'title' : gM( 'ajax-error-title' ) |
| 425 | + 'title' : mw.msg( 'ajax-error-title' ) |
277 | 426 | }; |
278 | 427 | |
279 | 428 | dialog.dialog( dialogOptions ); |
280 | | - }, |
| 429 | + } |
281 | 430 | |
282 | | - setupAJAXCategories : function() { |
| 431 | + function _createButton ( icon, title, category, text ){ |
| 432 | + var button = $( '<a>' ).addClass( category || '' ) |
| 433 | + .attr('title', title); |
| 434 | + |
| 435 | + if ( text ) { |
| 436 | + var icon = $( '<a>' ).addClass( 'icon ' + icon ); |
| 437 | + button.addClass( 'icon-parent' ).append( icon ).append( text ); |
| 438 | + } else { |
| 439 | + button.addClass( 'icon ' + icon ); |
| 440 | + } |
| 441 | + return button; |
| 442 | + } |
| 443 | + function _createCatButtons ( element ) { |
| 444 | + // Create remove & edit buttons |
| 445 | + var deleteButton = _createButton('icon-close', mw.msg( 'ajax-remove-category' ) ); |
| 446 | + var editButton = _createButton('icon-edit', mw.msg( 'ajax-edit-category' ) ); |
| 447 | + |
| 448 | + //Not yet used |
| 449 | + var saveButton = _createButton('icon-tick', mw.msg( 'ajax-confirm-save' ) ).hide(); |
| 450 | + |
| 451 | + deleteButton.click( _handleDeleteLink ); |
| 452 | + editButton.click( _handleEditLink ); |
| 453 | + |
| 454 | + $( element ).after( deleteButton ).after( editButton ); |
| 455 | + |
| 456 | + //Save references to all links and buttons |
| 457 | + _catElements[$( element ).text()] = { |
| 458 | + link : $( element ), |
| 459 | + parent : $( element ).parent(), |
| 460 | + saveButton : saveButton, |
| 461 | + deleteButton: deleteButton, |
| 462 | + editButton : editButton |
| 463 | + }; |
| 464 | + } |
| 465 | + function _setup() { |
| 466 | + // Could be set by gadgets like HotCat etc. |
| 467 | + if ( mw.config.get('disableAJAXCategories') ) { |
| 468 | + return; |
| 469 | + } |
283 | 470 | // Only do it for articles. |
284 | | - if ( !wgIsArticle ) return; |
| 471 | + if ( !mw.config.get( 'wgIsArticle' ) ) return; |
285 | 472 | |
286 | | - var clElement = $j( '.catlinks' ); |
| 473 | + var clElement = $( '#mw-normal-catlinks' ); |
287 | 474 | |
288 | 475 | // Unhide hidden category holders. |
289 | | - clElement.removeClass( 'catlinks-allhidden' ); |
| 476 | + $('#mw-hidden-catlinks').show(); |
290 | 477 | |
291 | | - var addLink = $j( '<a/>' ); |
292 | | - addLink.addClass( 'mw-ajax-addcategory' ); |
293 | 478 | |
294 | 479 | // Create [Add Category] link |
295 | | - addLink.text( gM( 'ajax-add-category' ) ); |
296 | | - addLink.attr( 'href', '#' ); |
297 | | - addLink.click( ajaxCategories.handleAddLink ); |
| 480 | + var addLink = _createButton('icon-add', |
| 481 | + mw.msg( 'ajax-add-category' ), |
| 482 | + 'mw-ajax-addcategory', |
| 483 | + mw.msg( 'ajax-add-category' ) |
| 484 | + ); |
| 485 | + addLink.click( _handleAddLink ); |
298 | 486 | clElement.append( addLink ); |
299 | 487 | |
300 | 488 | // Create add category prompt |
301 | | - var promptContainer = $j( '<div id="mw-addcategory-prompt"/>' ); |
302 | | - var promptTextbox = $j( '<input type="text" size="45" id="mw-addcategory-input"/>' ); |
303 | | - var addButton = $j( '<input type="button" id="mw-addcategory-button"/>' ); |
304 | | - addButton.val( gM( 'ajax-add-category-submit' ) ); |
305 | | - |
306 | | - promptTextbox.keypress( ajaxCategories.handleCategoryInput ); |
307 | | - addButton.click( ajaxCategories.handleCategoryAdd ); |
308 | | - |
309 | | - promptContainer.append( promptTextbox ); |
310 | | - promptContainer.append( addButton ); |
| 489 | + var promptContainer = _makeSuggestionBox( '', _handleCategoryAdd, mw.msg( 'ajax-add-category-submit' ) ); |
311 | 490 | promptContainer.hide(); |
312 | 491 | |
313 | | - // Create delete link for each category. |
314 | | - $j( '.catlinks div span a' ).each( function( e ) { |
315 | | - // Create a remove link |
316 | | - var deleteLink = $j( '<a class="mw-remove-category" href="#"/>' ); |
| 492 | + // Create edit & delete link for each category. |
| 493 | + $( '#catlinks li a' ).each( function( e ) { |
| 494 | + _createCatButtons( this ); |
| 495 | + }); |
317 | 496 | |
318 | | - deleteLink.click( ajaxCategories.handleDeleteLink ); |
319 | | - |
320 | | - $j( this ).after( deleteLink ); |
321 | | - } ); |
322 | | - |
323 | 497 | clElement.append( promptContainer ); |
324 | 498 | } |
325 | | -}; |
| 499 | + function _teardown() { |
| 500 | + |
| 501 | + } |
| 502 | + _tasks = { |
| 503 | + list : [], |
| 504 | + executed : [], |
| 505 | + add : function( obj ) { |
| 506 | + this.list.push( obj ); |
| 507 | + }, |
| 508 | + next : function() { |
| 509 | + var task = this.list.shift(); |
| 510 | + //run task |
| 511 | + this.executed.push( task ); |
| 512 | + } |
| 513 | + } |
| 514 | + $(document).ready( function() {_setup()}); |
326 | 515 | |
327 | | -js2AddOnloadHook( ajaxCategories.setupAJAXCategories ); |
| 516 | +} )( jQuery, mediaWiki ); |
\ No newline at end of file |
Index: trunk/phase3/resources/mediawiki.page/images/AJAXCategorySprite.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/phase3/resources/mediawiki.page/images/AJAXCategorySprite.png |
___________________________________________________________________ |
Added: svn:mime-type |
328 | 517 | + image/png |
Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.css |
— | — | @@ -0,0 +1,62 @@ |
| 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 | + |
| 16 | +#catlinks:hover .icon { |
| 17 | + opacity: 1; |
| 18 | +} |
| 19 | + |
| 20 | +.mw-ajax-addcategory { |
| 21 | + padding-left: 30px; |
| 22 | + margin-right: 1em; |
| 23 | + cursor: pointer; |
| 24 | +} |
| 25 | +#catlinks .icon { |
| 26 | + cursor: pointer; |
| 27 | + padding: 1px 8px; |
| 28 | + margin: 0; |
| 29 | + background: url('images/AJAXCategorySprite.png') 0 0 no-repeat; |
| 30 | + opacity: 0.5; |
| 31 | +} |
| 32 | +#catlinks .icon-parent { |
| 33 | + cursor: pointer; |
| 34 | +} |
| 35 | +#catlinks .icon-parent:hover .icon { |
| 36 | + background-position-y: -16px; |
| 37 | +} |
| 38 | +#catlinks .no-text { |
| 39 | +} |
| 40 | +#catlinks .icon-close { |
| 41 | + background-position: 0 0; |
| 42 | +} |
| 43 | +#catlinks .icon-edit { |
| 44 | + background-position: -16px 0; |
| 45 | +} |
| 46 | +#catlinks .icon-tick { |
| 47 | + background-position: -32px 0; |
| 48 | +} |
| 49 | +#catlinks .icon-add { |
| 50 | + background-position: -64px 0; |
| 51 | +} |
| 52 | +#catlinks .icon-close:hover { |
| 53 | + background-position: 0 -16px; |
| 54 | +} |
| 55 | +#catlinks .icon-edit:hover { |
| 56 | + background-position: -16px -16px; |
| 57 | +} |
| 58 | +#catlinks .icon-tick:hover { |
| 59 | + background-position: -32px -16px; |
| 60 | +} |
| 61 | +#catlinks .icon-add:hover { |
| 62 | + background-position: -64px -16px; |
| 63 | +} |
\ No newline at end of file |
Property changes on: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 64 | + native |