diff options
Diffstat (limited to 'www/wiki/resources/src/mediawiki/page/watch.js')
-rw-r--r-- | www/wiki/resources/src/mediawiki/page/watch.js | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki/page/watch.js b/www/wiki/resources/src/mediawiki/page/watch.js new file mode 100644 index 00000000..5b41876d --- /dev/null +++ b/www/wiki/resources/src/mediawiki/page/watch.js @@ -0,0 +1,193 @@ +/** + * Animate watch/unwatch links to use asynchronous API requests to + * watch pages, rather than navigating to a different URI. + * + * Usage: + * + * var watch = require( 'mediawiki.page.watch.ajax' ); + * watch.updateWatchLink( + * $node, + * 'watch', + * 'loading' + * ); + * + * @class mw.plugin.page.watch.ajax + * @singleton + */ +( function ( mw, $ ) { + var watch, + // The name of the page to watch or unwatch + title = mw.config.get( 'wgRelevantPageName' ); + + /** + * Update the link text, link href attribute and (if applicable) + * "loading" class. + * + * @param {jQuery} $link Anchor tag of (un)watch link + * @param {string} action One of 'watch', 'unwatch' + * @param {string} [state="idle"] 'idle' or 'loading'. Default is 'idle' + */ + function updateWatchLink( $link, action, state ) { + var msgKey, $li, otherAction; + + // A valid but empty jQuery object shouldn't throw a TypeError + if ( !$link.length ) { + return; + } + + // Invalid actions shouldn't silently turn the page in an unrecoverable state + if ( action !== 'watch' && action !== 'unwatch' ) { + throw new Error( 'Invalid action' ); + } + + // message keys 'watch', 'watching', 'unwatch' or 'unwatching'. + msgKey = state === 'loading' ? action + 'ing' : action; + otherAction = action === 'watch' ? 'unwatch' : 'watch'; + $li = $link.closest( 'li' ); + + // Trigger a 'watchpage' event for this List item. + // Announce the otherAction value as the first param. + // Used to monitor the state of watch link. + // TODO: Revise when system wide hooks are implemented + if ( state === undefined ) { + $li.trigger( 'watchpage.mw', otherAction ); + } + + $link + .text( mw.msg( msgKey ) ) + .attr( 'title', mw.msg( 'tooltip-ca-' + action ) ) + .updateTooltipAccessKeys() + .attr( 'href', mw.util.getUrl( title, { action: action } ) ); + + // Most common ID style + if ( $li.prop( 'id' ) === 'ca-' + otherAction ) { + $li.prop( 'id', 'ca-' + action ); + } + + if ( state === 'loading' ) { + $link.addClass( 'loading' ); + } else { + $link.removeClass( 'loading' ); + } + } + + /** + * TODO: This should be moved somewhere more accessible. + * + * @private + * @param {string} url + * @return {string} The extracted action, defaults to 'view' + */ + function mwUriGetAction( url ) { + var action, actionPaths, key, m, parts; + + // TODO: Does MediaWiki give action path or query param + // precedence? If the former, move this to the bottom + action = mw.util.getParamValue( 'action', url ); + if ( action !== null ) { + return action; + } + + actionPaths = mw.config.get( 'wgActionPaths' ); + for ( key in actionPaths ) { + if ( actionPaths.hasOwnProperty( key ) ) { + parts = actionPaths[ key ].split( '$1' ); + parts = parts.map( mw.RegExp.escape ); + m = new RegExp( parts.join( '(.+)' ) ).exec( url ); + if ( m && m[ 1 ] ) { + return key; + } + + } + } + + return 'view'; + } + + // Expose public methods + watch = { + updateWatchLink: updateWatchLink + }; + module.exports = watch; + + $( function () { + var $links = $( '.mw-watchlink a[data-mw="interface"], a.mw-watchlink[data-mw="interface"]' ); + if ( !$links.length ) { + // Fallback to the class-based exclusion method for backwards-compatibility + $links = $( '.mw-watchlink a, a.mw-watchlink' ); + // Restrict to core interfaces, ignore user-generated content + $links = $links.filter( ':not( #bodyContent *, #content * )' ); + } + + $links.click( function ( e ) { + var mwTitle, action, api, $link; + + mwTitle = mw.Title.newFromText( title ); + action = mwUriGetAction( this.href ); + + if ( !mwTitle || ( action !== 'watch' && action !== 'unwatch' ) ) { + // Let native browsing handle the link + return true; + } + e.preventDefault(); + e.stopPropagation(); + + $link = $( this ); + + if ( $link.hasClass( 'loading' ) ) { + return; + } + + updateWatchLink( $link, action, 'loading' ); + + // Preload the notification module for mw.notify + mw.loader.load( 'mediawiki.notification' ); + + api = new mw.Api(); + + api[ action ]( title ) + .done( function ( watchResponse ) { + var message, otherAction = action === 'watch' ? 'unwatch' : 'watch'; + + if ( mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { + message = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; + } else { + message = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; + } + + mw.notify( mw.message( message, mwTitle.getPrefixedText() ).parseDom(), { + tag: 'watch-self' + } ); + + // Set link to opposite + updateWatchLink( $link, otherAction ); + + // Update the "Watch this page" checkbox on action=edit when the + // page is watched or unwatched via the tab (T14395). + $( '#wpWatchthis' ).prop( 'checked', watchResponse.watched === true ); + } ) + .fail( function () { + var msg, link; + + // Reset link to non-loading mode + updateWatchLink( $link, action ); + + // Format error message + link = mw.html.element( + 'a', { + href: mw.util.getUrl( title ), + title: mwTitle.getPrefixedText() + }, mwTitle.getPrefixedText() + ); + msg = mw.message( 'watcherrortext', link ); + + // Report to user about the error + mw.notify( msg, { + tag: 'watch-self', + type: 'error' + } ); + } ); + } ); + } ); + +}( mediaWiki, jQuery ) ); |