Implement ability to select and save watch temporarily.

Add functionality that allows users to select a watch period via a
drop-down in the pop-up that shows up when a user watches a page via star.

Update the expiry dropdown when user is in a page edit-form.

Bug: T249262
Change-Id: I9a7dfcaf84be8083e0319789dc95f2d15cee245a
This commit is contained in:
hmonroy 2020-05-27 19:38:36 -07:00
parent 08eb0dbc33
commit 68d81806ca
9 changed files with 160 additions and 23 deletions

View file

@ -117,22 +117,22 @@ class WatchAction extends FormAction {
* @since 1.35
* @todo Move this somewhere better when it's being used in more than just this action.
*
* @param IContextSource $context
* @param MessageLocalizer $msgLocalizer
* @param WatchedItem|bool $watchedItem
*
* @return mixed[] With keys `options` (string[]) and `default` (string).
*/
public static function getExpiryOptions( IContextSource $context, $watchedItem ) {
$expiryOptionsMsg = $context->msg( 'watchlist-expiry-options' )->text();
$expiryOptions = XmlSelect::parseOptionsMessage( $expiryOptionsMsg );
public static function getExpiryOptions( MessageLocalizer $msgLocalizer, $watchedItem ) {
$expiryOptionsMsg = $msgLocalizer->msg( 'watchlist-expiry-options' );
$expiryOptionsMsgText = $expiryOptionsMsg->text();
$expiryOptions = XmlSelect::parseOptionsMessage( $expiryOptionsMsgText );
$default = 'infinite';
if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() ) {
// If it's already being temporarily watched,
// add the existing expiry as the default option in the dropdown.
$expiry = MWTimestamp::getInstance( $watchedItem->getExpiry() );
$diffInDays = $watchedItem->getExpiryInDays();
$daysLeft = $context->msg( 'watchlist-expiry-days-left', [ $diffInDays ] )->text();
$daysLeft = $msgLocalizer->msg( 'watchlist-expiry-days-left', [ $diffInDays ] )->text();
$expiryOptions = array_merge(
[ $daysLeft => $expiry->getTimestamp( TS_MW ) ],
$expiryOptions

View file

@ -2383,6 +2383,11 @@
"addedwatchtext": "\"[[:$1]]\" and its discussion page have been added to your [[Special:Watchlist|watchlist]].",
"addedwatchtext-talk": "\"[[:$1]]\" and its associated page have been added to your [[Special:Watchlist|watchlist]].",
"addedwatchtext-short": "The page \"$1\" has been added to your watchlist.",
"addedwatchexpiry-options-label": "Watchlist time period:",
"addedwatchexpirytext": "\"[[:$1]]\" and its discussion page have been added to your [[Special:Watchlist|watchlist]] for $2.",
"addedwatchexpirytext-talk": "\"[[:$1]]\" and its associated page have been added to your [[Special:Watchlist|watchlist]] for $2.",
"addedwatchindefinitelytext": "\"[[:$1]]\" and its discussion page have been added to your [[Special:Watchlist|watchlist]] permanently.",
"addedwatchindefinitelytext-talk": "\"[[:$1]]\" and its associated page have been added to your [[Special:Watchlist|watchlist]] permanently.",
"removewatch": "Remove from watchlist",
"removedwatchtext": "\"[[:$1]]\" and its discussion page have been removed from your [[Special:Watchlist|watchlist]].",
"removedwatchtext-talk": "\"[[:$1]]\" and its associated page have been removed from your [[Special:Watchlist|watchlist]].",

View file

@ -2599,6 +2599,11 @@
"addedwatchtext": "Message shown after clicking on the {{msg-mw|Watch}} tab in a content namespace page. Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Removedwatchtext}}\n* {{msg-mw|Addedwatchtext-talk}}",
"addedwatchtext-talk": "Message shown after clicking on the {{msg-mw|Watch}} tab in a talk namespace page. Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Removedwatchtext-talk}}\n* {{msg-mw|Addedwatchtext}}",
"addedwatchtext-short": "Explanation shown when watching item from [[Special:UnwatchedPages]].\n\nSee also:\n* {{msg-mw|Removedwatchtext-short}}\n* {{msg-mw|Addedwatchtext}}",
"addedwatchexpiry-options-label": "Text for watchlist expiry dropdown shown after clicking on the {{msg-mw|Watch}} tab in a content namespace page.",
"addedwatchexpirytext": "Message shown after selecting to watch a page temporarily. A selection is made from the watchlist expiry dropdown options in the pop up. Parameters:\n* $1 - page title\n* $2 - the expiry selected",
"addedwatchexpirytext-talk": "Message shown after selecting to watch a talk page temporarily. A selection is made from the watchlist expiry dropdown options in the pop up. Parameters:\n* $1 - page title\n* $2 - the expiry selected",
"addedwatchindefinitelytext": "Message shown after selecting to watch a page indefinitely. The selection is made from the watchlist expiry dropdown options in the pop up. Parameters:\n* $1 - page title",
"addedwatchindefinitelytext-talk": "Message shown after selecting to watch a talk page indefinitely. The selection is made from the watchlist expiry dropdown options in the pop up. Parameters:\n* $1 - page title",
"removewatch": "A confirmation message that is shown after a user clicks the watchlist star icon if the JavaScript does not or cannot load, or if they visit the \"action=\" link directly, e.g. https://translatewiki.net/w/i.php?title=MediaWiki:Removewatch/qqq&action=unwatch\n\nSee also:\n* {{msg-mw|Addwatch}}\n* {{msg-mw|Confirm-unwatch-top}}\n* {{msg-mw|Confirm-unwatch-button}}",
"removedwatchtext": "Message shown after clicking on the {{msg-mw|Unwatch}} tab in a content namespace page. Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Addedwatchtext}}\n* {{msg-mw|Removedwatchtext-talk}}",
"removedwatchtext-talk": "Message shown after clicking on the {{msg-mw|Unwatch}} tab in a talk namespace page. Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Addedwatchtext-talk}}\n* {{msg-mw|Removedwatchtext}}",

View file

@ -2682,12 +2682,25 @@ return [
],
],
'mediawiki.watchstar.widgets' => [
'localBasePath' => "$IP/resources/src/mediawiki.watchstar.widgets",
'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.watchstar.widgets",
'packageFiles' => [
'resources/src/mediawiki.watchstar.widgets/WatchlistExpiryWidget.js',
'WatchlistExpiryWidget.js',
[ 'name' => 'data.json', 'callback' => function ( MessageLocalizer $messageLocalizer ) {
return WatchAction::getExpiryOptions( $messageLocalizer, false );
} ]
],
'styles' => 'WatchlistExpiryWidget.css',
'dependencies' => [
'oojs-ui'
],
'messages' => [
'addedwatchexpiry-options-label',
'addedwatchexpirytext',
'addedwatchexpirytext-talk',
'addedwatchindefinitelytext',
'addedwatchindefinitelytext-talk'
],
'targets' => [ 'desktop', 'mobile' ],
],

View file

@ -18,6 +18,11 @@
// Change state on every change of the watchthis checkbox.
watchThisWidget.on( 'change', function ( enabled ) {
watchlistExpiryWidget.setDisabled( !enabled );
// Reset the watchlist-expiry dropdown to the 'infinite' value
if ( watchlistExpiryWidget.isDisabled() ) {
watchlistExpiryWidget.setValue( 'infinite' );
}
} );
} );

View file

@ -47,9 +47,11 @@
* Convenience method for `action=watch`.
*
* @inheritdoc #doWatchInternal
* @since 1.35 - expiry parameter can be passed when
* Watchlist Expiry is enabled
*/
watch: function ( pages ) {
return doWatchInternal.call( this, pages );
watch: function ( pages, expiry ) {
return doWatchInternal.call( this, pages, { expiry: expiry } );
},
/**

View file

@ -16,7 +16,8 @@
*/
( function () {
// The name of the page to watch or unwatch
var pageTitle = mw.config.get( 'wgRelevantPageName' );
var pageTitle = mw.config.get( 'wgRelevantPageName' ),
isWatchlistExpiryEnabled = require( './config.json' ).WatchlistExpiry;
/**
* Update the link text, link href attribute and (if applicable)
@ -183,7 +184,6 @@
.done( function ( watchResponse ) {
var message,
watchlistPopup = null,
isWatchlistExpiryEnabled = require( './config.json' ).WatchlistExpiry,
otherAction = action === 'watch' ? 'unwatch' : 'watch';
if ( mwTitle.isTalkPage() ) {
@ -195,22 +195,33 @@
// @since 1.35 - pop up notification will be loaded with OOUI
// only if Watchlist Expiry is enabled
if ( isWatchlistExpiryEnabled ) {
if ( action === 'watch' ) { // The message should include `infinite` watch period
message = mwTitle.isTalkPage() ? 'addedwatchindefinitelytext-talk' : 'addedwatchindefinitelytext';
}
mw.loader.using( 'mediawiki.watchstar.widgets' ).done( function ( require ) {
var WatchlistExpiryWidget = require( 'mediawiki.watchstar.widgets' );
if ( !watchlistPopup ) {
watchlistPopup = new WatchlistExpiryWidget( {
// The following messages can be used here:
// * addedwatchtext-talk
// * addedwatchtext
// * removedwatchtext-talk
// * removedwatchtext
message: mw.message( message, mwTitle.getPrefixedText() ).parseDom()
} );
watchlistPopup = new WatchlistExpiryWidget(
action,
title,
{
// The following messages can be used here:
// * addedwatchindefinitelytext-talk
// * addedwatchindefinitelytext
// * removedwatchtext-talk
// * removedwatchtext
message: mw.message( message, mwTitle.getPrefixedText() ).parseDom(),
$link: $link,
$li: $link.closest( 'li' )
} );
}
mw.notify( watchlistPopup.$element, {
tag: 'watch-self',
autoHideSeconds: 'long',
autoHide: true
} );

View file

@ -0,0 +1,3 @@
.mw-WatchlistExpiryWidgetwatchlist-dropdown-label {
margin-top: 0.8em;
}

View file

@ -1,23 +1,116 @@
/* eslint-disable no-implicit-globals */
/**
* A special widget that displays a message that a page is being watched/unwatched
* with a selection widget that can determine how long the page will be watched
* with a selection widget that can determine how long the page will be watched.
* If a page is being watched then a dropdown with expiry options is included.
*
* @class
* @extends OO.ui.Widget
* @param {string} action One of 'watch', 'unwatch'
* @param {string} pageTitle Title of page that this widget will watch or unwatch
* @param {Object} config Configuration object
*/
var WatchlistExpiryWidget = function ( config ) {
var WatchlistExpiryWidget = function ( action, pageTitle, config ) {
var dataExpiryOptions = require( './data.json' ).options,
messageLabel, dropdownLabel,
expiryDropdown, onDropdownChange, api, $link, $li,
expiryOptions = [];
config = config || {};
$link = config.$link;
$li = config.$li;
WatchlistExpiryWidget.parent.call( this, config );
this.message = new OO.ui.LabelWidget( {
messageLabel = new OO.ui.LabelWidget( {
label: config.message
} );
this.$element
.addClass( 'mw-watchstar-WatchlistExpiryWidget' )
.append( this.message.$element );
.append( messageLabel.$element );
if ( action === 'watch' ) {
Object.keys( dataExpiryOptions ).forEach( function ( key ) {
expiryOptions.push( { data: dataExpiryOptions[ key ], label: key } );
} );
dropdownLabel = new OO.ui.LabelWidget( {
label: mw.message( 'addedwatchexpiry-options-label' ).parseDom(),
classes: [ 'mw-WatchlistExpiryWidgetwatchlist-dropdown-label' ]
} );
expiryDropdown = new OO.ui.DropdownInputWidget( {
options: expiryOptions,
classes: [ 'mw-watchexpiry' ]
} );
onDropdownChange = function ( value ) {
var notif = mw.notification;
if ( typeof $link !== 'undefined' ) {
$link.addClass( 'loading' )
.text( mw.msg( 'watching' ) );
}
// Pause the mw.notify so that we can wait for watch request to finish
notif.pause();
api = new mw.Api();
api.watch( pageTitle, value )
.done( function () {
var message,
mwTitle = mw.Title.newFromText( pageTitle );
if ( mwTitle.isTalkPage() ) {
message = value === 'infinite' ? 'addedwatchindefinitelytext-talk' : 'addedwatchexpirytext-talk';
} else {
message = value === 'infinite' ? 'addedwatchindefinitelytext' : 'addedwatchexpirytext';
}
// The following messages can be used here:
// * addedwatchindefinitelytext-talk
// * addedwatchindefinitelytext
// * addedwatchexpirytext-talk
// * addedwatchexpirytext
messageLabel.setLabel(
mw.message( message, mwTitle.getPrefixedText(), value ).parseDom()
);
// Resume the mw.notify once the label has been updated
notif.resume();
if ( typeof $link !== 'undefined' ) {
$link.removeClass( 'loading' );
}
if ( typeof $li !== 'undefined' ) {
if ( value === 'infinite' ) {
$li.removeClass( 'mw-watchlink-temp' );
} else {
$li.addClass( 'mw-watchlink-temp' );
}
$link.text( mw.msg( 'unwatch' ) );
}
// Update the "Watch this page" checkbox on action=edit when the
// page is watched or unwatched via the tab.
if ( document.getElementById( 'wpWatchlistExpiryWidget' ) ) {
OO.ui.infuse( '#wpWatchlistExpiryWidget' ).setValue( value );
}
} )
.fail( function ( code, data ) {
// Format error message
var $msg = api.getErrorMessage( data );
// Report to user about the error
mw.notify( $msg, {
tag: 'watch-self',
type: 'error'
} );
// Resume the mw.notify once the error has been reported
notif.resume();
} );
};
expiryDropdown.on( 'change', onDropdownChange );
this.$element.append( dropdownLabel.$element, expiryDropdown.$element );
}
};
OO.inheritClass( WatchlistExpiryWidget, OO.ui.Widget );