r65570 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r65569‎ | r65570 | r65571 >
Date:15:09, 27 April 2010
Author:happy-melon
Status:ok (Comments)
Tags:
Comment:
Rewrite ajaxwatch.js to use the API watch action, and JQuery. Allows it to be used to asynchronise any watch links, not just the watch tab/links/spinny-starry-thing. Will make it nice and easy to add unwatch links to Special:Watchlist, etc.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/AjaxFunctions.php (modified) (history)
  • /trunk/phase3/includes/OutputPage.php (modified) (history)
  • /trunk/phase3/includes/Setup.php (modified) (history)
  • /trunk/phase3/includes/api/ApiWatch.php (modified) (history)
  • /trunk/phase3/skins/common/ajaxwatch.js (modified) (history)

Diff [purge]

Index: trunk/phase3/skins/common/ajaxwatch.js
@@ -1,13 +1,10 @@
2 -// dependencies:
3 -// * ajax.js:
4 - /*extern sajax_init_object, sajax_do_call */
5 -// * wikibits.js:
6 - /*extern changeText, hookEvent, jsMsg */
 2+/**
 3+ * Animate watch/unwatch links to use asynchronous API requests to
 4+ * watch pages, rather than clicking on links. Requires JQuery.
 5+ * Uses jsMsg() from wikibits.js.
 6+ */
