From b46ec8fde5dfd3e80fa3861a01f1aac6f5a7b5ba Mon Sep 17 00:00:00 2001 From: Eranroz Date: Sun, 3 Jun 2012 17:39:04 +0300 Subject: [PATCH] AJAXify watchlist editor 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 | 32 +++++++--- includes/specials/SpecialEditWatchlist.php | 59 +++++++++++++------ languages/messages/MessagesEn.php | 1 + languages/messages/MessagesQqq.php | 1 + maintenance/language/messages.inc | 1 + resources/Resources.php | 8 +++ .../mediawiki.special.editWatchlist.js | 44 ++++++++++++++ 7 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 resources/mediawiki.special/mediawiki.special.editWatchlist.js diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index c923c6d4bb..a897cfdf2e 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -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, diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php index 67f6d688c2..4c58e09d30 100644 --- a/includes/specials/SpecialEditWatchlist.php +++ b/includes/specials/SpecialEditWatchlist.php @@ -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 = '

' . $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, + $this->limit, array(), ( $rowNum < $this->limit ) ) . '

'; + $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 '' . $link . '' . " (" . $this->getLanguage()->pipeList( $tools ) . ")"; } /** diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 86954ba4d7..1951853e78 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -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', diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index e2d79034a3..f9a68b18ad 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -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}}', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 30c3d9ab31..8d96b3df98 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -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', diff --git a/resources/Resources.php b/resources/Resources.php index 26d73c5182..b4f4617248 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -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 index 0000000000..66329cf6a8 --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.editWatchlist.js @@ -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 ); -- 2.20.1