Rewrite ajaxCategories for ResourceLoader. Add some missing functionality (edit categ...
authorLeo Koppelkamm <diebuche@users.mediawiki.org>
Wed, 13 Jul 2011 22:36:03 +0000 (22:36 +0000)
committerLeo Koppelkamm <diebuche@users.mediawiki.org>
Wed, 13 Jul 2011 22:36:03 +0000 (22:36 +0000)
includes/DefaultSettings.php
includes/OutputPage.php
languages/messages/MessagesEn.php
maintenance/language/messages.inc
resources/Resources.php
resources/mediawiki.page/images/AJAXCategorySprite.png [new file with mode: 0644]
resources/mediawiki.page/mediawiki.page.ajaxCategories.css [new file with mode: 0644]
resources/mediawiki.page/mediawiki.page.ajaxCategories.js
skins/common/shared.css

index 7cbb25a..9123ec8 100644 (file)
@@ -5500,6 +5500,19 @@ $wgSeleniumConfigFile = null;
 $wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
 $wgDBtestpassword = '';
 
+/**
+ * Whether or not to use the AJAX categories system.
+ */
+$wgUseAJAXCategories = false;
+
+/**
+ * Only enable AJAXCategories on configured namespaces. Default is all.
+ *
+ * Example:
+ *   $wgAJAXCategoriesNamespaces = array( NS_MAIN, NS_PROJECT );
+ */
+$wgAJAXCategoriesNamespaces = array();
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 82d082a..79ceefe 100644 (file)
@@ -2340,8 +2340,8 @@ $templates
         * Add the default ResourceLoader modules to this object
         */
        private function addDefaultModules() {
-               global $wgIncludeLegacyJavaScript,
-                       $wgUseAjax, $wgAjaxWatch, $wgEnableMWSuggest;
+               global $wgIncludeLegacyJavaScript, $wgUseAjax, 
+                       $wgAjaxWatch, $wgEnableMWSuggest, $wgUseAJAXCategories;
 
                // Add base resources
                $this->addModules( array(
@@ -2369,9 +2369,19 @@ $templates
                        }
                }
 
-               if( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
+               if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
                        $this->addModules( 'mediawiki.action.view.rightClickEdit' );
                }
+               
+               if ( $wgUseAJAXCategories ) {
+                       global $wgAJAXCategoriesNamespaces;
+
+                       $title = $this->getTitle();
+
+                       if( empty( $wgAJAXCategoriesNamespaces ) || in_array( $title->getNamespace(), $wgAJAXCategoriesNamespaces ) ) {
+                               $this->addModules( 'mediawiki.page.ajaxCategories' );
+                       }
+               }
        }
 
        /**
index badda88..68e148d 100644 (file)
@@ -4586,4 +4586,24 @@ This site is experiencing technical difficulties.',
 'sqlite-has-fts' => '$1 with full-text search support',
 'sqlite-no-fts'  => '$1 without full-text search support',
 
+
+# Add categories per AJAX
+'ajax-add-category'            => 'Add category',
+'ajax-remove-category'         => 'Remove category',
+'ajax-edit-category'           => 'Edit category',
+'ajax-add-category-submit'     => 'Add',
+'ajax-confirm-title'           => 'Confirm action',
+'ajax-confirm-prompt'          => 'You can provide an edit summary below.
+Click "Save" to save your edit.',
+'ajax-confirm-save'            => 'Save',
+'ajax-add-category-summary'    => 'Add category "$1"',
+'ajax-edit-category-summary'   => 'Change category "$1" to "$2"',
+'ajax-remove-category-summary' => 'Remove category "$1"',
+'ajax-confirm-actionsummary'   => 'Action to take:',
+'ajax-error-title'             => 'Error',
+'ajax-error-dismiss'           => 'OK',
+'ajax-remove-category-error'   => 'It was not possible to remove this category.
+This usually occurs when the category has been added to the page in a template.',
+'ajax-edit-category-error'     => 'It was not possible to edit this category.
+This usually occurs when the category has been added to the page in a template.',
 );
index c67b793..efc3fa9 100644 (file)
@@ -3459,6 +3459,24 @@ $wgMessageStructure = array(
        'unwatch' => array(
                'confirm-unwatch-button',
        ),
+       'ajax-category' => array(
+               'ajax-add-category',
+               'ajax-remove-category',
+               'ajax-edit-category',
+               'ajax-add-category-submit',
+               'ajax-confirm-title',
+               'ajax-confirm-prompt',
+               'ajax-confirm-save',
+               'ajax-add-category-summary',
+               'ajax-edit-category-summary',
+               'ajax-remove-category-summary',
+               'ajax-confirm-actionsummary',
+               'ajax-error-title',
+               'ajax-error-dismiss',
+               'ajax-remove-category-error',
+               'ajax-edit-category-error',
+       ),
+
 );
 
 /** Comments for each block */
@@ -3687,4 +3705,5 @@ Variants for Chinese language",
        'db-error-messages'     => 'Database error messages',
        'html-forms'            => 'HTML forms',
        'sqlite'                => 'SQLite database support',
+       'ajax-category'         => 'Add categories per AJAX',
 );
