From 69ea9bcf9bcdb433e2497c161f831514a9153723 Mon Sep 17 00:00:00 2001 From: Aryeh Gregor Date: Tue, 26 Dec 2006 23:53:34 +0000 Subject: [PATCH] (bug 7169) Use Ajax to watch/unwatch articles. Patch by Dan Li with some modification by me. --- includes/AjaxDispatcher.php | 10 +-- includes/AjaxFunctions.php | 39 +++++++++ includes/DefaultSettings.php | 9 ++- includes/OutputPage.php | 15 ++-- includes/Setup.php | 1 + includes/Skin.php | 23 +++++- languages/messages/MessagesEn.php | 3 + skins/common/ajaxwatch.js | 127 ++++++++++++++++++++++++++++++ skins/common/wikibits.js | 15 +++- 9 files changed, 224 insertions(+), 18 deletions(-) create mode 100644 skins/common/ajaxwatch.js diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index 618c2736c8..d19035e547 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -15,7 +15,7 @@ class AjaxDispatcher { var $args; function AjaxDispatcher() { - wfProfileIn( 'AjaxDispatcher::AjaxDispatcher' ); + wfProfileIn( __METHOD__ ); $this->mode = ""; @@ -42,7 +42,7 @@ class AjaxDispatcher { $this->args = array(); } } - wfProfileOut( 'AjaxDispatcher::AjaxDispatcher' ); + wfProfileOut( __METHOD__ ); } function performAction() { @@ -51,7 +51,7 @@ class AjaxDispatcher { if ( empty( $this->mode ) ) { return; } - wfProfileIn( 'AjaxDispatcher::performAction' ); + wfProfileIn( __METHOD__ ); if (! in_array( $this->func_name, $wgAjaxExportList ) ) { header( 'Status: 400 Bad Request', true, 400 ); @@ -72,7 +72,7 @@ class AjaxDispatcher { $result->sendHeaders(); $result->printText(); } - + } catch (Exception $e) { if (!headers_sent()) { header( 'Status: 500 Internal Error', true, 500 ); @@ -83,7 +83,7 @@ class AjaxDispatcher { } } - wfProfileOut( 'AjaxDispatcher::performAction' ); + wfProfileOut( __METHOD__ ); $wgOut = null; } } diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php index 9f7a332f4b..3e74a19b1c 100644 --- a/includes/AjaxFunctions.php +++ b/includes/AjaxFunctions.php @@ -129,4 +129,43 @@ function wfSajaxSearch( $term ) { return $response; } +/** + * Called for AJAX watch/unwatch requests. + * @param $pageID Integer ID of the page to be watched/unwatched + * @param $watch String 'w' to watch, 'u' to unwatch + * @return String '' or '' on successful watch or unwatch, respectively, or '' on error (invalid XML in case we want to add HTML sometime) + */ +function wfAjaxWatch($pageID, $watch) { + if(wfReadOnly()) + return ''; // redirect to action=(un)watch, which will display the database lock message + + if(('w' !== $watch && 'u' !== $watch) || !is_numeric($pageID)) + return ''; + $watch = 'w' === $watch; + $pageID = intval($pageID); + + $title = Title::newFromID($pageID); + if(!$title) + return ''; + $article = new Article($title); + $watching = $title->userIsWatching(); + + if($watch) { + if(!$watching) { + $dbw =& wfGetDB(DB_MASTER); + $dbw->begin(); + $article->doWatch(); + $dbw->commit(); + } + } else { + if($watching) { + $dbw =& wfGetDB(DB_MASTER); + $dbw->begin(); + $article->doUnwatch(); + $dbw->commit(); + } + } + + return $watch ? '' : ''; +} ?> diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 49e536d020..3da4b44940 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1042,7 +1042,7 @@ $wgCacheEpoch = '20030516000000'; * to ensure that client-side caches don't keep obsolete copies of global * styles. */ -$wgStyleVersion = '37'; +$wgStyleVersion = '38'; # Server-side caching: @@ -2253,6 +2253,13 @@ $wgAjaxSearch = false; */ $wgAjaxExportList = array( ); +/** + * Enable watching/unwatching pages using AJAX. + * Requires $wgUseAjax to be true too. + * Causes wfAjaxWatch to be added to $wgAjaxExportList + */ +$wgAjaxWatch = false; + /** * Allow DISPLAYTITLE to change title display */ diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 65f391cb66..d83e1e1634 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -524,8 +524,8 @@ class OutputPage { public function output() { global $wgUser, $wgOutputEncoding, $wgRequest; global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType; - global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgServer; - global $wgStyleVersion; + global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgAjaxWatch; + global $wgServer, $wgStyleVersion; if( $this->mDoNothing ){ return; @@ -536,11 +536,14 @@ class OutputPage { if ( $wgUseAjax ) { $this->addScript( "\n" ); - } + if( $wgAjaxSearch ) { + $this->addScript( "\n" ); + $this->addScript( "\n" ); + } - if ( $wgUseAjax && $wgAjaxSearch ) { - $this->addScript( "\n" ); - $this->addScript( "\n" ); + if( $wgAjaxWatch && $wgUser->isLoggedIn() ) { + $this->addScript( "\n" ); + } } if ( '' != $this->mRedirect ) { diff --git a/includes/Setup.php b/includes/Setup.php index e143395745..387805f678 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -171,6 +171,7 @@ $wgDeferredUpdateList = array(); $wgPostCommitUpdateList = array(); if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch'; +if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch'; wfSeedRandom(); diff --git a/includes/Skin.php b/includes/Skin.php index bc86268a12..662e229231 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -23,6 +23,7 @@ class Skin extends Linker { var $rc_cache ; # Cache for Enhanced Recent Changes var $rcCacheIndex ; # Recent Changes Cache Counter for visibility toggle var $rcMoveIndex; + var $mWatchLinkNum = 0; // Appended to end of watch link id's /**#@-*/ /** Constructor, call parent constructor */ @@ -392,7 +393,7 @@ class Skin extends Linker { */ function getUserJs() { $fname = 'Skin::getUserJs'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); global $wgStylePath; $s = "/* generated javascript */\n"; @@ -403,7 +404,20 @@ class Skin extends Linker { $s .= $commonJs; } - wfProfileOut( $fname ); + global $wgUseAjax, $wgAjaxWatch; + if($wgUseAjax && $wgAjaxWatch) { + $s .= " + +/* AJAX (un)watch (see /skins/common/ajaxwatch.js) */ +var wgAjaxWatch = { + watchMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'watch', array() ) )."', + unwatchMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'unwatch', array() ) )."', + watchingMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'watching', array() ) )."', + unwatchingMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'unwatching', array() ) )."' +};"; + } + + wfProfileOut( __METHOD__ ); return $s; } @@ -1272,16 +1286,19 @@ END; function watchThisPage() { global $wgOut, $wgTitle; + ++$this->mWatchLinkNum; if ( $wgOut->isArticleRelated() ) { if ( $wgTitle->userIsWatching() ) { $t = wfMsg( 'unwatchthispage' ); $q = 'action=unwatch'; + $id = "mw-unwatch-link".$this->mWatchLinkNum; } else { $t = wfMsg( 'watchthispage' ); $q = 'action=watch'; + $id = 'mw-watch-link'.$this->mWatchLinkNum; } - $s = $this->makeKnownLinkObj( $wgTitle, $t, $q ); + $s = $this->makeKnownLinkObj( $wgTitle, $t, $q, '', '', " id=\"$id\"" ); } else { $s = wfMsg( 'notanarticle' ); } diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 32dea4fc4a..711b0e4593 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -1633,6 +1633,9 @@ at the bottom of the screen (deleting a content page also deletes the accompanyi 'wlhideshowown' => '$1 my edits', 'wlhideshowbots' => '$1 bot edits', 'wldone' => 'Done.', +# Displayed when you click the "watch" button and it's in the process of watching +'watching' => 'Watching...', +'unwatching' => 'Unwatching...', 'enotif_mailer' => '{{SITENAME}} Notification Mailer', 'enotif_reset' => 'Mark all pages visited', diff --git a/skins/common/ajaxwatch.js b/skins/common/ajaxwatch.js new file mode 100644 index 0000000000..16e4fdc455 --- /dev/null +++ b/skins/common/ajaxwatch.js @@ -0,0 +1,127 @@ +// dependencies: +// * ajax.js: + /*extern sajax_init_object, sajax_do_call */ +// * wikibits.js: + /*extern changeText, akeytt, hookEvent */ + +// These should have been initialized in the generated js +/*extern wgAjaxWatch, wgArticleId */ + +if(typeof wgAjaxWatch === "undefined" || !wgAjaxWatch) { + var wgAjaxWatch = { + watchMsg: "Watch", + unwatchMsg: "Unwatch", + watchingMsg: "Watching...", + unwatchingMsg: "Unwatching..." + }; +} + +wgAjaxWatch.supported = true; // supported on current page and by browser +wgAjaxWatch.watching = false; // currently watching page +wgAjaxWatch.inprogress = false; // ajax request in progress +wgAjaxWatch.timeoutID = null; // see wgAjaxWatch.ajaxCall +wgAjaxWatch.watchLink1 = null; // "watch"/"unwatch" link +wgAjaxWatch.watchLink2 = null; // second one, for (some?) non-Monobook-based +wgAjaxWatch.oldHref = null; // url for action=watch/action=unwatch + +wgAjaxWatch.setLinkText = function(newText) { + changeText(wgAjaxWatch.watchLink1, newText); + if (wgAjaxWatch.watchLink2) { + changeText(wgAjaxWatch.watchLink2, newText); + } +}; + +wgAjaxWatch.setLinkID = function(newId) { + wgAjaxWatch.watchLink1.id = newId; + akeytt(newId); // update tooltips for Monobook +}; + +wgAjaxWatch.ajaxCall = function() { + if(!wgAjaxWatch.supported || wgAjaxWatch.inprogress) { + return; + } + wgAjaxWatch.inprogress = true; + wgAjaxWatch.setLinkText(wgAjaxWatch.watching ? wgAjaxWatch.unwatchingMsg : wgAjaxWatch.watchingMsg); + sajax_do_call("wfAjaxWatch", [wgArticleId, (wgAjaxWatch.watching ? "u" : "w")], wgAjaxWatch.processResult); + // if the request isn't done in 10 seconds, allow user to try again + wgAjaxWatch.timeoutID = window.setTimeout(function() { wgAjaxWatch.inprogress = false; }, 10000); + return; +}; + +wgAjaxWatch.processResult = function(request) { + if(!wgAjaxWatch.supported) { + return; + } + var response = request.responseText; + if(response == "") { + window.location.href = wgAjaxWatch.oldHref; + return; + } else if(response == "") { + wgAjaxWatch.watching = true; + wgAjaxWatch.setLinkText(wgAjaxWatch.unwatchMsg); + wgAjaxWatch.setLinkID("ca-unwatch"); + wgAjaxWatch.oldHref = wgAjaxWatch.oldHref.replace(/action=watch/, "action=unwatch"); + } else if(response == "") { + wgAjaxWatch.watching = false; + wgAjaxWatch.setLinkText(wgAjaxWatch.watchMsg); + wgAjaxWatch.setLinkID("ca-watch"); + wgAjaxWatch.oldHref = wgAjaxWatch.oldHref.replace(/action=unwatch/, "action=watch"); + } + wgAjaxWatch.inprogress = false; + if(wgAjaxWatch.timeoutID) { + window.clearTimeout(wgAjaxWatch.timeoutID); + } + return; +}; + +wgAjaxWatch.onLoad = function() { + var el1 = document.getElementById("ca-unwatch"); + var el2 = null; + if (!el1) { + el1 = document.getElementById("mw-unwatch-link1"); + el2 = document.getElementById("mw-unwatch-link2"); + } + if(el1) { + wgAjaxWatch.watching = true; + } else { + wgAjaxWatch.watching = false; + el1 = document.getElementById("ca-watch"); + if (!el1) { + el1 = document.getElementById("mw-watch-link1"); + el2 = document.getElementById("mw-watch-link2"); + } + if(!el1) { + wgAjaxWatch.supported = false; + return; + } + } + + if(!wfSupportsAjax()) { + wgAjaxWatch.supported = false; + return; + } + + // The id can be either for the parent (Monobook-based) or the element + // itself (non-Monobook) + wgAjaxWatch.watchLink1 = el1.tagName.toLowerCase() == "a" ? el1 : el1.firstChild; + wgAjaxWatch.watchLink2 = el2 ? el2 : null; + + wgAjaxWatch.oldHref = wgAjaxWatch.watchLink1.getAttribute("href"); + wgAjaxWatch.watchLink1.setAttribute("href", "javascript:wgAjaxWatch.ajaxCall()"); + if (wgAjaxWatch.watchLink2) { + wgAjaxWatch.watchLink2.setAttribute("href", "javascript:wgAjaxWatch.ajaxCall()"); + } + return; +}; + +hookEvent("load", wgAjaxWatch.onLoad); + +/** + * @return boolean whether the browser supports XMLHttpRequest + */ +function wfSupportsAjax() { + var request = sajax_init_object(); + var supportsAjax = request ? true : false; + delete request; + return supportsAjax; +} \ No newline at end of file diff --git a/skins/common/wikibits.js b/skins/common/wikibits.js index 55fca49e0d..eabdcbb43d 100644 --- a/skins/common/wikibits.js +++ b/skins/common/wikibits.js @@ -473,11 +473,16 @@ function insertTags(tagOpen, tagClose, sampleText) { } } -function akeytt() { +/** + * Set up accesskeys/tooltips. If doId is specified, only set up for that id. + * + * @param mixed doId string or null + */ +function akeytt( doId ) { if (typeof ta == "undefined" || !ta) { return; } - + var pref; if (is_safari || navigator.userAgent.toLowerCase().indexOf('mac') + 1 || navigator.userAgent.toLowerCase().indexOf('konqueror') + 1 ) { @@ -492,6 +497,10 @@ function akeytt() { pref = 'alt-'; } + if ( doId ) { + ta = [ta[doId]]; + } + for (var id in ta) { var n = document.getElementById(id); if (n) { @@ -881,7 +890,7 @@ function runOnloadHook() { histrowinit(); unhidetzbutton(); tabbedprefs(); - akeytt(); + akeytt( null ); scrollEditBox(); setupCheckboxShiftClick(); sortableTables(); -- 2.20.1