77
8 -// These should have been initialized in the generated js
9 -/*extern wgAjaxWatch, wgPageName */
10 -
11 -if(typeof wgAjaxWatch === "undefined" || !wgAjaxWatch) {
 8+if( typeof wgAjaxWatch === "undefined" || !wgAjaxWatch ) {
129 var wgAjaxWatch = {
1310 watchMsg: "Watch",
1411 unwatchMsg: "Unwatch",
@@ -18,163 +15,114 @@
1916 };
2017 }
2118
22 -wgAjaxWatch.supported = true; // supported on current page and by browser
23 -wgAjaxWatch.watching = false; // currently watching page
24 -wgAjaxWatch.inprogress = false; // ajax request in progress
25 -wgAjaxWatch.timeoutID = null; // see wgAjaxWatch.ajaxCall
26 -wgAjaxWatch.watchLinks = []; // "watch"/"unwatch" links
27 -wgAjaxWatch.iconMode = false; // new icon driven functionality
28 -wgAjaxWatch.imgBasePath = ""; // base img path derived from icons on load
29 -
30 -wgAjaxWatch.setLinkText = function( newText ) {
31 - if( wgAjaxWatch.iconMode ) {
32 - for ( i = 0; i < wgAjaxWatch.watchLinks.length; i++ ) {
33 - wgAjaxWatch.watchLinks[i].firstChild.alt = newText;
34 - if ( newText == wgAjaxWatch.watchingMsg || newText == wgAjaxWatch.unwatchingMsg ) {
35 - wgAjaxWatch.watchLinks[i].className += ' loading';
36 - } else if ( newText == wgAjaxWatch.watchMsg || newText == wgAjaxWatch.unwatchMsg ) {
37 - wgAjaxWatch.watchLinks[i].className =
38 - wgAjaxWatch.watchLinks[i].className.replace( /loading/i, '' );
39 - // update the title text on the link
40 - var keyCommand = wgAjaxWatch.watchLinks[i].title.match( /\[.*?\]$/ ) ?
41 - wgAjaxWatch.watchLinks[i].title.match( /\[.*?\]$/ )[0] : "";
42 - wgAjaxWatch.watchLinks[i].title = ( newText == wgAjaxWatch.watchMsg ?
43 - wgAjaxWatch['tooltip-ca-watchMsg'] : wgAjaxWatch['tooltip-ca-unwatchMsg'] )
44 - + " " + keyCommand;
45 - }
 19+wgAjaxWatch.setLinkText = function( $link, action ) {
 20+ if( action == 'watch' || action == 'unwatch' ){
 21+ // save the accesskey from the title
 22+ var keyCommand = $link.attr('title').match( /\[.*?\]$/ )
 23+ ? $link.attr('title').match( /\[.*?\]$/ )[0]
 24+ : '';
 25+ $link.attr( 'title', wgAjaxWatch['tooltip-ca-'+action+'Msg']+' '+keyCommand );
 26+ }
 27+ if( $link.data('icon') ) {
 28+ $link.attr( 'alt', wgAjaxWatch[action+'Msg'] );
 29+ if ( action == 'watching' || action == 'unwatching' ) {
 30+ $link.addClass( 'loading' );
 31+ } else {
 32+ $link.removeClass( 'loading' );
4633 }
4734 } else {
48 - for ( i = 0; i < wgAjaxWatch.watchLinks.length; i++ ) {
49 - changeText( wgAjaxWatch.watchLinks[i], newText );
50 - }
 35+ $link.html( wgAjaxWatch[action+'Msg'] );
5136 }
5237 };
5338
54 -wgAjaxWatch.setLinkID = function( newId ) {
55 - // We can only set the first one
56 - wgAjaxWatch.watchLinks[0].parentNode.setAttribute( 'id', newId );
57 -};
58 -
59 -wgAjaxWatch.setHref = function( string ) {
60 - for( i = 0; i < wgAjaxWatch.watchLinks.length; i++ ) {
61 - if( string == 'watch' ) {
62 - wgAjaxWatch.watchLinks[i].href = wgAjaxWatch.watchLinks[i].href
63 - .replace( /&action=unwatch/, '&action=watch' );
64 - } else if( string == 'unwatch' ) {
65 - wgAjaxWatch.watchLinks[i].href = wgAjaxWatch.watchLinks[i].href
66 - .replace( /&action=watch/, '&action=unwatch' );
67 - }
68 - }
69 -}
70 -
71 -wgAjaxWatch.ajaxCall = function() {
72 - if(!wgAjaxWatch.supported) {
73 - return true;
74 - } else if (wgAjaxWatch.inprogress) {
75 - return false;
76 - }
77 - if(!wfSupportsAjax()) {
78 - // Lazy initialization so we don't toss up
79 - // ActiveX warnings on initial page load
80 - // for IE 6 users with security settings.
81 - wgAjaxWatch.supported = false;
82 - return true;
83 - }
84 -
85 - wgAjaxWatch.inprogress = true;
86 - wgAjaxWatch.setLinkText( wgAjaxWatch.watching
87 - ? wgAjaxWatch.unwatchingMsg : wgAjaxWatch.watchingMsg);
88 - sajax_do_call(
89 - "wfAjaxWatch",
90 - [wgPageName, (wgAjaxWatch.watching ? "u" : "w")],
91 - wgAjaxWatch.processResult
92 - );
93 - // if the request isn't done in 10 seconds, allow user to try again
94 - wgAjaxWatch.timeoutID = window.setTimeout(
95 - function() { wgAjaxWatch.inprogress = false; },
96 - 10000
97 - );
98 - return false;
99 -};
100 -
101 -wgAjaxWatch.processResult = function(request) {
102 - if(!wgAjaxWatch.supported) {
 39+wgAjaxWatch.processResult = function( response ) {
 40+ response = response.watch;
 41+ var $link = $j(this);
 42+ // To ensure we set the same status for all watch links with the
 43+ // same target we trigger a custom event on *all* watch links.
 44+ if( response.watched !== undefined ){
 45+ wgAjaxWatch.$links.trigger( 'mw-ajaxwatch', [response.title, 'watch'] );
 46+ } else if ( response.unwatched !== undefined ){
 47+ wgAjaxWatch.$links.trigger( 'mw-ajaxwatch', [response.title, 'unwatch'] );
 48+ } else {
 49+ // Either we got an error code or it just plain broke.
 50+ window.location.href = $link.attr('href');
10351 return;
10452 }
105 - var response = request.responseText;
106 - if( response.match(/^<w#>/) ) {
107 - wgAjaxWatch.watching = true;
108 - wgAjaxWatch.setLinkText(wgAjaxWatch.unwatchMsg);
109 - wgAjaxWatch.setLinkID("ca-unwatch");
110 - wgAjaxWatch.setHref( 'unwatch' );
111 - } else if( response.match(/^<u#>/) ) {
112 - wgAjaxWatch.watching = false;
113 - wgAjaxWatch.setLinkText(wgAjaxWatch.watchMsg);
114 - wgAjaxWatch.setLinkID("ca-watch");
115 - wgAjaxWatch.setHref( 'watch' );
 53+
 54+ jsMsg( response.message, 'watch' );
 55+
 56+ // Bug 12395 - update the watch checkbox on edit pages when the
 57+ // page is watched or unwatched via the tab.
 58+ if( response.watched !== undefined ){
 59+ $j("#wpWatchthis").attr( 'checked', '1' );
11660 } else {
117 - // Either we got a <err#> error code or it just plain broke.
118 - window.location.href = wgAjaxWatch.watchLinks[0].href;
119 - return;
 61+ $j("#wpWatchthis").removeAttr( 'checked' );
12062 }
121 - jsMsg( response.substr(4), 'watch' );
122 - wgAjaxWatch.inprogress = false;
123 - if(wgAjaxWatch.timeoutID) {
124 - window.clearTimeout(wgAjaxWatch.timeoutID);
125 - }
126 - // Bug 12395 - avoid some watch link confusion on edit
127 - var watchthis = document.getElementById("wpWatchthis");
128 - if( watchthis && response.match(/^<[uw]#>/) ) {
129 - watchthis.checked = response.match(/^<w#>/) ? "checked" : "";
130 - }
131 - return;
13263 };
13364
134 -wgAjaxWatch.onLoad = function() {
135 - // This document structure hardcoding sucks. We should make a class and
136 - // toss all this out the window.
 65+$j(document).ready( function(){
 66+ var $links = $j( '.mw-watchlink a, a.mw-watchlink' );
 67+ //BC with older skins
 68+ $links = $links
 69+ .add( $j( '#ca-watch a, #ca-unwatch a, a#mw-unwatch-link1' ) )
 70+ .add( $j( 'a#mw-unwatch-link2, a#mw-watch-link2, a#mw-watch-link1' ) );
 71+ // allowing people to add inline animated links is a little scary
 72+ $links = $links.filter( ':not( #bodyContent *, #content * )' );
13773
138 - var el1 = document.getElementById("ca-unwatch");
139 - var el2 = null;
140 - if ( !el1 ) {
141 - el1 = document.getElementById("mw-unwatch-link1");
142 - el2 = document.getElementById("mw-unwatch-link2");
143 - }
144 - if( el1 ) {
145 - wgAjaxWatch.watching = true;
146 - } else {
147 - wgAjaxWatch.watching = false;
148 - el1 = document.getElementById("ca-watch");
149 - if ( !el1 ) {
150 - el1 = document.getElementById("mw-watch-link1");
151 - el2 = document.getElementById("mw-watch-link2");
152 - }
153 - if( !el1 ) {
154 - wgAjaxWatch.supported = false;
155 - return;
156 - }
157 - }
 74+ $links.each( function(){
 75+ var $link = $j(this);
 76+ $link
 77+ .data( 'icon', $link.parent().hasClass( 'icon' ) )
 78+ .data( 'action', $link.attr( 'href' ).match( /[\?\&]action=unwatch/i ) ? 'unwatch' : 'watch' );
 79+ var title = $link.attr( 'href' ).match( /[\?\&]title=(.*?)&/i )[1];
 80+ $link.data( 'target', decodeURIComponent( title ).replace( /_/g, ' ' ) );
15881
159 - // Detect if the watch/unwatch feature is in icon mode
160 - if ( el1.className.match( /icon/i ) ) {
161 - wgAjaxWatch.iconMode = true;
162 - }
 82+ });
16383
164 - // The id can be either for the parent (Monobook-based) or the element
165 - // itself (non-Monobook)
166 - wgAjaxWatch.watchLinks.push( el1.tagName.toLowerCase() == "a"
167 - ? el1 : el1.firstChild );
 84+ $links.click( function(event){
 85+ var $link = $j(this);
16886
169 - if( el2 ) {
170 - wgAjaxWatch.watchLinks.push( el2 );
171 - }
 87+ if( wgAjaxWatch.supported === false || !wfSupportsAjax() ) {
 88+ // Lazy initialization so we don't toss up
 89+ // ActiveX warnings on initial page load
 90+ // for IE 6 users with security settings.
 91+ wgAjaxWatch.$links.unbind( 'click' );
 92+ return true;
 93+ }
 94+
 95+ wgAjaxWatch.setLinkText( $link, $link.data('action')+'ing' );
 96+ $j.get( wgScriptPath
 97+ + '/api.php?action=watch&format=json&title='
 98+ + $link.data('target')
 99+ + ( $link.data('action')=='unwatch' ? '&unwatch' : '' ),
 100+ {},
 101+ wgAjaxWatch.processResult,
 102+ 'json'
 103+ );
172104
173 - // I couldn't get for (watchLink in wgAjaxWatch.watchLinks) to work, if
174 - // you can be my guest.
175 - for( i = 0; i < wgAjaxWatch.watchLinks.length; i++ ) {
176 - wgAjaxWatch.watchLinks[i].onclick = wgAjaxWatch.ajaxCall;
177 - }
178 - return;
179 -};
180 -
181 -hookEvent("load", wgAjaxWatch.onLoad);
 105+ return false;
 106+ });
 107+
 108+ // When a request returns, a custom event 'mw-ajaxwatch' is triggered
 109+ // on *all* watch links, so they can be updated if necessary
 110+ $links.bind( 'mw-ajaxwatch', function( event, target, action ){
 111+ var $link = $j(this);
 112+ var foo = $link.data( 'target' );
 113+ if( $link.data( 'target' ) == target ){
 114+ var otheraction = action == 'watch'
 115+ ? 'unwatch'
 116+ : 'watch';
 117+
 118+ $link.data( 'action', otheraction );
 119+ wgAjaxWatch.setLinkText( $link, otheraction );
 120+ $link.attr( 'href', $link.attr('href').replace( '/&action='+action+'/', '&action='+otheraction ) );
 121+ if( $link.parent().attr('id') == 'ca-'+action ){
 122+ $link.parent().attr( 'id', 'ca-'+otheraction );
 123+ }
 124+ };
 125+ return false;
 126+ });
 127+
 128+ wgAjaxWatch.$links = $links;
 129+});
Index: trunk/phase3/includes/Setup.php
@@ -345,7 +345,6 @@
346346 $wgDeferredUpdateList = array();
347347 $wgPostCommitUpdateList = array();
348348
349 -if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
350349 if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning';
351350
352351 # Placeholders in case of DB error
Index: trunk/phase3/includes/OutputPage.php
@@ -1518,6 +1518,7 @@
15191519 wfRunHooks( 'AjaxAddScript', array( &$this ) );
15201520
15211521 if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
 1522+ $this->includeJQuery();
15221523 $this->addScriptFile( 'ajaxwatch.js' );
15231524 }
15241525
Index: trunk/phase3/includes/api/ApiWatch.php
@@ -57,9 +57,11 @@
5858
5959 if ( $params['unwatch'] ) {
6060 $res['unwatched'] = '';
 61+ $res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
6162 $success = $article->doUnwatch();
6263 } else {
6364 $res['watched'] = '';
 65+ $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
6466 $success = $article->doWatch();
6567 }
6668 if ( !$success ) {
Index: trunk/phase3/includes/AjaxFunctions.php
@@ -74,60 +74,6 @@
7575 }
7676
7777 /**
78 - * Called for AJAX watch/unwatch requests.
79 - * @param $pagename Prefixed title string for page to watch/unwatch
80 - * @param $watch String 'w' to watch, 'u' to unwatch
81 - * @return String '<w#>' or '<u#>' on successful watch or unwatch,
82 - * respectively, followed by an HTML message to display in the alert box; or
83 - * '<err#>' on error
84 - */
85 -function wfAjaxWatch( $pagename = "", $watch = "" ) {
86 - if ( wfReadOnly() ) {
87 - // redirect to action=(un)watch, which will display the database lock
88 - // message
89 - return '<err#>';
90 - }
91 -
92 - if ( 'w' !== $watch && 'u' !== $watch ) {
93 - return '<err#>';
94 - }
95 - $watch = 'w' === $watch;
96 -
97 - $title = Title::newFromDBkey( $pagename );
98 - if ( !$title ) {
99 - // Invalid title
100 - return '<err#>';
101 - }
102 - $article = new Article( $title );
103 - $watching = $title->userIsWatching();
104 -
105 - if ( $watch ) {
106 - if ( !$watching ) {
107 - $dbw = wfGetDB( DB_MASTER );
108 - $dbw->begin();
109 - $ok = $article->doWatch();
110 - $dbw->commit();
111 - }
112 - } else {
113 - if ( $watching ) {
114 - $dbw = wfGetDB( DB_MASTER );
115 - $dbw->begin();
116 - $ok = $article->doUnwatch();
117 - $dbw->commit();
118 - }
119 - }
120 - // Something stopped the change
121 - if ( isset( $ok ) && !$ok ) {
122 - return '<err#>';
123 - }
124 - if ( $watch ) {
125 - return '<w#>' . wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
126 - } else {
127 - return '<u#>' . wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
128 - }
129 -}
130 -
131 -/**
13278 * Called in some places (currently just extensions)
13379 * to get the thumbnail URL for a given file at a given resolution.
13480 */
Index: trunk/phase3/RELEASE-NOTES
@@ -58,6 +58,8 @@
5959 * (bug 22858) $wgLocalStylePath is by default set to the same value as
6060 $wgStylePath but should never point to a different domain than the site is
6161 on, allowing skins to use .htc files which are not cross-domain friendly.
 62+* ajaxwatch now uses the API and JQuery, and can be used to animate arbitrary
 63+ watch links, not just to watch the page the link is on.
6264
6365 === Bug fixes in 1.17 ===
6466 * (bug 17560) Half-broken deletion moved image files to deletion archive

Follow-up revisions

RevisionCommit summaryAuthorDate
r65614Follow-up per r65570 CR, also check wgEnableWriteAPI.happy-melon12:27, 28 April 2010

Comments

#Comment by Bryan (talk | contribs)   08:53, 28 April 2010

So does monobook include JQuery in 1.17?

#Comment by Happy-melon (talk | contribs)   11:16, 28 April 2010

Any and all skins include JQuery when it's needed; calling $wgOut->includeJQuery() prompts the script to be included exactly once.

#Comment by Bryan (talk | contribs)   09:10, 28 April 2010

$link.data('target') should be urlencoded. wgScriptExtension should be used when constructing the API url.

#Comment by Happy-melon (talk | contribs)   11:17, 28 April 2010
$link.data('target') should be urlencoded

Where?

#Comment by Bryan (talk | contribs)   11:25, 28 April 2010
+		$j.get( wgScriptPath 
+				+ '/api.php?action=watch&format=json&title='
+				+ $link.data('target')
+				+ ( $link.data('action')=='unwatch' ? '&unwatch' : '' ),
+			{},
+			wgAjaxWatch.processResult,
+			'json'
+		);
#Comment by Happy-melon (talk | contribs)   12:28, 28 April 2010

Gotcha. Done in r65614.

#Comment by Catrope (talk | contribs)   12:51, 22 July 2010
+		wgAjaxWatch.setLinkText( $link, $link.data('action')+'ing' );

That's not very localizable, now, is it? :)

#Comment by Bryan (talk | contribs)   17:58, 23 October 2010

wgAjaxWatch.setLinkText() transforms the key ('watch' + 'ing') to an appropriate mediaWiki.msg.get call already.

Status & tagging log