Commit RELEASE-NOTES line for the wgCategories js variable I added some time ago.
[lhc/web/wiklou.git] / js2 / ajaxcategories.js
1 loadGM( {
2 "ajax-add-category" : "[Add Category]",
3 "ajax-add-category-submit" : "[Add]",
4 "ajax-confirm-prompt" : "[Confirmation Text]",
5 "ajax-confirm-title" : "[Confirmation Title]",
6 "ajax-confirm-save" : "[Save]",
7 "ajax-add-category-summary" : "[Add category $1]",
8 "ajax-remove-category-summary" : "[Remove category $2]",
9 "ajax-confirm-actionsummary" : "[Summary]",
10 "ajax-error-title" : "Error",
11 "ajax-error-dismiss" : "OK",
12 "ajax-remove-category-error" : "[RemoveErr]"
13 } );
14
15 var ajaxCategories = {
16 handleAddLink : function( e ) {
17 e.preventDefault();
18
19 // Make sure the suggestion plugin is loaded. Load everything else while we're at it
20 mvJsLoader.doLoad(
21 ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'],
22 function() {
23 $j( '#mw-addcategory-prompt' ).toggle();
24
25 $j( '#mw-addcategory-input' ).suggestions( {
26 'fetch':ajaxCategories.fetchSuggestions,
27 'cancel': function() {
28 var req = ajaxCategories.request;
29 if ( req.abort )
30 req.abort();
31 }
32 } );
33
34 $j( '#mw-addcategory-input' ).suggestions();
35 }
36 );
37 },
38
39 fetchSuggestions : function( query ) {
40 var that = this;
41 var request = $j.ajax( {
42 url: wgScriptPath + '/api.php',
43 data: {
44 'action': 'query',
45 'list': 'allpages',
46 'apnamespace': 14,
47 'apprefix': $j( this ).val(),
48 'format': 'json'
49 },
50 dataType: 'json',
51 success: function( data ) {
52 // Process data.query.allpages into an array of titles
53 var pages = data.query.allpages;
54 var titleArr = [];
55
56 $j.each( pages, function( i, page ) {
57 var title = page.title.split( ':', 2 )[1];
58 titleArr.push( title );
59 } );
60
61 $j( that ).suggestions( 'suggestions', titleArr );
62 }
63 } );
64
65 ajaxCategories.request = request;
66 },
67
68 reloadCategoryList : function( response ) {
69 var holder = $j( '<div/>' );
70
71 holder.load(
72 window.location.href + ' .catlinks',
73 function() {
74 $j( '.catlinks' ).replaceWith( holder.find( '.catlinks' ) );
75 ajaxCategories.setupAJAXCategories();
76 ajaxCategories.removeProgressIndicator( $j( '.catlinks' ) );
77 }
78 );
79 },
80
81 confirmEdit : function( page, fn, actionSummary, doneFn ) {
82 // Load jQuery UI
83 mvJsLoader.doLoad(
84 ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'],
85 function() {
86 // Produce a confirmation dialog
87
88 var dialog = $j( '<div/>' );
89
90 dialog.addClass( 'mw-ajax-confirm-dialog' );
91 dialog.attr( 'title', gM( 'ajax-confirm-title' ) );
92
93 // Intro text.
94 var confirmIntro = $j( '<p/>' );
95 confirmIntro.text( gM( 'ajax-confirm-prompt' ) );
96 dialog.append( confirmIntro );
97
98 // Summary of the action to be taken
99 var summaryHolder = $j( '<p/>' );
100 var summaryLabel = $j( '<strong/>' );
101 summaryLabel.text( gM( 'ajax-confirm-actionsummary' ) + " " );
102 summaryHolder.text( actionSummary );
103 summaryHolder.prepend( summaryLabel );
104 dialog.append( summaryHolder );
105
106 // Reason textbox.
107 var reasonBox = $j( '<input type="text" size="45" />' );
108 reasonBox.addClass( 'mw-ajax-confirm-reason' );
109 dialog.append( reasonBox );
110
111 // Submit button
112 var submitButton = $j( '<input type="button"/>' );
113 submitButton.val( gM( 'ajax-confirm-save' ) );
114
115 var submitFunction = function() {
116 ajaxCategories.addProgressIndicator( dialog );
117 ajaxCategories.doEdit(
118 page,
119 fn,
120 reasonBox.val(),
121 function() {
122 doneFn();
123 dialog.dialog( 'close' );
124 ajaxCategories.removeProgressIndicator( dialog );
125 }
126 );
127 };
128
129 var buttons = { };
130 buttons[gM( 'ajax-confirm-save' )] = submitFunction;
131 var dialogOptions = {
132 'AutoOpen' : true,
133 'buttons' : buttons,
134 'width' : 450
135 };
136
137 $j( '#catlinks' ).prepend( dialog );
138 dialog.dialog( dialogOptions );
139 }
140 );
141 },
142
143 doEdit : function( page, fn, summary, doneFn ) {
144 // Get an edit token for the page.
145 var getTokenVars = {
146 'action':'query',
147 'prop':'info|revisions',
148 'intoken':'edit',
149 'titles':page,
150 'rvprop':'content|timestamp',
151 'format':'json'
152 };
153
154 $j.get( wgScriptPath + '/api.php', getTokenVars,
155 function( reply ) {
156 var infos = reply.query.pages;
157 $j.each(
158 infos,
159 function( pageid, data ) {
160 var token = data.edittoken;
161 var timestamp = data.revisions[0].timestamp;
162 var oldText = data.revisions[0]['*'];
163
164 var newText = fn( oldText );
165
166 if ( newText === false ) return;
167
168 var postEditVars = {
169 'action':'edit',
170 'title':page,
171 'text':newText,
172 'summary':summary,
173 'token':token,
174 'basetimestamp':timestamp,
175 'format':'json'
176 };
177
178 $j.post( wgScriptPath + '/api.php', postEditVars, doneFn, 'json' );
179 }
180 );
181 }
182 , 'json' );
183 },
184
185 addProgressIndicator : function( elem ) {
186 var indicator = $j( '<div/>' );
187
188 indicator.addClass( 'mw-ajax-loader' );
189
190 elem.append( indicator );
191 },
192
193 removeProgressIndicator : function( elem ) {
194 elem.find( '.mw-ajax-loader' ).remove();
195 },
196
197 handleCategoryAdd : function( e ) {
198 // Grab category text
199 var category = $j( '#mw-addcategory-input' ).val();
200 var appendText = "\n[[" + wgFormattedNamespaces[14] + ":" + category + "]]\n";
201 var summary = gM( 'ajax-add-category-summary', category );
202
203 ajaxCategories.confirmEdit(
204 wgPageName,
205 function( oldText ) { return oldText + appendText },
206 summary,
207 ajaxCategories.reloadCategoryList
208 );
209 },
210
211 handleDeleteLink : function( e ) {
212 e.preventDefault();
213
214 var category = $j( this ).parent().find( 'a' ).text();
215
216 // Build a regex that matches legal invocations of that category.
217
218 // In theory I should escape the aliases, but there's no JS function for it
219 // Shouldn't have any real impact, can't be exploited or anything, so we'll
220 // leave it for now.
221 var categoryNSFragment = '';
222 $j.each( wgNamespaceIds, function( name, id ) {
223 if ( id == 14 ) {
224 // Allow the first character to be any case
225 var firstChar = name.charAt( 0 );
226 firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']';
227 categoryNSFragment += '|' + firstChar + name.substr( 1 );
228 }
229 } );
230 categoryNSFragment = categoryNSFragment.substr( 1 ); // Remove leading |
231
232 // Build the regex
233 var titleFragment = category;
234
235 firstChar = category.charAt( 0 );
236 firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']';
237 titleFragment = firstChar + category.substr( 1 );
238 var categoryRegex = '\\[\\[' + categoryNSFragment + ':' + titleFragment + '(\\|[^\\]]*)?\\]\\]';
239 categoryRegex = new RegExp( categoryRegex, 'g' );
240
241 var summary = gM( 'ajax-remove-category-summary', category );
242
243 ajaxCategories.confirmEdit(
244 wgPageName,
245 function( oldText ) {
246 var newText = oldText.replace( categoryRegex, '' );
247
248 if ( newText == oldText ) {
249 var error = gM( 'ajax-remove-category-error' );
250 ajaxCategories.showError( error );
251 ajaxCategories.removeProgressIndicator( $j( '.mw-ajax-confirm-dialog' ) );
252 $j( '.mw-ajax-confirm-dialog' ).dialog( 'close' );
253 return false;
254 }
255
256 return newText;
257 },
258 summary, ajaxCategories.reloadCategoryList
259 );
260 },
261
262 showError : function( str ) {
263 var dialog = $j( '<div/>' );
264 dialog.text( str );
265
266 $j( '#bodyContent' ).append( dialog );
267
268 var buttons = { };
269 buttons[gM( 'ajax-error-dismiss' )] = function( e ) {
270 dialog.dialog( 'close' );
271 };
272 var dialogOptions = {
273 'buttons' : buttons,
274 'AutoOpen' : true,
275 'title' : gM( 'ajax-error-title' )
276 };
277
278 dialog.dialog( dialogOptions );
279 },
280
281 setupAJAXCategories : function() {
282 // Only do it for articles.
283 if ( !wgIsArticle ) return;
284
285 var clElement = $j( '.catlinks' );
286
287 // Unhide hidden category holders.
288 clElement.removeClass( 'catlinks-allhidden' );
289
290 var addLink = $j( '<a/>' );
291 addLink.addClass( 'mw-ajax-addcategory' );
292
293 // Create [Add Category] link
294 addLink.text( gM( 'ajax-add-category' ) );
295 addLink.attr( 'href', '#' );
296 addLink.click( ajaxCategories.handleAddLink );
297 clElement.append( addLink );
298
299 // Create add category prompt
300 var promptContainer = $j( '<div id="mw-addcategory-prompt"/>' );
301 var promptTextbox = $j( '<input type="text" size="45" id="mw-addcategory-input"/>' );
302 var addButton = $j( '<input type="button" id="mw-addcategory-button"/>' );
303 addButton.val( gM( 'ajax-add-category-submit' ) );
304
305 promptTextbox.keypress( ajaxCategories.handleCategoryInput );
306 addButton.click( ajaxCategories.handleCategoryAdd );
307
308 promptContainer.append( promptTextbox );
309 promptContainer.append( addButton );
310 promptContainer.hide();
311
312 // Create delete link for each category.
313 $j( '.catlinks div span a' ).each( function( e ) {
314 // Create a remove link
315 var deleteLink = $j( '<a class="mw-remove-category" href="#"/>' );
316
317 deleteLink.click( ajaxCategories.handleDeleteLink );
318
319 $j( this ).after( deleteLink );
320 } );
321
322 clElement.append( promptContainer );
323 }
324 };
325
326 js2AddOnloadHook( ajaxCategories.setupAJAXCategories );