index f2692dc..9bc4b8f 100644 (file)
@@ -483,6 +483,31 @@ return array(
                        'jquery.ui.autocomplete',
                ),
        ),
+       'mediawiki.page.ajaxCategories' => array(
+               'scripts' => 'resources/mediawiki.page/mediawiki.page.ajaxCategories.js',
+               'styles' => 'resources/mediawiki.page/mediawiki.page.ajaxCategories.css',
+               'dependencies' => array(
+                       'jquery.suggestions',
+                       'jquery.ui.dialog',
+               ),
+               'messages' => array(
+                       'ajax-add-category',
+                       'ajax-remove-category',
+                       'ajax-edit-category',
+                       'ajax-add-category-submit',
+                       'ajax-confirm-prompt',
+                       'ajax-confirm-title',
+                       'ajax-confirm-save',
+                       'ajax-add-category-summary',
+                       'ajax-remove-category-summary',
+                       'ajax-edit-category-summary',
+                       'ajax-confirm-actionsummary',
+                       'ajax-error-title',
+                       'ajax-error-dismiss',
+                       'ajax-remove-category-error',
+                       'ajax-edit-category-error',
+               ),
+       ),
        'mediawiki.libs.jpegmeta' => array(
                'scripts' => 'resources/mediawiki.libs/mediawiki.libs.jpegmeta.js',
        ),
diff --git a/resources/mediawiki.page/images/AJAXCategorySprite.png b/resources/mediawiki.page/images/AJAXCategorySprite.png
new file mode 100644 (file)
index 0000000..d5f9cf4
Binary files /dev/null and b/resources/mediawiki.page/images/AJAXCategorySprite.png differ
diff --git a/resources/mediawiki.page/mediawiki.page.ajaxCategories.css b/resources/mediawiki.page/mediawiki.page.ajaxCategories.css
new file mode 100644 (file)
index 0000000..1a9f6ce
--- /dev/null
@@ -0,0 +1,62 @@
+.mw-addcategory-prompt {
+       display: inline;
+}
+
+.mw-addcategory-prompt input {
+       margin-left: 0.5em;
+       margin-right: 0.5em;
+}
+
+.mw-remove-category {
+       padding: 2px 8px;
+       display:inline;
+}
+
+#catlinks:hover .icon {
+       opacity: 1;
+}
+
+.mw-ajax-addcategory {
+       padding-left: 30px;
+       margin-right: 1em;
+       cursor: pointer;
+}
+#catlinks .icon {
+       cursor: pointer;
+       padding: 1px 8px;
+       margin: 0;
+       background: url('images/AJAXCategorySprite.png') 0 0 no-repeat;
+       opacity: 0.5;
+}
+#catlinks .icon-parent {
+       cursor: pointer;
+}
+#catlinks .icon-parent:hover .icon {
+       background-position-y: -16px;
+}
+#catlinks .no-text {
+}
+#catlinks .icon-close {
+       background-position: 0 0;
+}
+#catlinks .icon-edit {
+       background-position: -16px 0;
+}
+#catlinks .icon-tick {
+       background-position: -32px 0;
+}
+#catlinks .icon-add {
+       background-position: -64px 0;
+}
+#catlinks .icon-close:hover {
+       background-position: 0 -16px;
+}
+#catlinks .icon-edit:hover {
+       background-position: -16px -16px;
+}
+#catlinks .icon-tick:hover {
+       background-position: -32px -16px;
+}
+#catlinks .icon-add:hover {
+       background-position: -64px -16px;
+}
\ No newline at end of file
index b46d8b3..87410a0 100644 (file)
@@ -1,50 +1,42 @@
-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',
@@ -53,94 +45,149 @@ var ajaxCategories = {
                                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',
@@ -151,10 +198,10 @@ var ajaxCategories = {
                        '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;
@@ -175,152 +222,294 @@ var ajaxCategories = {
                                                        '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
index b08853c..0ff29bc 100644 (file)
@@ -583,32 +583,6 @@ div.gallerytext {
        word-wrap: break-word;
 }
 
-#mw-addcategory-prompt {
-       display: inline;
-       margin-left: 1em;
-}
-
-#mw-addcategory-prompt input {
-       margin-left: 0.5em;
-       margin-right: 0.5em;
-}
-
-.mw-remove-category {
-       padding: 8px;
-       /* @embed */
-       background-image: url(images/remove.png);
-       background-position: center center;
-       background-repeat: no-repeat;
-}
-
-.mw-ajax-addcategory {
-       padding-left: 20px;
-       /* @embed */
-       background-image: url(images/add.png);
-       background-position: left center;
-       background-repeat: no-repeat;
-}
-
 .mw-ajax-loader {
        /* @embed */
        background-image: url(images/ajax-loader.gif);