From 700e49ddddcb9b791f5edbb5dd569b18c3a7f2eb Mon Sep 17 00:00:00 2001 From: Geoffrey Mon Date: Fri, 10 Jun 2016 20:59:58 -0400 Subject: [PATCH] Unwatch link for pages in Special:Watchlist MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit When the 'watchlistunwatchlinks' preference option is enabled, this adds a '×' link to each entry of the watchlist that unwatches the page of that entry. When clicked, it changes into a '+' which can be used to re-watch the page (effectively undoing the earlier unwatch). When a page is unwatched, its entries and the entries of its associated talk page (or vice versa) become translucent and are struck through. Without JS, '×'/'+' link to action=(un)watch for the relevant page. In addition, ChangesList classes have been modified to allow a prefixer that adds a prefix to each line (used in this case to put the unwatch link) and to add HTML data attributes to reliably determine the target page of each entry. Unit tests have been updated accordingly. Bug: T2424 Change-Id: I450b2901413d7e75c11de2a446829fdbb22d31e1 --- RELEASE-NOTES-1.30 | 4 + includes/DefaultSettings.php | 1 + includes/Preferences.php | 5 + includes/changes/ChangesList.php | 18 ++- includes/changes/EnhancedChangesList.php | 30 ++++- includes/changes/OldChangesList.php | 9 ++ includes/specials/SpecialWatchlist.php | 18 +++ .../EnhancedChangesListGroup.mustache | 6 +- languages/i18n/en.json | 3 + languages/i18n/qqq.json | 5 +- resources/Resources.php | 20 +++- .../mediawiki.special.watchlist.css | 15 +++ .../mediawiki.special.watchlist.js | 110 ++++++++++++++++-- .../changes/EnhancedChangesListTest.php | 49 +++++++- .../includes/changes/OldChangesListTest.php | 34 ++++++ .../specials/SpecialWatchlistTest.php | 1 + 16 files changed, 308 insertions(+), 20 deletions(-) create mode 100644 resources/src/mediawiki.special/mediawiki.special.watchlist.css diff --git a/RELEASE-NOTES-1.30 b/RELEASE-NOTES-1.30 index 452cb35b2c..9c4bcf044c 100644 --- a/RELEASE-NOTES-1.30 +++ b/RELEASE-NOTES-1.30 @@ -49,6 +49,10 @@ section). * Added RecentChangesPurgeRows hook to allow extensions to purge data that depends on the recentchanges table. * Added JS config values wgDiffOldId/wgDiffNewId to the output of diff pages. +* (T2424) Added direct unwatch links to entries in Special:Watchlist (if the + 'watchlistunwatchlinks' preference option is enabled). With JavaScript + enabled, these links toggle so the user can also re-watch pages that have + just been unwatched. === Languages updated in 1.30 === diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index ba755fa1a2..7fa55bc5d7 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -4933,6 +4933,7 @@ $wgDefaultUserOptions = [ 'watchlisthidepatrolled' => 0, 'watchlisthidecategorization' => 1, 'watchlistreloadautomatically' => 0, + 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'wllimit' => 250, diff --git a/includes/Preferences.php b/includes/Preferences.php index 039d99c830..c74d6e1dce 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -1044,6 +1044,11 @@ class Preferences { 'section' => 'watchlist/advancedwatchlist', 'label-message' => 'tog-watchlistreloadautomatically', ]; + $defaultPreferences['watchlistunwatchlinks'] = [ + 'type' => 'toggle', + 'section' => 'watchlist/advancedwatchlist', + 'label-message' => 'tog-watchlistunwatchlinks', + ]; if ( $config->get( 'RCWatchCategoryMembership' ) ) { $defaultPreferences['watchlisthidecategorization'] = [ diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php index 65eb3205c5..cac476929b 100644 --- a/includes/changes/ChangesList.php +++ b/includes/changes/ChangesList.php @@ -41,6 +41,9 @@ class ChangesList extends ContextSource { protected $rclistOpen; protected $rcMoveIndex; + /** @var callable */ + protected $changeLinePrefixer; + /** @var BagOStuff */ protected $watchMsgCache; @@ -169,12 +172,14 @@ class ChangesList extends ContextSource { * @return array of classes */ protected function getHTMLClasses( $rc, $watched ) { - $classes = []; + $classes = [ self::CSS_CLASS_PREFIX . 'line' ]; $logType = $rc->mAttribs['rc_log_type']; if ( $logType ) { + $classes[] = self::CSS_CLASS_PREFIX . 'log'; $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'log-' . $logType ); } else { + $classes[] = self::CSS_CLASS_PREFIX . 'edit'; $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns-' . @@ -766,4 +771,15 @@ class ChangesList extends ContextSource { return $attrs; } + + /** + * Sets the callable that generates a change line prefix added to the beginning of each line. + * + * @param callable $prefixer Callable to run that generates the change line prefix. + * Takes three parameters: a RecentChange object, a ChangesList object, + * and whether the current entry is a grouped entry. + */ + public function setChangeLinePrefixer( callable $prefixer ) { + $this->changeLinePrefixer = $prefixer; + } } diff --git a/includes/changes/EnhancedChangesList.php b/includes/changes/EnhancedChangesList.php index ffe668c60d..def6457ebf 100644 --- a/includes/changes/EnhancedChangesList.php +++ b/includes/changes/EnhancedChangesList.php @@ -172,12 +172,14 @@ class EnhancedChangesList extends ChangesList { $recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' ); # Add the namespace and title of the block as part of the class - $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' ]; + $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc', 'mw-changeslist-line' ]; if ( $block[0]->mAttribs['rc_log_type'] ) { # Log entry + $tableClasses[] = 'mw-changeslist-log'; $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $block[0]->mAttribs['rc_log_type'] ); } else { + $tableClasses[] = 'mw-changeslist-edit'; $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns' . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); } @@ -330,6 +332,11 @@ class EnhancedChangesList extends ChangesList { implode( $this->message['semicolon-separator'], $users ) )->escaped(); + $prefix = ''; + if ( is_callable( $this->changeLinePrefixer ) ) { + $prefix = call_user_func( $this->changeLinePrefixer, $block[0], $this, true ); + } + $templateParams = [ 'articleLink' => $articleLink, 'charDifference' => $charDifference, @@ -338,6 +345,7 @@ class EnhancedChangesList extends ChangesList { 'lines' => $lines, 'logText' => $logText, 'numberofWatchingusers' => $numberofWatchingusers, + 'prefix' => $prefix, 'rev-deleted-event' => $revDeletedMsg, 'tableClasses' => $tableClasses, 'timestamp' => $block[0]->timestamp, @@ -366,7 +374,7 @@ class EnhancedChangesList extends ChangesList { $type = $rcObj->mAttribs['rc_type']; $data = []; - $lineParams = []; + $lineParams = [ 'targetTitle' => $rcObj->getTitle() ]; $classes = [ 'mw-enhanced-rc' ]; if ( $rcObj->watched @@ -609,8 +617,10 @@ class EnhancedChangesList extends ChangesList { if ( $logType ) { # Log entry + $classes[] = 'mw-changeslist-log'; $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType ); } else { + $classes[] = 'mw-changeslist-edit'; $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' . $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] ); } @@ -690,8 +700,15 @@ class EnhancedChangesList extends ChangesList { return $key === 'class' || Sanitizer::isReservedDataAttribute( $key ); } ); + $prefix = ''; + if ( is_callable( $this->changeLinePrefixer ) ) { + $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false ); + } + $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' ); - $line .= ''; + $line .= Html::rawElement( 'td', [], '' ); + $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix ); + $line .= ''; if ( isset( $data['recentChangesFlags'] ) ) { $line .= $this->recentChangesFlags( $data['recentChangesFlags'] ); @@ -702,7 +719,12 @@ class EnhancedChangesList extends ChangesList { $line .= ' ' . $data['timestampLink']; unset( $data['timestampLink'] ); } - $line .= ' '; + $line .= ' '; + $line .= Html::openElement( 'td', [ + 'class' => 'mw-changeslist-line-inner', + // Used for reliable determination of the affiliated page + 'data-target-page' => $rcObj->getTitle(), + ] ); // everything else: makes it easier for extensions to add or remove data $line .= implode( '', $data ); diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php index 4d6187b830..88c3c226cd 100644 --- a/includes/changes/OldChangesList.php +++ b/includes/changes/OldChangesList.php @@ -142,6 +142,15 @@ class OldChangesList extends ChangesList { $html .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers ); } + $html = Html::rawElement( 'span', [ + 'class' => 'mw-changeslist-line-inner', + 'data-target-page' => $rc->getTitle(), // Used for reliable determination of the affiliated page + ], $html ); + if ( is_callable( $this->changeLinePrefixer ) ) { + $prefix = call_user_func( $this->changeLinePrefixer, $rc, $this, false ); + $html = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix ) . $html; + } + return $html; } } diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 51ddc0ba0b..1d76e36106 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -58,6 +58,7 @@ class SpecialWatchlist extends ChangesListSpecialPage { 'mediawiki.special.changeslist.visitedstatus', 'mediawiki.special.watchlist', ] ); + $output->addModuleStyles( [ 'mediawiki.special.watchlist.styles' ] ); $mode = SpecialEditWatchlist::getMode( $request, $subpage ); if ( $mode !== false ) { @@ -431,6 +432,23 @@ class SpecialWatchlist extends ChangesListSpecialPage { $list = ChangesList::newFromContext( $this->getContext(), $this->filterGroups ); $list->setWatchlistDivs(); $list->initChangesListRows( $rows ); + if ( $user->getOption( 'watchlistunwatchlinks' ) ) { + $list->setChangeLinePrefixer( function ( RecentChange $rc, ChangesList $cl, $grouped ) { + // Don't show unwatch link if the line is a grouped log entry using EnhancedChangesList, + // since EnhancedChangesList groups log entries by performer rather than by target article + if ( $rc->mAttribs['rc_type'] == RC_LOG && $cl instanceof EnhancedChangesList && + $grouped ) { + return ''; + } else { + return $this->getLinkRenderer() + ->makeKnownLink( $rc->getTitle(), + $this->msg( 'watchlist-unwatch' )->text(), [ + 'class' => 'mw-unwatch-link', + 'title' => $this->msg( 'tooltip-ca-unwatch' )->text() + ], [ 'action' => 'unwatch' ] ) . ' '; + } + } ); + } $dbr->dataSeek( $rows, 0 ); if ( $this->getConfig()->get( 'RCShowWatchingUsers' ) diff --git a/includes/templates/EnhancedChangesListGroup.mustache b/includes/templates/EnhancedChangesListGroup.mustache index 3a37c2ebcc..cd59d6d3df 100644 --- a/includes/templates/EnhancedChangesListGroup.mustache +++ b/includes/templates/EnhancedChangesListGroup.mustache @@ -3,8 +3,9 @@ + {{{ prefix }}} {{{ collectedRcFlags }}} {{ timestamp }}  - + {{# rev-deleted-event }}{{{ . }}}{{/ rev-deleted-event }} {{{ articleLink }}}{{{ languageDirMark }}}{{{ logText }}} . . @@ -15,9 +16,10 @@ {{# lines }} + {{{ recentChangesFlags }}}  - + {{# timestampLink }} {{{ . }}} {{/ timestampLink }} diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 86ac78ef77..d7a3aeb163 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -37,6 +37,7 @@ "tog-watchlisthideminor": "Hide minor edits from the watchlist", "tog-watchlisthideliu": "Hide edits by logged in users from the watchlist", "tog-watchlistreloadautomatically": "Reload the watchlist automatically whenever a filter is changed (JavaScript required)", + "tog-watchlistunwatchlinks": "Add direct unwatch/watch links to watchlist entries (JavaScript required for toggle functionality)", "tog-watchlisthideanons": "Hide edits by anonymous users from the watchlist", "tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist", "tog-watchlisthidecategorization": "Hide categorization of pages", @@ -2229,6 +2230,8 @@ "watching": "Watching...", "unwatching": "Unwatching...", "watcherrortext": "An error occurred while changing your watchlist settings for \"$1\".", + "watchlist-unwatch": "×", + "watchlist-unwatch-undo": "+", "enotif_reset": "Mark all pages visited", "enotif_impersonal_salutation": "{{SITENAME}} user", "enotif_subject_deleted": "{{SITENAME}} page $1 has been {{GENDER:$2|deleted}} by $2", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 01cb8cab48..f7b176cfbe 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -226,7 +226,8 @@ "tog-watchlisthidebots": "[[Special:Preferences]], tab 'Watchlist'. Offers user to hide bot edits from watchlist. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}", "tog-watchlisthideminor": "[[Special:Preferences]], tab 'Watchlist'. Offers user to hide minor edits from watchlist. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}", "tog-watchlisthideliu": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}", - "tog-watchlistreloadautomatically": "[[Special:Preferences]], tab 'Watchlist'. Offers user to to automatically refresh the watchlist page, when a filter is changed.", + "tog-watchlistreloadautomatically": "[[Special:Preferences]], tab 'Watchlist'. Offers user to automatically refresh the watchlist page, when a filter is changed.", + "tog-watchlistunwatchlinks": "[[Special:Preferences]], tab 'Watchlist'. Offers user to add an unwatch/watch toggle link to watchlist entries.", "tog-watchlisthideanons": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}", "tog-watchlisthidepatrolled": "Option in Watchlist tab of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}", "tog-watchlisthidecategorization": "Option in Watchlist tab of [[Special:Preferences]]. Offers user to hide/show categorization of pages. Appears next to checkboxes with labels such as {{msg-mw|tog-watchlisthideminor}}.", @@ -2419,6 +2420,8 @@ "watching": "Text displayed when clicked on the watch tab: {{msg-mw|Watch}}. It means the wiki is adding that page to your watchlist.", "unwatching": "Text displayed when clicked on the unwatch tab: {{msg-mw|Unwatch}}. It means the wiki is removing that page from your watchlist.", "watcherrortext": "When a user clicked the watch/unwatch tab and the action did not succeed, this message is displayed.\n\nThis message is used raw and should not contain wikitext.\n\nParameters:\n* $1 - ...\nSee also:\n* {{msg-mw|Addedwatchtext}}", + "watchlist-unwatch": "Symbol used for the link to unwatch a page from the watchlist.", + "watchlist-unwatch-undo": "Symbol used for the link to re-watch a page that has been unwatched from the watchlist.", "enotif_reset": "Used in [[Special:Watchlist]].\n\nThis should be translated as \"Mark all pages '''as''' visited\".\n\nSee also:\n* {{msg-mw|Watchlist-options|fieldset}}\n* {{msg-mw|Watchlist-details|watchlist header}}\n* {{msg-mw|Wlheader-enotif|watchlist header}}", "enotif_impersonal_salutation": "Used for impersonal e-mail notifications, suitable for bulk mailing.\n{{Identical|User}}", "enotif_subject_deleted": "Email notification subject for deleted pages. Parameters:\n* $1 - page title\n* $2 - username who has deleted the page, can be used for GENDER", diff --git a/resources/Resources.php b/resources/Resources.php index f004d62052..144747b476 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2218,11 +2218,27 @@ return [ ], 'mediawiki.special.watchlist' => [ 'scripts' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.js', + 'messages' => [ + 'addedwatchtext', + 'addedwatchtext-talk', + 'removedwatchtext', + 'removedwatchtext-talk', + 'tooltip-ca-watch', + 'tooltip-ca-unwatch', + 'watchlist-unwatch', + 'watchlist-unwatch-undo', + ], 'dependencies' => [ - 'mediawiki.api', + 'mediawiki.api.watch', + 'mediawiki.jqueryMsg', + 'mediawiki.Title', + 'mediawiki.util', 'oojs-ui-core', 'user.options', - ] + ], + ], + 'mediawiki.special.watchlist.styles' => [ + 'styles' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.css', ], 'mediawiki.special.version' => [ 'styles' => 'resources/src/mediawiki.special/mediawiki.special.version.css', diff --git a/resources/src/mediawiki.special/mediawiki.special.watchlist.css b/resources/src/mediawiki.special/mediawiki.special.watchlist.css new file mode 100644 index 0000000000..c9861c25e8 --- /dev/null +++ b/resources/src/mediawiki.special/mediawiki.special.watchlist.css @@ -0,0 +1,15 @@ +/*! + * Styling for elements generated by JavaScript on Special:Watchlist + */ +.mw-changelist-line-inner-unwatched { + text-decoration: line-through; + opacity: 0.5; +} + +span.mw-changeslist-line-prefix { + display: inline-block; +} +/* This can be either a span or a table cell */ +.mw-changeslist-line-prefix { + width: 1.25em; +} diff --git a/resources/src/mediawiki.special/mediawiki.special.watchlist.js b/resources/src/mediawiki.special/mediawiki.special.watchlist.js index 7cc9b9bb5a..535ca93753 100644 --- a/resources/src/mediawiki.special/mediawiki.special.watchlist.js +++ b/resources/src/mediawiki.special/mediawiki.special.watchlist.js @@ -3,7 +3,7 @@ */ ( function ( mw, $, OO ) { $( function () { - var $progressBar, $resetForm = $( '#mw-watchlist-resetbutton' ); + var api = new mw.Api(), $progressBar, $resetForm = $( '#mw-watchlist-resetbutton' ); // If the user wants to reset their watchlist, use an API call to do so (no reload required) // Adapted from a user script by User:NQ of English Wikipedia @@ -19,8 +19,7 @@ if ( !$progressBar ) { $progressBar = new OO.ui.ProgressBarWidget( { progress: false } ).$element; $progressBar.css( { - position: 'absolute', - width: '100%' + position: 'absolute', width: '100%' } ); } // Show progress bar @@ -28,10 +27,8 @@ // Use action=setnotificationtimestamp to mark all as visited, // then set all watchlist lines accordingly - new mw.Api().postWithToken( 'csrf', { - formatversion: 2, - action: 'setnotificationtimestamp', - entirewatchlist: true + api.postWithToken( 'csrf', { + formatversion: 2, action: 'setnotificationtimestamp', entirewatchlist: true } ).done( function () { // Enable button again $button.prop( 'disabled', false ); @@ -56,6 +53,103 @@ $( '#mw-watchlist-form' ).submit(); } ); } + + if ( mw.user.options.get( 'watchlistunwatchlinks' ) ) { + // Watch/unwatch toggle link: + // If a page is on the watchlist, a '×' is shown which, when clicked, removes the page from the watchlist. + // After unwatching a page, the '×' becomes a '+', which if clicked re-watches the page. + // Unwatched page entries are struck through and have lowered opacity. + $( '.mw-unwatch-link, .mw-watch-link' ).click( function ( event ) { + var $unwatchLink = $( this ), // EnhancedChangesList uses for each row, while OldChangesList uses
  • for each row + $watchlistLine = $unwatchLink.closest( 'li, table' ) + .find( '[data-target-page]' ), + pageTitle = $watchlistLine.data( 'targetPage' ), + isTalk = mw.Title.newFromText( pageTitle ).getNamespaceId() % 2 === 1; + + // Utility function for looping through each watchlist line that matches + // a certain page or its associated page (e.g. Talk) + function forEachMatchingTitle( title, callback ) { + + var titleObj = mw.Title.newFromText( title ), + pageNamespaceId = titleObj.getNamespaceId(), + isTalk = pageNamespaceId % 2 === 1, + associatedTitle = mw.Title.makeTitle( isTalk ? pageNamespaceId - 1 : pageNamespaceId + 1, + titleObj.getMainText() ).getPrefixedText(); + $( '.mw-changeslist-line' ).each( function () { + var $this = $( this ), $row, $unwatchLink; + + $this.find( '[data-target-page]' ).each( function () { + var $this = $( this ), rowTitle = $this.data( 'targetPage' ); + if ( rowTitle === title || rowTitle === associatedTitle ) { + + // EnhancedChangesList groups log entries by performer rather than target page. Therefore... + // * If using OldChangesList, use the
  • + // * If using EnhancedChangesList and $this is part of a grouped log entry, use the
  • sub-entry + // * If using EnhancedChangesList and $this is not part of a grouped log entry, use the grouped entry + $row = + $this.closest( + 'li, table.mw-collapsible.mw-changeslist-log td[data-target-page], table' ); + $unwatchLink = $row.find( '.mw-unwatch-link, .mw-watch-link' ); + + callback( rowTitle, $row, $unwatchLink ); + } + } ); + } ); + } + + // Depending on whether we are watching or unwatching, for each entry of the page (and its associated page i.e. Talk), + // change the text, tooltip, and non-JS href of the (un)watch button, and update the styling of the watchlist entry. + if ( $unwatchLink.hasClass( 'mw-unwatch-link' ) ) { + api.unwatch( pageTitle ) + .done( function () { + forEachMatchingTitle( pageTitle, + function ( rowPageTitle, $row, $rowUnwatchLink ) { + $rowUnwatchLink + .text( mw.msg( 'watchlist-unwatch-undo' ) ) + .attr( 'title', mw.msg( 'tooltip-ca-watch' ) ) + .attr( 'href', + mw.util.getUrl( rowPageTitle, { action: 'watch' } ) ) + .removeClass( 'mw-unwatch-link loading' ) + .addClass( 'mw-watch-link' ); + $row.find( + '.mw-changeslist-line-inner, .mw-enhanced-rc-nested' ) + .addBack( '.mw-enhanced-rc-nested' ) // For matching log sub-entry + .addClass( 'mw-changelist-line-inner-unwatched' ); + } ); + + mw.notify( + mw.message( isTalk ? 'removedwatchtext-talk' : 'removedwatchtext', + pageTitle ), { tag: 'watch-self' } ); + } ); + } else { + api.watch( pageTitle ) + .then( function () { + forEachMatchingTitle( pageTitle, + function ( rowPageTitle, $row, $rowUnwatchLink ) { + $rowUnwatchLink + .text( mw.msg( 'watchlist-unwatch' ) ) + .attr( 'title', mw.msg( 'tooltip-ca-unwatch' ) ) + .attr( 'href', + mw.util.getUrl( rowPageTitle, { action: 'unwatch' } ) ) + .removeClass( 'mw-watch-link loading' ) + .addClass( 'mw-unwatch-link' ); + $row.find( '.mw-changelist-line-inner-unwatched' ) + .addBack( '.mw-enhanced-rc-nested' ) + .removeClass( 'mw-changelist-line-inner-unwatched' ); + } ); + + mw.notify( + mw.message( isTalk ? 'addedwatchtext-talk' : 'addedwatchtext', + pageTitle ), { tag: 'watch-self' } ); + } ); + } + + event.preventDefault(); + event.stopPropagation(); + $unwatchLink.blur(); + } ); + } } ); -}( mediaWiki, jQuery, OO ) ); +}( mediaWiki, jQuery, OO ) +); diff --git a/tests/phpunit/includes/changes/EnhancedChangesListTest.php b/tests/phpunit/includes/changes/EnhancedChangesListTest.php index 1290c641df..420fe7493e 100644 --- a/tests/phpunit/includes/changes/EnhancedChangesListTest.php +++ b/tests/phpunit/includes/changes/EnhancedChangesListTest.php @@ -74,6 +74,47 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { $this->assertEquals( '', $html ); } + public function testRecentChangesPrefix() { + $mockContext = $this->getMockBuilder( RequestContext::class ) + ->setMethods( [ 'getTitle' ] ) + ->getMock(); + $mockContext->method( 'getTitle' ) + ->will( $this->returnValue( Title::newFromText( 'Expected Context Title' ) ) ); + + // One group of two lines + $enhancedChangesList = $this->newEnhancedChangesList(); + $enhancedChangesList->setContext( $mockContext ); + $enhancedChangesList->setChangeLinePrefixer( function ( $rc, $changesList ) { + // Make sure RecentChange and ChangesList objects are the same + $this->assertEquals( 'Expected Context Title', $changesList->getContext()->getTitle() ); + $this->assertTrue( $rc->getTitle() == 'Cat' || $rc->getTitle() == 'Dog' ); + return 'Hello world prefix'; + } ); + $enhancedChangesList->beginRecentChangesList(); + + $recentChange = $this->getEditChange( '20131103092153' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + $recentChange = $this->getEditChange( '20131103092154' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + + $html = $enhancedChangesList->endRecentChangesList(); + + $this->assertRegExp( '/Hello world prefix/', $html ); + + // Two separate lines + $enhancedChangesList->beginRecentChangesList(); + + $recentChange = $this->getEditChange( '20131103092153' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + $recentChange = $this->getEditChange( '20131103092154', 'Dog' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + + $html = $enhancedChangesList->endRecentChangesList(); + + preg_match_all( '/Hello world prefix/', $html, $matches ); + $this->assertCount( 2, $matches[0] ); + } + public function testCategorizationLineFormatting() { $html = $this->createCategorizationLine( $this->getCategorizationChange( '20150629191735', 0, 0 ) @@ -112,12 +153,16 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { preg_match_all( '/td class="mw-enhanced-rc-nested"/', $html, $matches ); $this->assertCount( 2, $matches[0] ); + preg_match_all( '/data-target-page="Cat"/', $html, $matches ); + $this->assertCount( 2, $matches[0] ); + $recentChange3 = $this->getLogChange(); $enhancedChangesList->recentChangesLine( $recentChange3, false ); $html = $enhancedChangesList->endRecentChangesList(); $this->assertContains( 'data-mw-logaction="foo/bar"', $html ); $this->assertContains( 'data-mw-logid="25"', $html ); + $this->assertContains( 'data-target-page="Title"', $html ); } /** @@ -133,10 +178,10 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { /** * @return RecentChange */ - private function getEditChange( $timestamp ) { + private function getEditChange( $timestamp, $pageTitle = 'Cat' ) { $user = $this->getMutableTestUser()->getUser(); $recentChange = $this->testRecentChangesHelper->makeEditRecentChange( - $user, 'Cat', 0, 5, 191, $timestamp, 0, 0 + $user, $pageTitle, 0, 5, 191, $timestamp, 0, 0 ); return $recentChange; diff --git a/tests/phpunit/includes/changes/OldChangesListTest.php b/tests/phpunit/includes/changes/OldChangesListTest.php index 244a05decd..91dc731224 100644 --- a/tests/phpunit/includes/changes/OldChangesListTest.php +++ b/tests/phpunit/includes/changes/OldChangesListTest.php @@ -155,6 +155,40 @@ class OldChangesListTest extends MediaWikiLangTestCase { $this->assertRegExp( "/watchlist-0-Cat/", $line ); } + public function testRecentChangesLine_dataAttribute() { + $oldChangesList = $this->getOldChangesList(); + $oldChangesList->setWatchlistDivs( true ); + + $recentChange = $this->getEditChange(); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + $this->assertRegExp( '/data-target-page=\"Cat\"/', $line ); + + $recentChange = $this->getLogChange( 'delete', 'delete' ); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + $this->assertRegExp( '/data-target-page="Abc"/', $line ); + } + + public function testRecentChangesLine_prefix() { + $mockContext = $this->getMockBuilder( RequestContext::class ) + ->setMethods( [ 'getTitle' ] ) + ->getMock(); + $mockContext->method( 'getTitle' ) + ->will( $this->returnValue( Title::newFromText( 'Expected Context Title' ) ) ); + + $oldChangesList = $this->getOldChangesList(); + $oldChangesList->setContext( $mockContext ); + $recentChange = $this->getEditChange(); + + $oldChangesList->setChangeLinePrefixer( function ( $rc, $changesList ) { + // Make sure RecentChange and ChangesList objects are the same + $this->assertEquals( 'Expected Context Title', $changesList->getContext()->getTitle() ); + $this->assertEquals( 'Cat', $rc->getTitle() ); + return 'I am a prefix'; + } ); + $line = $oldChangesList->recentChangesLine( $recentChange ); + $this->assertRegExp( "/I am a prefix/", $line ); + } + private function getNewBotEditChange() { $user = $this->getMutableTestUser()->getUser(); diff --git a/tests/phpunit/includes/specials/SpecialWatchlistTest.php b/tests/phpunit/includes/specials/SpecialWatchlistTest.php index b0490ec663..1c43919955 100644 --- a/tests/phpunit/includes/specials/SpecialWatchlistTest.php +++ b/tests/phpunit/includes/specials/SpecialWatchlistTest.php @@ -41,6 +41,7 @@ class SpecialWatchlistTest extends SpecialPageTestBase { 'watchlisthidepatrolled' => 0, 'watchlisthidecategorization' => 1, 'watchlistreloadautomatically' => 0, + 'watchlistunwatchlinks' => 0, ] ); } -- 2.20.1