AJAXify watchlist editor
authorEranroz <eranroz89@gmail.com>
Sun, 3 Jun 2012 14:39:04 +0000 (17:39 +0300)
committerEranroz <eranroz89@gmail.com>
Mon, 30 Jul 2012 21:47:36 +0000 (00:47 +0300)
This patch makes the watchlist editor to use pagination. That would
avoid old browsers crashing (bug 20483).
Patch also add some AJAX operations to the editor, for example to remove
items from watchlist (bug 32151).

The AJAX support is an ALTERNATIVE to the form based method, to keep
support for non javascript users.

This change contains a required change in the API for watch operation,
to allow batch operation, by support titles parameter. The old title
(single page) parameter is still used to keep backward compatibility.

Change-Id: I1d8c66db9ba6456858ef655397935a2b3a421632

includes/api/ApiWatch.php
includes/specials/SpecialEditWatchlist.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/language/messages.inc
resources/Resources.php
resources/mediawiki.special/mediawiki.special.editWatchlist.js [new file with mode: 0644]

index c923c6d..a897cfd 100644 (file)
@@ -40,14 +40,27 @@ class ApiWatch extends ApiBase {
                if ( !$user->isLoggedIn() ) {
                        $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
                }
-
                $params = $this->extractRequestParams();
-               $title = Title::newFromText( $params['title'] );
-
-               if ( !$title || $title->getNamespace() < 0 ) {
-                       $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+               // titles can handle basic request of 1 title,
+               // but title is still supported for backward compatability
+               if ( isset( $params['title'] ) ) {
+                       $title = Title::newFromText( $params['title'] );
+                       if ( !$title || $title->getNamespace() < 0 ) {
+                               $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+                       }
+                       $res = $this->watchTitle( $title, $user, $params);
+               } else {
+                       $pageSet = new ApiPageSet( $this );
+                       $pageSet->execute();
+                       $res = array();
+                       foreach ( $pageSet->getTitles() as $title ) {
+                               $r = $this->watchTitle( $title, $user, $params);
+                               $res[] = $r;
+                       }
                }
-
+               $this->getResult()->addValue( null, $this->getModuleName(), $res );
+       }
+       private function watchTitle( $title, $user, $params ) {
                $res = array( 'title' => $title->getPrefixedText() );
 
                if ( $params['unwatch'] ) {
@@ -62,7 +75,7 @@ class ApiWatch extends ApiBase {
                if ( !$success ) {
                        $this->dieUsageMsg( 'hookaborted' );
                }
-               $this->getResult()->addValue( null, $this->getModuleName(), $res );
+               return $res;
        }
 
        public function mustBePosted() {
@@ -85,7 +98,10 @@ class ApiWatch extends ApiBase {
                return array(
                        'title' => array(
                                ApiBase::PARAM_TYPE => 'string',
-                               ApiBase::PARAM_REQUIRED => true
+                       ),
+                       'titles' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_ISMULTI => true
                        ),
                        'unwatch' => false,
                        'token' => null,
index 67f6d68..4c58e09 100644 (file)
@@ -43,6 +43,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
        const EDIT_RAW = 2;
        const EDIT_NORMAL = 3;
 
+       protected $offset = 0;
+       protected $limit = 0;
        protected $successMessage;
 
        protected $toc;
@@ -92,6 +94,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                        }
                }
                $mode = self::getMode( $this->getRequest(), $mode );
+               list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset( 50, 'wllimit' );
 
                switch( $mode ) {
                        case self::EDIT_CLEAR:
@@ -110,6 +113,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                        case self::EDIT_NORMAL:
                        default:
                                $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
+                               $out->addModules( 'mediawiki.special.editWatchlist' );
                                $form = $this->getNormalForm();
                                if( $form->show() ){
                                        $out->addHTML( $this->successMessage );
@@ -264,29 +268,42 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
        }
 
        /**
-        * Get a list of titles on a user's watchlist, excluding talk pages,
-        * and return as a two-dimensional array with namespace and title.
-        *
-        * @return array
-        */
-       private function getWatchlistInfo() {
-               $titles = array();
+       * select from DB  watchlist items watched by the current user
+       * @return q query result of watchlist items watched by the current user
+       */
+       private function selectWatchListInfo( ) {
+               $options = array(
+                       'ORDER BY' => array( 'wl_namespace', 'wl_title' ),
+                       'LIMIT' => intval( $this->limit ),
+                       'OFFSET' => intval( $this->offset )
+               );
                $dbr = wfGetDB( DB_MASTER );
-
+               //query only non talk namespaces.
+               $nonTalkNamespaces = MWNamespace::getContentNamespaces();
                $res = $dbr->select(
                        array( 'watchlist' ),
                        array( 'wl_namespace',  'wl_title' ),
-                       array( 'wl_user' => $this->getUser()->getId() ),
+                       array( 'wl_user' => $this->getUser()->getId(), 'wl_namespace' => $nonTalkNamespaces  ),
                        __METHOD__,
-                       array( 'ORDER BY' => array( 'wl_namespace', 'wl_title' ) )
+                       $options
                );
 
+               return $res;
+       }
+
+       /**
+        * Get a list of titles on a user's watchlist, excluding talk pages,
+        * and return as a two-dimensional array with namespace and title.
+        *
+        * @param $watchedItems rows of watched items
+        * @return array
+        */
+       private function getWatchlistInfo( $watchedItems ) {
+               $titles = array();
                $lb = new LinkBatch();
-               foreach ( $res as $row ) {
+               foreach ( $watchedItems as $row ) {
                        $lb->add( $row->wl_namespace, $row->wl_title );
-                       if ( !MWNamespace::isTalk( $row->wl_namespace ) ) {
-                               $titles[$row->wl_namespace][$row->wl_title] = 1;
-                       }
+                       $titles[$row->wl_namespace][$row->wl_title] = 1;
                }
 
                $lb->execute();
@@ -461,8 +478,9 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
 
                $fields = array();
                $count = 0;
-
-               foreach( $this->getWatchlistInfo() as $namespace => $pages ){
+               $watchedItems = $this->selectWatchListInfo();
+               $rowNum = $watchedItems->numRows();
+               foreach ( $this->getWatchlistInfo( $watchedItems ) as $namespace => $pages ) {
                        if ( $namespace >= 0 ) {
                                $fields['TitlesNs'.$namespace] = array(
                                        'class' => 'EditWatchlistCheckboxSeriesField',
@@ -471,7 +489,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                                );
                        }
 
-                       foreach( array_keys( $pages ) as $dbkey ){
+                       foreach ( array_keys( $pages ) as $dbkey ) {
                                $title = Title::makeTitleSafe( $namespace, $dbkey );
                                if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
                                        $text = $this->buildRemoveLine( $title );
@@ -504,10 +522,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() );
                $form->setTitle( $this->getTitle() );
                $form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
+               $form->setSubmitID( 'watchlistedit-submit' );
                # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
                $form->setSubmitTooltip('watchlistedit-normal-submit');
                $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
-               $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
+               $paging = '<p>' . $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset,
+                               $this->limit,  array(), ( $rowNum < $this->limit ) ) . '</p>';
+               $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() . $paging );
                $form->setSubmitCallback( array( $this, 'submitNormal' ) );
                return $form;
        }
@@ -542,7 +563,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
 
                wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) );
 
-               return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
+               return '<span class="watchlist-item">' . $link . '</span>' . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
        }
 
        /**
index 86954ba..1951853 100644 (file)
@@ -4527,6 +4527,7 @@ Try normal preview.',
 To remove a title, check the box next to it, and click "{{int:Watchlistedit-normal-submit}}".
 You can also [[Special:EditWatchlist/raw|edit the raw list]].',
 'watchlistedit-normal-submit'  => 'Remove titles',
+'watchlistedit-normal-submitting'  => 'Removing titles...',
 'watchlistedit-normal-done'    => '{{PLURAL:$1|1 title was|$1 titles were}} removed from your watchlist:',
 'watchlistedit-raw-title'      => 'Edit raw watchlist',
 'watchlistedit-raw-legend'     => 'Edit raw watchlist',
index e2d7903..f9a68b1 100644 (file)
@@ -4300,6 +4300,7 @@ Bitrate (of a file, typically) in yottabits (1 yottabits = 1000×1000×1000×100
 Hint: the text "Remove Titles" is in {{msg-mw|watchlistedit-normal-submit}}',
 'watchlistedit-normal-submit' => 'Text of submit button on [[Special:Watchlist/edit]].',
 'watchlistedit-normal-done' => 'Message on [[Special:EditWatchlist]] after pages are removed from the watchlist.',
+'watchlistedit-normal-submitting' => 'Text of submit button on [[Special:Watchlist/edit]] when submiting an AJAX request',
 'watchlistedit-raw-title' => 'Title of [[Special:Watchlist/raw|Special page]].
 
 {{Identical|Edit raw watchlist}}',
index 30c3d9a..8d96b3d 100644 (file)
@@ -3399,6 +3399,7 @@ $wgMessageStructure = array(
                'watchlistedit-normal-legend',
                'watchlistedit-normal-explain',
                'watchlistedit-normal-submit',
+               'watchlistedit-normal-submiting',
                'watchlistedit-normal-done',
                'watchlistedit-raw-title',
                'watchlistedit-raw-legend',
index 26d73c5..b4f4617 100644 (file)
@@ -799,6 +799,14 @@ return array(
                'styles' => 'resources/mediawiki.special/mediawiki.special.changeslist.css',
                'dependencies' => array( 'jquery.makeCollapsible' ),
        ),
+       'mediawiki.special.editWatchlist' => array(
+               'scripts' => 'resources/mediawiki.special/mediawiki.special.editWatchlist.js',
+               'dependencies' => array(
+                       'mediawiki.api',
+                       'user.tokens'
+               ),
+               'messages' => array( 'watchlistedit-normal-submit', 'watchlistedit-normal-submiting' )
+       ),
        'mediawiki.special.movePage' => array(
                'scripts' => 'resources/mediawiki.special/mediawiki.special.movePage.js',
                'dependencies' => 'jquery.byteLimit',
diff --git a/resources/mediawiki.special/mediawiki.special.editWatchlist.js b/resources/mediawiki.special/mediawiki.special.editWatchlist.js
new file mode 100644 (file)
index 0000000..66329cf
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * JavaScript for Special:EditWatchlist
+ */
+
+/**
+ * Replace the submit button action to operate with ajax.
+ */
+( function ( mw, $ ) {
+       $( '#watchlistedit-submit' ).parents( 'form:first' ).on( 'submit.ajax', function ( e ) {
+               var titlesToRemove, params, api;
+               titlesToRemove = $.map( $( '.mw-htmlform-flatlist-item input:checked' ), function ( el ) {
+                       return $( el ).val();
+               } ).join( '|' );
+               params = {
+                       action: 'watch',
+                       titles: titlesToRemove,
+                       token: mw.user.tokens.get( 'watchToken' ),
+                       unwatch: '1'
+               };
+               api = new mw.Api();
+               api.ajax( params, { type: 'POST' } ).done( function ( data ) {
+                               $.each( data.watch, function ( e ) {
+                                       var removedItem = this.title;
+                                       var item = $( '.watchlist-item a' ).filter( function ( ) {
+                                                               return this.title === removedItem;
+                                                       } ).parents( '.mw-htmlform-flatlist-item' ).fadeOut();
+                               } );
+                               $( '#watchlistedit-submit' ).prop( {
+                                       disabled: false,
+                                       value: mw.msg( 'watchlistedit-normal-submit' )
+                               } );
+                       } ).fail( function () {
+                               //some error occurred.
+                               //re-enable the submit and try to send normal submit
+                               $( '#watchlistedit-submit' ).prop( {
+                                       disabled: false,
+                                       value: mw.msg( 'watchlistedit-normal-submit' )
+                               } ).parents( 'form:first' )
+                                  .off( 'submit.ajax' ).submit();
+                       } );
+               $( '#watchlistedit-submit' ).prop( { disabled: true, value:  mw.msg( 'watchlistedit-normal-submitting' )  } );
+               e.preventDefault();
+       } );
+} )( mediaWiki, jQuery );