-loadGM( {
- "ajax-add-category" : "[Add Category]",
- "ajax-add-category-submit" : "[Add]",
- "ajax-confirm-prompt" : "[Confirmation Text]",
- "ajax-confirm-title" : "[Confirmation Title]",
- "ajax-confirm-save" : "[Save]",
- "ajax-add-category-summary" : "[Add category $1]",
- "ajax-remove-category-summary" : "[Remove category $2]",
- "ajax-confirm-actionsummary" : "[Summary]",
- "ajax-error-title" : "Error",
- "ajax-error-dismiss" : "OK",
- "ajax-remove-category-error" : "[RemoveErr]"
-} );
-
-var ajaxCategories = {
- handleAddLink : function( e ) {
- e.preventDefault();
-
- // Make sure the suggestion plugin is loaded. Load everything else while we're at it
- mvJsLoader.doLoad(
- ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'],
- function() {
- $j( '#mw-addcategory-prompt' ).toggle();
-
- $j( '#mw-addcategory-input' ).suggestions( {
- 'fetch':ajaxCategories.fetchSuggestions,
- 'cancel': function() {
- var req = ajaxCategories.request;
- if ( req.abort )
- req.abort();
- }
- } );
-
- $j( '#mw-addcategory-input' ).suggestions();
- }
- );
- },
-
- fetchSuggestions : function( query ) {
- var that = this;
- var request = $j.ajax( {
+// TODO
+//
+// * The edit summary should contain the added/removed category name too.
+// Something like: "Category:Foo added. Reason"
+// Requirement: Be able to get msg with lang option.
+// * Handle uneditable cats. Needs serverside changes!
+// * Add Hooks for change, delete, add
+// * Add Hooks for soft redirect
+// * Handle normal redirects
+// * api.php / api.php5
+// * Simple / MultiEditMode
+
+( function( $, mw ) {
+ var catLinkWrapper = '<li/>'
+ var $container = $( '.catlinks' );
+
+ var categoryLinkSelector = '#mw-normal-catlinks li a';
+ var _request;
+
+ var _catElements = {};
+ var _otherElements = {};
+
+ var namespaceIds = mw.config.get( 'wgNamespaceIds' )
+ var categoryNamespaceId = namespaceIds['category'];
+ var categoryNamespace = mw.config.get( 'wgFormattedNamespaces' )[categoryNamespaceId];
+ var wgScriptPath = mw.config.get( 'wgScriptPath' );
+
+ function _fetchSuggestions ( query ) {
+ //SYNCED
+ var _this = this;
+ // ignore bad characters, they will be stripped out
+ var catName = _stripIllegals( $( this ).val() );
+ var request = $.ajax( {
url: wgScriptPath + '/api.php',
data: {
'action': 'query',
'list': 'allpages',
- 'apnamespace': 14,
- 'apprefix': $j( this ).val(),
+ 'apnamespace': categoryNamespaceId,
+ 'apprefix': catName,
'format': 'json'
},
dataType: 'json',
var pages = data.query.allpages;
var titleArr = [];
- $j.each( pages, function( i, page ) {
+ $.each( pages, function( i, page ) {
var title = page.title.split( ':', 2 )[1];
titleArr.push( title );
} );
- $j( that ).suggestions( 'suggestions', titleArr );
+ $( _this ).suggestions( 'suggestions', titleArr );
}
} );
+ //TODO
+ _request = request;
+ }
- ajaxCategories.request = request;
- },
+ function _stripIllegals( cat ) {
+ return cat.replace( /[\x00-\x1f\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f]+/g, '' );
+ }
+
+ function _insertCatDOM( cat, isHidden ) {
+ // User can implicitely state a sort key.
+ // Remove before display
+ cat = cat.replace(/\|.*/, '');
+
+ // strip out bad characters
+ cat = _stripIllegals ( cat );
+
+ if ( $.isEmpty( cat ) || _containsCat( cat ) ) {
+ return;
+ }
+
+ var $catLinkWrapper = $( catLinkWrapper );
+ var $anchor = $( '<a/>' ).append( cat );
+ $catLinkWrapper.append( $anchor );
+ $anchor.attr( { target: "_blank", href: _catLink( cat ) } );
+ if ( isHidden ) {
+ $container.find( '#mw-hidden-catlinks ul' ).append( $catLinkWrapper );
+ } else {
+ $container.find( '#mw-normal-catlinks ul' ).append( $catLinkWrapper );
+ }
+ _createCatButtons( $anchor.get(0) );
+ }
+
+ function _makeSuggestionBox( prefill, callback, buttonVal ) {
+ // Create add category prompt
+ var promptContainer = $( '<div class="mw-addcategory-prompt"/>' );
+ var promptTextbox = $( '<input type="text" size="45" class="mw-addcategory-input"/>' );
+ if ( prefill !== '' ) {
+ promptTextbox.val( prefill );
+ }
+ var addButton = $( '<input type="button" class="mw-addcategory-button"/>' );
+ addButton.val( buttonVal );
+
+ addButton.click( callback );
+
+ promptTextbox.suggestions( {
+ 'fetch':_fetchSuggestions,
+ 'cancel': function() {
+ var req = _request;
+ // XMLHttpRequest.abort is unimplemented in IE6, also returns nonstandard value of "unknown" for typeof
+ if ( req && ( typeof req.abort !== 'unknown' ) && ( typeof req.abort !== 'undefined' ) && req.abort ) {
+ req.abort();
+ }
+ }
+ } );
- reloadCategoryList : function( response ) {
- var holder = $j( '<div/>' );
+ promptTextbox.suggestions();
- holder.load(
- window.location.href + ' .catlinks',
- function() {
- $j( '.catlinks' ).replaceWith( holder.find( '.catlinks' ) );
- ajaxCategories.setupAJAXCategories();
- ajaxCategories.removeProgressIndicator( $j( '.catlinks' ) );
- }
- );
- },
+ promptContainer.append( promptTextbox );
+ promptContainer.append( addButton );
- confirmEdit : function( page, fn, actionSummary, doneFn ) {
- // Load jQuery UI
- mvJsLoader.doLoad(
- ['$j.ui', '$j.ui.dialog', '$j.fn.suggestions'],
- function() {
- // Produce a confirmation dialog
-
- var dialog = $j( '<div/>' );
-
- dialog.addClass( 'mw-ajax-confirm-dialog' );
- dialog.attr( 'title', gM( 'ajax-confirm-title' ) );
-
- // Intro text.
- var confirmIntro = $j( '<p/>' );
- confirmIntro.text( gM( 'ajax-confirm-prompt' ) );
- dialog.append( confirmIntro );
-
- // Summary of the action to be taken
- var summaryHolder = $j( '<p/>' );
- var summaryLabel = $j( '<strong/>' );
- summaryLabel.text( gM( 'ajax-confirm-actionsummary' ) + " " );
- summaryHolder.text( actionSummary );
- summaryHolder.prepend( summaryLabel );
- dialog.append( summaryHolder );
-
- // Reason textbox.
- var reasonBox = $j( '<input type="text" size="45" />' );
- reasonBox.addClass( 'mw-ajax-confirm-reason' );
- dialog.append( reasonBox );
-
- // Submit button
- var submitButton = $j( '<input type="button"/>' );
- submitButton.val( gM( 'ajax-confirm-save' ) );
-
- var submitFunction = function() {
- ajaxCategories.addProgressIndicator( dialog );
- ajaxCategories.doEdit(
- page,
- fn,
- reasonBox.val(),
- function() {
- doneFn();
- dialog.dialog( 'close' );
- ajaxCategories.removeProgressIndicator( dialog );
- }
- );
- };
-
- var buttons = { };
- buttons[gM( 'ajax-confirm-save' )] = submitFunction;
- var dialogOptions = {
- 'AutoOpen' : true,
- 'buttons' : buttons,
- 'width' : 450
- };
-
- $j( '#catlinks' ).prepend( dialog );
- dialog.dialog( dialogOptions );
- }
- );
- },
+ return promptContainer;
+ }
+
+ // Create a valid link to the category.
+ function _catLink ( cat ) {
+ //SYNCED
+ return mw.util.wikiGetlink( categoryNamespace + ':' + $.ucFirst( cat ) );
+ }
+
+ function _getCats() {
+ return $container.find( categoryLinkSelector ).map( function() { return $.trim( $( this ).text() ); } );
+ }
- doEdit : function( page, fn, summary, doneFn ) {
+ function _containsCat( cat ) {
+ //TODO: SYNC
+ return _getCats().filter( function() { return $.ucFirst(this) == $.ucFirst(cat); } ).length !== 0;
+ }
+
+ function _confirmEdit ( page, fn, actionSummary, doneFn ) {
+
+ // Produce a confirmation dialog
+ var dialog = $( '<div/>' );
+
+ dialog.addClass( 'mw-ajax-confirm-dialog' );
+ dialog.attr( 'title', mw.msg( 'ajax-confirm-title' ) );
+
+ // Intro text.
+ var confirmIntro = $( '<p/>' );
+ confirmIntro.text( mw.msg( 'ajax-confirm-prompt' ) );
+ dialog.append( confirmIntro );
+
+ // Summary of the action to be taken
+ var summaryHolder = $( '<p/>' );
+ var summaryLabel = $( '<strong/>' );
+ summaryLabel.text( mw.msg( 'ajax-confirm-actionsummary' ) + " " );
+ summaryHolder.text( actionSummary );
+ summaryHolder.prepend( summaryLabel );
+ dialog.append( summaryHolder );
+
+ // Reason textbox.
+ var reasonBox = $( '<input type="text" size="45" />' );
+ reasonBox.addClass( 'mw-ajax-confirm-reason' );
+ dialog.append( reasonBox );
+
+ // Submit button
+ var submitButton = $( '<input type="button"/>' );
+ submitButton.val( mw.msg( 'ajax-confirm-save' ) );
+
+ var submitFunction = function() {
+ _addProgressIndicator( dialog );
+ _doEdit(
+ page,
+ fn,
+ reasonBox.val(),
+ function() {
+ doneFn();
+ dialog.dialog( 'close' );
+ _removeProgressIndicator( dialog );
+ }
+ );
+ };
+
+ var buttons = { };
+ buttons[mw.msg( 'ajax-confirm-save' )] = submitFunction;
+ var dialogOptions = {
+ 'AutoOpen' : true,
+ 'buttons' : buttons,
+ 'width' : 450
+ };
+
+ $( '#catlinks' ).prepend( dialog );
+ dialog.dialog( dialogOptions );
+ }
+
+ function _doEdit ( page, fn, summary, doneFn ) {
// Get an edit token for the page.
var getTokenVars = {
'action':'query',
'format':'json'
};
- $j.get( wgScriptPath + '/api.php', getTokenVars,
+ $.get( wgScriptPath + '/api.php', getTokenVars,
function( reply ) {
var infos = reply.query.pages;
- $j.each(
+ $.each(
infos,
function( pageid, data ) {
var token = data.edittoken;
'format':'json'
};
- $j.post( wgScriptPath + '/api.php', postEditVars, doneFn, 'json' );
+ $.post( wgScriptPath + '/api.php', postEditVars, doneFn, 'json' );
}
);
}
, 'json' );
- },
+ }
- addProgressIndicator : function( elem ) {
- var indicator = $j( '<div/>' );
+ function _addProgressIndicator ( elem ) {
+ var indicator = $( '<div/>' );
indicator.addClass( 'mw-ajax-loader' );
elem.append( indicator );
- },
+ }
- removeProgressIndicator : function( elem ) {
+ function _removeProgressIndicator ( elem ) {
elem.find( '.mw-ajax-loader' ).remove();
- },
-
- handleCategoryAdd : function( e ) {
- // Grab category text
- var category = $j( '#mw-addcategory-input' ).val();
- var appendText = "\n[[" + wgFormattedNamespaces[14] + ":" + category + "]]\n";
- var summary = gM( 'ajax-add-category-summary', category );
-
- ajaxCategories.confirmEdit(
- wgPageName,
- function( oldText ) { return oldText + appendText },
- summary,
- ajaxCategories.reloadCategoryList
- );
- },
-
- handleDeleteLink : function( e ) {
- e.preventDefault();
-
- var category = $j( this ).parent().find( 'a' ).text();
-
+ }
+
+ function _makeCaseInsensitiv( string ) {
+ var newString = '';
+ for (var i=0; i < string.length; i++) {
+ newString += '[' + string[i].toUpperCase() + string[i].toLowerCase() + ']';
+ };
+ return newString;
+ }
+ function _buildRegex ( category ) {
// Build a regex that matches legal invocations of that category.
-
- // In theory I should escape the aliases, but there's no JS function for it
- // Shouldn't have any real impact, can't be exploited or anything, so we'll
- // leave it for now.
var categoryNSFragment = '';
- $j.each( wgNamespaceIds, function( name, id ) {
+ $.each( namespaceIds, function( name, id ) {
if ( id == 14 ) {
- // Allow the first character to be any case
- var firstChar = name.charAt( 0 );
- firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']';
- categoryNSFragment += '|' + firstChar + name.substr( 1 );
+ // The parser accepts stuff like cATegORy,
+ // we need to do the same
+ categoryNSFragment += '|' + _makeCaseInsensitiv ( $.escapeRE(name) );
}
} );
categoryNSFragment = categoryNSFragment.substr( 1 ); // Remove leading |
-
+
// Build the regex
- var titleFragment = category;
+ var titleFragment = $.escapeRE(category);
firstChar = category.charAt( 0 );
firstChar = '[' + firstChar.toUpperCase() + firstChar.toLowerCase() + ']';
titleFragment = firstChar + category.substr( 1 );
- var categoryRegex = '\\[\\[' + categoryNSFragment + ':' + titleFragment + '(\\|[^\\]]*)?\\]\\]';
- categoryRegex = new RegExp( categoryRegex, 'g' );
+ var categoryRegex = '\\[\\[(' + categoryNSFragment + '):' + titleFragment + '(\\|[^\\]]*)?\\]\\]';
+
+ return new RegExp( categoryRegex, 'g' );
+ }
+
+ function _handleEditLink ( e ) {
+ e.preventDefault();
+ var $this = $( this );
+ var $link = $this.parent().find( 'a:not(.icon)' );
+ var category = $link.text();
+
+ var $input = _makeSuggestionBox( category, _handleCategoryEdit, mw.msg( 'ajax-confirm-save' ) );
+ $link.after( $input ).hide();
+ _catElements[category].editButton.hide();
+ _catElements[category].deleteButton.unbind('click').click( function() {
+ $input.remove();
+ $link.show();
+ _catElements[category].editButton.show();
+ $( this ).unbind('click').click( _handleDeleteLink );
+ });
+ }
+
+ function _handleAddLink ( e ) {
+ e.preventDefault();
+
+ $container.find( '#mw-normal-catlinks>.mw-addcategory-prompt' ).toggle();
+ }
+
+ function _handleDeleteLink ( e ) {
+ e.preventDefault();
- var summary = gM( 'ajax-remove-category-summary', category );
+ var $this = $( this );
+ var $link = $this.parent().find( 'a:not(.icon)' );
+ var category = $link.text();
- ajaxCategories.confirmEdit(
- wgPageName,
+ categoryRegex = _buildRegex( category );
+
+ var summary = mw.msg( 'ajax-remove-category-summary', category );
+
+ _confirmEdit(
+ mw.config.get('wgPageName'),
function( oldText ) {
+ //TODO Cleanup whitespace safely?
var newText = oldText.replace( categoryRegex, '' );
if ( newText == oldText ) {
- var error = gM( 'ajax-remove-category-error' );
- ajaxCategories.showError( error );
- ajaxCategories.removeProgressIndicator( $j( '.mw-ajax-confirm-dialog' ) );
- $j( '.mw-ajax-confirm-dialog' ).dialog( 'close' );
+ var error = mw.msg( 'ajax-remove-category-error' );
+ _showError( error );
+ _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) );
+ $( '.mw-ajax-confirm-dialog' ).dialog( 'close' );
return false;
}
return newText;
},
- summary, ajaxCategories.reloadCategoryList
+ summary,
+ function() {
+ $this.parent().remove();
+ }
);
- },
+ }
- showError : function( str ) {
- var dialog = $j( '<div/>' );
+ function _handleCategoryAdd ( e ) {
+ // Grab category text
+ var category = $( this ).parent().find( '.mw-addcategory-input' ).val();
+ category = $.ucFirst( category );
+
+ if ( _containsCat(category) ) {
+ // TODO add info alert
+ return;
+ }
+ var appendText = "\n[[" + categoryNamespace + ":" + category + "]]\n";
+ var summary = mw.msg( 'ajax-add-category-summary', category );
+
+ _confirmEdit(
+ mw.config.get( 'wgPageName' ),
+ function( oldText ) { return oldText + appendText },
+ summary,
+ function() {
+ _insertCatDOM( category, false );
+ }
+ );
+ }
+
+ function _handleCategoryEdit ( e ) {
+ e.preventDefault();
+
+ // Grab category text
+ var categoryNew = $( this ).parent().find( '.mw-addcategory-input' ).val();
+ categoryNew = $.ucFirst( categoryNew );
+
+ var $this = $( this );
+ var $link = $this.parent().parent().find( 'a:not(.icon)' );
+ var category = $link.text();
+
+ // User didn't change anything. Just close the box
+ if ( category == categoryNew ) {
+ $this.parent().remove();
+ $link.show();
+ return;
+ }
+ categoryRegex = _buildRegex( category );
+
+ var summary = mw.msg( 'ajax-edit-category-summary', category, categoryNew );
+
+ _confirmEdit(
+ mw.config.get( 'wgPageName' ),
+ function( oldText ) {
+ var matches = oldText.match( categoryRegex );
+
+ //Old cat wasn't found, likely to be transcluded
+ if ( !$.isArray( matches ) ) {
+ var error = mw.msg( 'ajax-edit-category-error' );
+ _showError( error );
+ _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) );
+ $( '.mw-ajax-confirm-dialog' ).dialog( 'close' );
+ return false;
+ }
+ var sortkey = matches[0].replace( categoryRegex, '$2' );
+ var newCategoryString = "[[" + categoryNamespace + ":" + categoryNew + sortkey + ']]';
+
+ if (matches.length > 1) {
+ // The category is duplicated.
+ // Remove all but one match
+ for (var i = 1; i < matches.length; i++) {
+ oldText = oldText.replace( matches[i], '');
+ }
+ }
+ var newText = oldText.replace( categoryRegex, newCategoryString );
+
+ return newText;
+ },
+ summary,
+ function() {
+ // Remove input box & button
+ $this.parent().remove();
+
+ // Update link text and href
+ $link.show().text( categoryNew ).attr( 'href', _catLink( categoryNew ) );
+ }
+ );
+ }
+ function _showError ( str ) {
+ var dialog = $( '<div/>' );
dialog.text( str );
- $j( '#bodyContent' ).append( dialog );
+ $( '#bodyContent' ).append( dialog );
var buttons = { };
- buttons[gM( 'ajax-error-dismiss' )] = function( e ) {
+ buttons[mw.msg( 'ajax-error-dismiss' )] = function( e ) {
dialog.dialog( 'close' );
};
var dialogOptions = {
'buttons' : buttons,
'AutoOpen' : true,
- 'title' : gM( 'ajax-error-title' )
+ 'title' : mw.msg( 'ajax-error-title' )
};
dialog.dialog( dialogOptions );
- },
+ }
- setupAJAXCategories : function() {
+ function _createButton ( icon, title, category, text ){
+ var button = $( '<a>' ).addClass( category || '' )
+ .attr('title', title);
+
+ if ( text ) {
+ var icon = $( '<a>' ).addClass( 'icon ' + icon );
+ button.addClass( 'icon-parent' ).append( icon ).append( text );
+ } else {
+ button.addClass( 'icon ' + icon );
+ }
+ return button;
+ }
+ function _createCatButtons ( element ) {
+ // Create remove & edit buttons
+ var deleteButton = _createButton('icon-close', mw.msg( 'ajax-remove-category' ) );
+ var editButton = _createButton('icon-edit', mw.msg( 'ajax-edit-category' ) );
+
+ //Not yet used
+ var saveButton = _createButton('icon-tick', mw.msg( 'ajax-confirm-save' ) ).hide();
+
+ deleteButton.click( _handleDeleteLink );
+ editButton.click( _handleEditLink );
+
+ $( element ).after( deleteButton ).after( editButton );
+
+ //Save references to all links and buttons
+ _catElements[$( element ).text()] = {
+ link : $( element ),
+ parent : $( element ).parent(),
+ saveButton : saveButton,
+ deleteButton: deleteButton,
+ editButton : editButton
+ };
+ }
+ function _setup() {
+ // Could be set by gadgets like HotCat etc.
+ if ( mw.config.get('disableAJAXCategories') ) {
+ return;
+ }
// Only do it for articles.
- if ( !wgIsArticle ) return;
+ if ( !mw.config.get( 'wgIsArticle' ) ) return;
- var clElement = $j( '.catlinks' );
+ var clElement = $( '#mw-normal-catlinks' );
// Unhide hidden category holders.
- clElement.removeClass( 'catlinks-allhidden' );
+ $('#mw-hidden-catlinks').show();
- var addLink = $j( '<a/>' );
- addLink.addClass( 'mw-ajax-addcategory' );
// Create [Add Category] link
- addLink.text( gM( 'ajax-add-category' ) );
- addLink.attr( 'href', '#' );
- addLink.click( ajaxCategories.handleAddLink );
+ var addLink = _createButton('icon-add',
+ mw.msg( 'ajax-add-category' ),
+ 'mw-ajax-addcategory',
+ mw.msg( 'ajax-add-category' )
+ );
+ addLink.click( _handleAddLink );
clElement.append( addLink );
// Create add category prompt
- var promptContainer = $j( '<div id="mw-addcategory-prompt"/>' );
- var promptTextbox = $j( '<input type="text" size="45" id="mw-addcategory-input"/>' );
- var addButton = $j( '<input type="button" id="mw-addcategory-button"/>' );
- addButton.val( gM( 'ajax-add-category-submit' ) );
-
- promptTextbox.keypress( ajaxCategories.handleCategoryInput );
- addButton.click( ajaxCategories.handleCategoryAdd );
-
- promptContainer.append( promptTextbox );
- promptContainer.append( addButton );
+ var promptContainer = _makeSuggestionBox( '', _handleCategoryAdd, mw.msg( 'ajax-add-category-submit' ) );
promptContainer.hide();
- // Create delete link for each category.
- $j( '.catlinks div span a' ).each( function( e ) {
- // Create a remove link
- var deleteLink = $j( '<a class="mw-remove-category" href="#"/>' );
-
- deleteLink.click( ajaxCategories.handleDeleteLink );
-
- $j( this ).after( deleteLink );
- } );
+ // Create edit & delete link for each category.
+ $( '#catlinks li a' ).each( function( e ) {
+ _createCatButtons( this );
+ });
clElement.append( promptContainer );
}
-};
+ function _teardown() {
+
+ }
+ _tasks = {
+ list : [],
+ executed : [],
+ add : function( obj ) {
+ this.list.push( obj );
+ },
+ next : function() {
+ var task = this.list.shift();
+ //run task
+ this.executed.push( task );
+ }
+ }
+ $(document).ready( function() {_setup()});
-js2AddOnloadHook( ajaxCategories.setupAJAXCategories );
+} )( jQuery, mediaWiki );
\ No newline at end of file