Index: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js |
— | — | @@ -0,0 +1,326 @@ |
| 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 | +} ); |
| 15 | + |
| 16 | +var ajaxCategories = { |
| 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( |
| 22 | + ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'], |
| 23 | + function() { |
| 24 | + $j( '#mw-addcategory-prompt' ).toggle(); |
| 25 | + |
| 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( { |
| 43 | + url: wgScriptPath + '/api.php', |
| 44 | + data: { |
| 45 | + 'action': 'query', |
| 46 | + 'list': 'allpages', |
| 47 | + 'apnamespace': 14, |
| 48 | + 'apprefix': $j( this ).val(), |
| 49 | + 'format': 'json' |
| 50 | + }, |
| 51 | + dataType: 'json', |
| 52 | + success: function( data ) { |
| 53 | + // Process data.query.allpages into an array of titles |
| 54 | + var pages = data.query.allpages; |
| 55 | + var titleArr = []; |
| 56 | + |
| 57 | + $j.each( pages, function( i, page ) { |
| 58 | + var title = page.title.split( ':', 2 )[1]; |
| 59 | + titleArr.push( title ); |
| 60 | + } ); |
| 61 | + |
| 62 | + $j( that ).suggestions( 'suggestions', titleArr ); |
| 63 | + } |
| 64 | + } ); |
| 65 | + |
| 66 | + ajaxCategories.request = request; |
| 67 | + }, |
| 68 | + |
| 69 | + reloadCategoryList : function( response ) { |
| 70 | + var holder = $j( '<div/>' ); |
| 71 | + |
| 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' ) ); |
| 78 | + } |
| 79 | + ); |
| 80 | + }, |
| 81 | + |
| 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 |
| 88 | + |
| 89 | + var dialog = $j( '<div/>' ); |
| 90 | + |
| 91 | + dialog.addClass( 'mw-ajax-confirm-dialog' ); |
| 92 | + dialog.attr( 'title', gM( 'ajax-confirm-title' ) ); |
| 93 | + |
| 94 | + // Intro text. |
| 95 | + var confirmIntro = $j( '<p/>' ); |
| 96 | + confirmIntro.text( gM( 'ajax-confirm-prompt' ) ); |
| 97 | + dialog.append( confirmIntro ); |
| 98 | + |
| 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 ); |
| 106 | + |
| 107 | + // Reason textbox. |
| 108 | + var reasonBox = $j( '<input type="text" size="45" />' ); |
| 109 | + reasonBox.addClass( 'mw-ajax-confirm-reason' ); |
| 110 | + dialog.append( reasonBox ); |
| 111 | + |
| 112 | + // Submit button |
| 113 | + var submitButton = $j( '<input type="button"/>' ); |
| 114 | + submitButton.val( gM( 'ajax-confirm-save' ) ); |
| 115 | + |
| 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 | + }; |
| 129 | + |
| 130 | + var buttons = { }; |
| 131 | + buttons[gM( 'ajax-confirm-save' )] = submitFunction; |
| 132 | + var dialogOptions = { |
| 133 | + 'AutoOpen' : true, |
| 134 | + 'buttons' : buttons, |
| 135 | + 'width' : 450 |
| 136 | + }; |
| 137 | + |
| 138 | + $j( '#catlinks' ).prepend( dialog ); |
| 139 | + dialog.dialog( dialogOptions ); |
| 140 | + } |
| 141 | + ); |
| 142 | + }, |
| 143 | + |
| 144 | + doEdit : function( page, fn, summary, doneFn ) { |
| 145 | + // Get an edit token for the page. |
| 146 | + var getTokenVars = { |
| 147 | + 'action':'query', |
| 148 | + 'prop':'info|revisions', |
| 149 | + 'intoken':'edit', |
| 150 | + 'titles':page, |
| 151 | + 'rvprop':'content|timestamp', |
| 152 | + 'format':'json' |
| 153 | + }; |
| 154 | + |
| 155 | + $j.get( wgScriptPath + '/api.php', getTokenVars, |
| 156 | + function( reply ) { |
| 157 | + var infos = reply.query.pages; |
| 158 | + $j.each( |
| 159 | + infos, |
| 160 | + function( pageid, data ) { |
| 161 | + var token = data.edittoken; |
| 162 | + var timestamp = data.revisions[0].timestamp; |
| 163 | + var oldText = data.revisions[0]['*']; |
| 164 | + |
| 165 | + var newText = fn( oldText ); |
| 166 | + |
| 167 | + if ( newText === false ) return; |
| 168 | + |
| 169 | + var postEditVars = { |
| 170 | + 'action':'edit', |
| 171 | + 'title':page, |
| 172 | + 'text':newText, |
| 173 | + 'summary':summary, |
| 174 | + 'token':token, |
| 175 | + 'basetimestamp':timestamp, |
| 176 | + 'format':'json' |
| 177 | + }; |
| 178 | + |
| 179 | + $j.post( wgScriptPath + '/api.php', postEditVars, doneFn, 'json' ); |
| 180 | + } |
| 181 | + ); |
| 182 | + } |
| 183 | + , 'json' ); |
| 184 | + }, |
| 185 | + |
| 186 | + addProgressIndicator : function( elem ) { |
| 187 | + var indicator = $j( '<div/>' ); |
| 188 | + |
| 189 | + indicator.addClass( 'mw-ajax-loader' ); |
| 190 | + |
| 191 | + elem.append( indicator ); |
| 192 | + }, |
| 193 | + |
| 194 | + removeProgressIndicator : function( elem ) { |
| 195 | + 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 | + |
| 217 | + // 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 | + var categoryNSFragment = ''; |
| 223 | + $j.each( wgNamespaceIds, function( name, id ) { |
| 224 | + 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 ); |
| 229 | + } |
| 230 | + } ); |
| 231 | + categoryNSFragment = categoryNSFragment.substr( 1 ); // Remove leading | |
| 232 | + |
| 233 | + // Build the regex |
| 234 | + var titleFragment = category; |
| 235 | + |
| 236 | + firstChar = category.charAt( 0 ); |
| 237 | + firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']'; |
| 238 | + titleFragment = firstChar + category.substr( 1 ); |
| 239 | + var categoryRegex = '\\[\\[' + categoryNSFragment + ':' + titleFragment + '(\\|[^\\]]*)?\\]\\]'; |
| 240 | + categoryRegex = new RegExp( categoryRegex, 'g' ); |
| 241 | + |
| 242 | + var summary = gM( 'ajax-remove-category-summary', category ); |
| 243 | + |
| 244 | + ajaxCategories.confirmEdit( |
| 245 | + wgPageName, |
| 246 | + function( oldText ) { |
| 247 | + var newText = oldText.replace( categoryRegex, '' ); |
| 248 | + |
| 249 | + 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' ); |
| 254 | + return false; |
| 255 | + } |
| 256 | + |
| 257 | + return newText; |
| 258 | + }, |
| 259 | + summary, ajaxCategories.reloadCategoryList |
| 260 | + ); |
| 261 | + }, |
| 262 | + |
| 263 | + showError : function( str ) { |
| 264 | + var dialog = $j( '<div/>' ); |
| 265 | + dialog.text( str ); |
| 266 | + |
| 267 | + $j( '#bodyContent' ).append( dialog ); |
| 268 | + |
| 269 | + var buttons = { }; |
| 270 | + buttons[gM( 'ajax-error-dismiss' )] = function( e ) { |
| 271 | + dialog.dialog( 'close' ); |
| 272 | + }; |
| 273 | + var dialogOptions = { |
| 274 | + 'buttons' : buttons, |
| 275 | + 'AutoOpen' : true, |
| 276 | + 'title' : gM( 'ajax-error-title' ) |
| 277 | + }; |
| 278 | + |
| 279 | + dialog.dialog( dialogOptions ); |
| 280 | + }, |
| 281 | + |
| 282 | + setupAJAXCategories : function() { |
| 283 | + // Only do it for articles. |
| 284 | + if ( !wgIsArticle ) return; |
| 285 | + |
| 286 | + var clElement = $j( '.catlinks' ); |
| 287 | + |
| 288 | + // Unhide hidden category holders. |
| 289 | + clElement.removeClass( 'catlinks-allhidden' ); |
| 290 | + |
| 291 | + var addLink = $j( '<a/>' ); |
| 292 | + addLink.addClass( 'mw-ajax-addcategory' ); |
| 293 | + |
| 294 | + // Create [Add Category] link |
| 295 | + addLink.text( gM( 'ajax-add-category' ) ); |
| 296 | + addLink.attr( 'href', '#' ); |
| 297 | + addLink.click( ajaxCategories.handleAddLink ); |
| 298 | + clElement.append( addLink ); |
| 299 | + |
| 300 | + // 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 ); |
| 311 | + promptContainer.hide(); |
| 312 | + |
| 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="#"/>' ); |
| 317 | + |
| 318 | + deleteLink.click( ajaxCategories.handleDeleteLink ); |
| 319 | + |
| 320 | + $j( this ).after( deleteLink ); |
| 321 | + } ); |
| 322 | + |
| 323 | + clElement.append( promptContainer ); |
| 324 | + } |
| 325 | +}; |
| 326 | + |
| 327 | +js2AddOnloadHook( ajaxCategories.setupAJAXCategories ); |
Property changes on: trunk/phase3/resources/mediawiki.page/mediawiki.page.ajaxCategories.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 328 | Merged /branches/REL1_15/phase3/js2/ajaxcategories.js:r51646 |
2 | 329 | Merged /branches/sqlite/js2/ajaxcategories.js:r58211-58321 |
Added: svn:eol-style |
3 | 330 | + native |