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