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 | + */ |
7 | 7 | |
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 ) { |
12 | 9 | var wgAjaxWatch = { |
13 | 10 | watchMsg: "Watch", |
14 | 11 | unwatchMsg: "Unwatch", |
— | — | @@ -18,163 +15,114 @@ |
19 | 16 | }; |
20 | 17 | } |
21 | 18 | |
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' ); |
46 | 33 | } |
47 | 34 | } else { |
48 | | - for ( i = 0; i < wgAjaxWatch.watchLinks.length; i++ ) { |
49 | | - changeText( wgAjaxWatch.watchLinks[i], newText ); |
50 | | - } |
| 35 | + $link.html( wgAjaxWatch[action+'Msg'] ); |
51 | 36 | } |
52 | 37 | }; |
53 | 38 | |
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'); |
103 | 51 | return; |
104 | 52 | } |
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' ); |
116 | 60 | } 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' ); |
120 | 62 | } |
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; |
132 | 63 | }; |
133 | 64 | |
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 * )' ); |
137 | 73 | |
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, ' ' ) ); |
158 | 81 | |
159 | | - // Detect if the watch/unwatch feature is in icon mode |
160 | | - if ( el1.className.match( /icon/i ) ) { |
161 | | - wgAjaxWatch.iconMode = true; |
162 | | - } |
| 82 | + }); |
163 | 83 | |
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); |
168 | 86 | |
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 | + ); |
172 | 104 | |
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 @@ |
346 | 346 | $wgDeferredUpdateList = array(); |
347 | 347 | $wgPostCommitUpdateList = array(); |
348 | 348 | |
349 | | -if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch'; |
350 | 349 | if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning'; |
351 | 350 | |
352 | 351 | # Placeholders in case of DB error |
Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -1518,6 +1518,7 @@ |
1519 | 1519 | wfRunHooks( 'AjaxAddScript', array( &$this ) ); |
1520 | 1520 | |
1521 | 1521 | if( $wgAjaxWatch && $wgUser->isLoggedIn() ) { |
| 1522 | + $this->includeJQuery(); |
1522 | 1523 | $this->addScriptFile( 'ajaxwatch.js' ); |
1523 | 1524 | } |
1524 | 1525 | |
Index: trunk/phase3/includes/api/ApiWatch.php |
— | — | @@ -57,9 +57,11 @@ |
58 | 58 | |
59 | 59 | if ( $params['unwatch'] ) { |
60 | 60 | $res['unwatched'] = ''; |
| 61 | + $res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() ); |
61 | 62 | $success = $article->doUnwatch(); |
62 | 63 | } else { |
63 | 64 | $res['watched'] = ''; |
| 65 | + $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() ); |
64 | 66 | $success = $article->doWatch(); |
65 | 67 | } |
66 | 68 | if ( !$success ) { |
Index: trunk/phase3/includes/AjaxFunctions.php |
— | — | @@ -74,60 +74,6 @@ |
75 | 75 | } |
76 | 76 | |
77 | 77 | /** |
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 | | -/** |
132 | 78 | * Called in some places (currently just extensions) |
133 | 79 | * to get the thumbnail URL for a given file at a given resolution. |
134 | 80 | */ |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -58,6 +58,8 @@ |
59 | 59 | * (bug 22858) $wgLocalStylePath is by default set to the same value as |
60 | 60 | $wgStylePath but should never point to a different domain than the site is |
61 | 61 | 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. |
62 | 64 | |
63 | 65 | === Bug fixes in 1.17 === |
64 | 66 | * (bug 17560) Half-broken deletion moved image files to deletion archive |