(bug 7169) Use Ajax to watch/unwatch articles. Patch by Dan Li with some modificatio...
authorAryeh Gregor <simetrical@users.mediawiki.org>
Tue, 26 Dec 2006 23:53:34 +0000 (23:53 +0000)
committerAryeh Gregor <simetrical@users.mediawiki.org>
Tue, 26 Dec 2006 23:53:34 +0000 (23:53 +0000)
includes/AjaxDispatcher.php
includes/AjaxFunctions.php
includes/DefaultSettings.php
includes/OutputPage.php
includes/Setup.php
includes/Skin.php
languages/messages/MessagesEn.php
skins/common/ajaxwatch.js [new file with mode: 0644]
skins/common/wikibits.js

index 618c273..d19035e 100644 (file)
@@ -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;
        }
 }
index 9f7a332..3e74a19 100644 (file)
@@ -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 '<w#>' or '<u#>' on successful watch or unwatch, respectively, or '<err#>' on error (invalid XML in case we want to add HTML sometime)
+ */
+function wfAjaxWatch($pageID, $watch) {
+       if(wfReadOnly())
+               return '<err#>'; // redirect to action=(un)watch, which will display the database lock message
+
+       if(('w' !== $watch && 'u' !== $watch) || !is_numeric($pageID))
+               return '<err#>';
+       $watch = 'w' === $watch;
+       $pageID = intval($pageID);
+
+       $title = Title::newFromID($pageID);
+       if(!$title)
+               return '<err#>';
+       $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 ? '<w#>' : '<u#>';
+}
 ?>
index 49e536d..3da4b44 100644 (file)
@@ -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
  */
index 65f391c..d83e1e1 100644 (file)
@@ -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( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js?$wgStyleVersion\"></script>\n" );
-               }
+                       if( $wgAjaxSearch ) {
+                               $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js\"></script>\n" );
+                               $this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" );
+                       }
 
-               if ( $wgUseAjax && $wgAjaxSearch ) {
-                       $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js?$wgStyleVersion\"></script>\n" );
-                       $this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" );
+                       if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
+                               $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js\"></script>\n" );
+                       }
                }
 
                if ( '' != $this->mRedirect ) {
index e143395..387805f 100644 (file)
@@ -171,6 +171,7 @@ $wgDeferredUpdateList = array();
 $wgPostCommitUpdateList = array();
 
 if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch';
+if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
 
 wfSeedRandom();
 
index bc86268..662e229 100644 (file)
@@ -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' );
                }
index 32dea4f..711b0e4 100644 (file)
@@ -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 (file)
index 0000000..16e4fdc
--- /dev/null
@@ -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 == "<err#>") {
+               window.location.href = wgAjaxWatch.oldHref;
+               return;
+       } else if(response == "<w#>") {
+               wgAjaxWatch.watching = true;
+               wgAjaxWatch.setLinkText(wgAjaxWatch.unwatchMsg);
+               wgAjaxWatch.setLinkID("ca-unwatch");
+               wgAjaxWatch.oldHref = wgAjaxWatch.oldHref.replace(/action=watch/, "action=unwatch");
+       } else if(response == "<u#>") {
+               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
index 55fca49..eabdcbb 100644 (file)
@@ -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();