Merge "Update psysh to 0.8.5"
[lhc/web/wiklou.git] / resources / src / mediawiki / page / watch.js
1 /**
2 * Animate watch/unwatch links to use asynchronous API requests to
3 * watch pages, rather than navigating to a different URI.
4 *
5 * @class mw.page.watch.ajax
6 */
7 ( function ( mw, $ ) {
8 // The name of the page to watch or unwatch
9 var title = mw.config.get( 'wgRelevantPageName' );
10
11 /**
12 * Update the link text, link href attribute and (if applicable)
13 * "loading" class.
14 *
15 * @param {jQuery} $link Anchor tag of (un)watch link
16 * @param {string} action One of 'watch', 'unwatch'
17 * @param {string} [state="idle"] 'idle' or 'loading'. Default is 'idle'
18 */
19 function updateWatchLink( $link, action, state ) {
20 var msgKey, $li, otherAction;
21
22 // A valid but empty jQuery object shouldn't throw a TypeError
23 if ( !$link.length ) {
24 return;
25 }
26
27 // Invalid actions shouldn't silently turn the page in an unrecoverable state
28 if ( action !== 'watch' && action !== 'unwatch' ) {
29 throw new Error( 'Invalid action' );
30 }
31
32 // message keys 'watch', 'watching', 'unwatch' or 'unwatching'.
33 msgKey = state === 'loading' ? action + 'ing' : action;
34 otherAction = action === 'watch' ? 'unwatch' : 'watch';
35 $li = $link.closest( 'li' );
36
37 // Trigger a 'watchpage' event for this List item.
38 // Announce the otherAction value as the first param.
39 // Used to monitor the state of watch link.
40 // TODO: Revise when system wide hooks are implemented
41 if ( state === undefined ) {
42 $li.trigger( 'watchpage.mw', otherAction );
43 }
44
45 $link
46 .text( mw.msg( msgKey ) )
47 .attr( 'title', mw.msg( 'tooltip-ca-' + action ) )
48 .updateTooltipAccessKeys()
49 .attr( 'href', mw.util.getUrl( title, { action: action } ) );
50
51 // Most common ID style
52 if ( $li.prop( 'id' ) === 'ca-' + otherAction ) {
53 $li.prop( 'id', 'ca-' + action );
54 }
55
56 if ( state === 'loading' ) {
57 $link.addClass( 'loading' );
58 } else {
59 $link.removeClass( 'loading' );
60 }
61 }
62
63 /**
64 * TODO: This should be moved somewhere more accessible.
65 *
66 * @private
67 * @param {string} url
68 * @return {string} The extracted action, defaults to 'view'
69 */
70 function mwUriGetAction( url ) {
71 var action, actionPaths, key, i, m, parts;
72
73 // TODO: Does MediaWiki give action path or query param
74 // precedence? If the former, move this to the bottom
75 action = mw.util.getParamValue( 'action', url );
76 if ( action !== null ) {
77 return action;
78 }
79
80 actionPaths = mw.config.get( 'wgActionPaths' );
81 for ( key in actionPaths ) {
82 if ( actionPaths.hasOwnProperty( key ) ) {
83 parts = actionPaths[ key ].split( '$1' );
84 for ( i = 0; i < parts.length; i++ ) {
85 parts[ i ] = mw.RegExp.escape( parts[ i ] );
86 }
87 m = new RegExp( parts.join( '(.+)' ) ).exec( url );
88 if ( m && m[ 1 ] ) {
89 return key;
90 }
91
92 }
93 }
94
95 return 'view';
96 }
97
98 // Expose public methods
99 mw.page = {
100 watch: {
101 updateWatchLink: updateWatchLink
102 }
103 };
104
105 $( function () {
106 var $links = $( '.mw-watchlink a, a.mw-watchlink' );
107 // Restrict to core interfaces, ignore user-generated content
108 $links = $links.filter( ':not( #bodyContent *, #content * )' );
109
110 $links.click( function ( e ) {
111 var mwTitle, action, api, $link;
112
113 mwTitle = mw.Title.newFromText( title );
114 action = mwUriGetAction( this.href );
115
116 if ( !mwTitle || ( action !== 'watch' && action !== 'unwatch' ) ) {
117 // Let native browsing handle the link
118 return true;
119 }
120 e.preventDefault();
121 e.stopPropagation();
122
123 $link = $( this );
124
125 if ( $link.hasClass( 'loading' ) ) {
126 return;
127 }
128
129 updateWatchLink( $link, action, 'loading' );
130
131 // Preload the notification module for mw.notify
132 mw.loader.load( 'mediawiki.notification' );
133
134 api = new mw.Api();
135
136 api[ action ]( title )
137 .done( function ( watchResponse ) {
138 var message, otherAction = action === 'watch' ? 'unwatch' : 'watch';
139
140 if ( mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) {
141 message = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
142 } else {
143 message = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
144 }
145
146 mw.notify( mw.message( message, mwTitle.getPrefixedText() ).parseDom(), {
147 tag: 'watch-self'
148 } );
149
150 // Set link to opposite
151 updateWatchLink( $link, otherAction );
152
153 // Update the "Watch this page" checkbox on action=edit when the
154 // page is watched or unwatched via the tab (T14395).
155 $( '#wpWatchthis' ).prop( 'checked', watchResponse.watched === true );
156 } )
157 .fail( function () {
158 var msg, link;
159
160 // Reset link to non-loading mode
161 updateWatchLink( $link, action );
162
163 // Format error message
164 link = mw.html.element(
165 'a', {
166 href: mw.util.getUrl( title ),
167 title: mwTitle.getPrefixedText()
168 }, mwTitle.getPrefixedText()
169 );
170 msg = mw.message( 'watcherrortext', link );
171
172 // Report to user about the error
173 mw.notify( msg, {
174 tag: 'watch-self',
175 type: 'error'
176 } );
177 } );
178 } );
179 } );
180
181 }( mediaWiki, jQuery ) );