Optional feature : 'Ajax show editors' based on an idea by Tim Starling
authorAntoine Musso <hashar@users.mediawiki.org>
Sat, 20 Jan 2007 21:05:36 +0000 (21:05 +0000)
committerAntoine Musso <hashar@users.mediawiki.org>
Sat, 20 Jan 2007 21:05:36 +0000 (21:05 +0000)
Merge from hashar's branch.

13 files changed:
RELEASE-NOTES
includes/AjaxFunctions.php
includes/AjaxHooks.php [new file with mode: 0644]
includes/DefaultSettings.php
includes/EditPage.php
includes/Setup.php
index.php
languages/messages/MessagesEn.php
maintenance/archives/patch-editings.sql [new file with mode: 0644]
maintenance/updaters.inc
skins/common/ajaxshoweditors.js [new file with mode: 0644]
skins/common/ajaxwatch.js
skins/monobook/main.css

index 40c28e7..f3ed5cf 100644 (file)
@@ -28,6 +28,9 @@ lighter making things easier to read.
 
 == Major new features ==
 
+$wgAjaxShowEditors will let your users see who else is editing the page
+they are currently editing.
+
 == Changes since 1.9 ==
 
 * (bug 7292) Fix site statistics when moving pages in/out of content namespaces
index 600cd57..8631b4e 100644 (file)
@@ -168,4 +168,102 @@ function wfAjaxWatch($pageID = "", $watch = "") {
 
        return $watch ? '<w#>' : '<u#>';
 }
+
+/**
+ * Return a list of Editors currently editing the article.
+ * Based on an idea by Tim Starling.
+ *
+ * @author Ashar Voultoiz <hashar@altern.org>
+ * @author Tim Starling
+ */
+function wfAjaxShowEditors( $articleId, $username ) {
+       global $wgOut;
+       $articleId = intval($articleId);
+
+       // Validate request
+       $title = Title::newFromID( $articleId );
+       if( !($title) ) { return 'ERR: page id invalid'; }
+
+       $user = User::newFromSession() ;
+       if( !$user ) { return 'ERR: user invalid'; }
+
+       $username = $user->getName();
+       if( !(  $user->isLoggedIn() or User::isIP( $username )  ) ) { return 'ERR: user not found'; }
+
+
+       // When did the user started editing ?
+       $dbr =& wfGetDB(DB_SLAVE);
+       $userStarted = $dbr->selectField( 'editings',
+               'editings_started',
+               array(
+                       'editings_user' => $username,
+                       'editings_page' => $title->getArticleID(),
+               ),
+               __METHOD__
+       );
+
+       // He just started editing, assume NOW
+       if(!$userStarted) { $userStarted = $dbr->timestamp(); }
+
+       # Either create a new entry or update the touched timestamp.
+       # This is done using a unique index on the database :
+       # `editings_page_started` (`editings_page`,`editings_user`,`editings_started`)
+
+       $dbw =& wfGetDB(DB_MASTER);
+       $dbw->replace( 'editings',
+               array( 'editings_page', 'editings_user', 'editings_started' ),
+               array(
+                       'editings_page' => $title->getArticleID() ,
+                       'editings_user' => $username,
+                       'editings_started' => $userStarted ,
+                       'editings_touched' => $dbw->timestamp(),
+               ), __METHOD__
+       );
+
+       // Now we get the list of all watching users
+       $dbr = & wfGetDB(DB_SLAVE);
+       $res = $dbr->select( 'editings',
+               array( 'editings_user','editings_started','editings_touched' ),
+               array( 'editings_page' => $title->getArticleID() ),
+               __METHOD__
+       );
+
+       $l = new Linker();
+
+       $wikitext = '';
+       $unix_now = wfTimestamp(TS_UNIX);
+       $first = 1;
+       while( $editor = $dbr->fetchObject( $res ) ) {
+
+               // Check idling time
+               $idle = $unix_now - wfTimestamp( TS_UNIX, $editor->editings_touched );
+
+               global $wgAjaxShowEditorsTimeout ;
+               if( $idle >= $wgAjaxShowEditorsTimeout ) {
+                       $dbw->delete('editings',
+                               array(
+                                       'editings_page' => $title->getArticleID(),
+                                       'editings_user' => $editor->editings_user,
+                               ),
+                               __METHOD__
+                       );
+                       continue; // we will not show the user
+               }
+
+               if( $first ) { $first = 0; }
+               else { $wikitext .= ' ~  '; }
+
+               $since = wfTimestamp( TS_DB, $editor->editings_started );
+               $wikitext .= $since;
+
+               $wikitext .= ' ' . $l->makeLinkObj(
+                               Title::makeTitle( NS_USER, $editor->editings_user ),
+                               $editor->editings_user
+                       );
+
+
+               $wikitext .= ' ' . wfMsg( 'ajax-se-idling', '<span>'.$idle.'</span>' );
+       }
+       return $wikitext ;
+}
 ?>
diff --git a/includes/AjaxHooks.php b/includes/AjaxHooks.php
new file mode 100644 (file)
index 0000000..244d89a
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+if( !defined( 'MEDIAWIKI' ) )
+       die( 1 );
+
+/**
+       $article: the article (object) saved
+       $user: the user (object) who saved the article
+       $text: the new article text
+       $summary: the article summary (comment)
+       $isminor: minor flag
+       $iswatch: watch flag
+       $section: section #
+*/
+function wfAjaxShowEditorsCleanup( $article, $user ) {
+       $articleId = $article->getID();
+       $userId = $user->getName();
+
+       $dbw =& wfGetDB(DB_MASTER);
+       $dbw->delete('editings',
+               array(
+                       'editings_page' => $articleId,
+                       'editings_user' => $userId,
+               ),
+               __METHOD__
+       );
+}
+
+$wgHooks['ArticleSaveComplete'][] = 'wfAjaxShowEditorsCleanup';
+?>
index ec85130..742aaf1 100644 (file)
@@ -1109,7 +1109,7 @@ $wgCacheEpoch = '20030516000000';
  * to ensure that client-side caches don't keep obsolete copies of global
  * styles.
  */
-$wgStyleVersion = '51';
+$wgStyleVersion = '52';
 
 
 # Server-side caching:
@@ -2350,6 +2350,15 @@ $wgAjaxExportList = array( );
  */
 $wgAjaxWatch = false;
 
+/**
+ * Let you show other peoples editing an article.
+ */
+$wgAjaxShowEditors = false;
+/**
+ * Number of seconds before an user is considered as no more editing
+ */
+$wgAjaxShowEditorsTimeout = 60;
+
 /**
  * Allow DISPLAYTITLE to change title display
  */
index e9b9713..cfde451 100644 (file)
@@ -1113,6 +1113,19 @@ class EditPage {
                $templates = ($this->preview || $this->section) ? $this->mPreviewTemplates : $this->mArticle->getUsedTemplates();
                $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != '');
 
+
+               global $wgAjaxShowEditors ;
+               if ( $wgAjaxShowEditors && isset( $this->mArticle )
+                 && isset( $this->mArticle->mRevision ) ) {
+                       global $wgJsMimeType, $wgStylePath, $wgStyleVersion;
+                       $wgOut->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxshoweditors.js?$wgStyleVersion\"></script>\n" );
+                       $wgOut->addWikiText(
+                               '<div id="ajax-se"><p id="ajax-se-title">'.wfMsg('ajax-se-title').'</p>'
+                               . '<p id="ajax-se-editors">'. wfMsg('ajax-se-pending') . '</p>'
+                               . '</div>'
+                               );
+               }
+
                global $wgUseMetadataEdit ;
                if ( $wgUseMetadataEdit ) {
                        $metadata = $this->mMetaData ;
index 6ed7c8d..7a88fb1 100644 (file)
@@ -198,6 +198,7 @@ $wgPostCommitUpdateList = array();
 
 if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch';
 if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
+if ( $wgAjaxShowEditors ) $wgAjaxExportList[] = 'wfAjaxShowEditors';
 
 wfSeedRandom();
 
index e3b753f..9955aa1 100644 (file)
--- a/index.php
+++ b/index.php
@@ -22,13 +22,17 @@ if ($wgTitle == NULL) {
 #
 # Send Ajax requests to the Ajax dispatcher.
 #
-if ( $wgUseAjax && $action == 'ajax' ) {
-       require_once( $IP . '/includes/AjaxDispatcher.php' );
-
-       $dispatcher = new AjaxDispatcher();
-       $dispatcher->performAction();
-       $mediaWiki->restInPeace( $wgLoadBalancer );
-       exit;
+if ( $wgUseAjax ) {
+       if( $action == 'ajax' ) {
+               require_once( $IP . '/includes/AjaxDispatcher.php' );
+
+               $dispatcher = new AjaxDispatcher();
+               $dispatcher->performAction();
+               $mediaWiki->restInPeace( $wgLoadBalancer );
+               exit;
+       } else {
+               require_once( $IP . '/includes/AjaxHooks.php' );
+       }
 }
 
 
index 9e18d0e..3630da7 100644 (file)
@@ -2759,6 +2759,11 @@ Please confirm that really want to recreate this page.',
 'size-megabytes' => '$1 MB',
 'size-gigabytes' => '$1 GB',
 
+# Ajax show editors
+'ajax-se-title' => 'Currently editing:',
+'ajax-se-pending' => 'pending refresh ... (click this box or start editing)',
+'ajax-se-idling' => '($1s ago)',
+
 );
 
 ?>
diff --git a/maintenance/archives/patch-editings.sql b/maintenance/archives/patch-editings.sql
new file mode 100644 (file)
index 0000000..33bbaa1
--- /dev/null
@@ -0,0 +1,14 @@
+--
+-- Tracks people currently editing an article
+-- Enabled with $wgAjaxShowEditors = true;
+--
+
+CREATE TABLE /*$wgDBprefix*/editings (
+  `editings_page` int(8) NOT NULL,
+  `editings_user` varchar(255) NOT NULL,
+  `editings_started` char(14) NOT NULL,
+  `editings_touched` char(14) NOT NULL,
+  PRIMARY KEY  (`editings_page`,`editings_user`),
+  KEY `editings_page` (`editings_page`)
+  KEY `editings_page_started` (`editings_page`,`editings_user`,`editings_started`)
+) TYPE=InnoDB;
index c042d5f..7fb2e1d 100644 (file)
@@ -30,6 +30,7 @@ $wgNewTables = array(
        array( 'transcache',    'patch-transcache.sql' ),
        array( 'trackbacks',    'patch-trackbacks.sql' ),
        array( 'externallinks', 'patch-externallinks.sql' ),
+       array( 'editings',      'patch-editings.sql' ),
        array( 'job',           'patch-job.sql' ),
        array( 'langlinks',     'patch-langlinks.sql' ),
        array( 'querycache_info', 'patch-querycacheinfo.sql' ),
diff --git a/skins/common/ajaxshoweditors.js b/skins/common/ajaxshoweditors.js
new file mode 100644 (file)
index 0000000..066ce81
--- /dev/null
@@ -0,0 +1,62 @@
+var sajax_debug_mode = false;
+var canRefresh = null;
+var ShowEditorsCounting = false;
+var wgAjaxShowEditors = {} ;
+
+// The loader. Look at bottom for the sajax hook registration
+wgAjaxShowEditors.onLoad = function() {
+       var elEditors = document.getElementById( 'ajax-se' );
+       // wgAjaxShowEditors.refresh();
+       elEditors.onclick = function() { wgAjaxShowEditors.refresh(); } ;
+
+       var elTextArea = document.getElementById( 'wpTextbox1' );
+       elTextArea.onkeypress = function() { wgAjaxShowEditors.refresh(); } ;
+
+       wgAjaxShowEditors.allowRefresh();
+}
+
+
+// Ask for new data & update UI
+wgAjaxShowEditors.refresh = function() {
+       if( !canRefresh ) { return; }
+
+       // Disable new requests for 5 seconds
+       canRefresh = false;
+       setTimeout( 'wgAjaxShowEditors.allowRefresh()', 5000 );
+
+       // Load the editors list element, it will get rewrote
+       var elEditorsList = document.getElementById( 'ajax-se-editors' );
+
+       if( wgUserName == null ) {
+               wgUserName = '';
+       }
+
+       // Do the ajax call to the server
+       sajax_do_call( "wfAjaxShowEditors", [ wgArticleId, wgUserName ], elEditorsList );
+       if(!ShowEditorsCounting) {
+               wgAjaxShowEditors.countup();
+       }
+}
+
+wgAjaxShowEditors.countup = function() {
+       ShowEditorsCounting = true;
+
+       var elEditorsList = document.getElementById( 'ajax-se-editors' );
+       for(var i=0;i<elEditorsList.childNodes.length;i++) {
+               var item = elEditorsList.childNodes[i];
+               if (item.nodeName == 'SPAN') {
+                       var value = parseInt( item.innerHTML );
+                       value++;
+                       item.innerHTML = value ;
+               }
+       }
+       setTimeout( "wgAjaxShowEditors.countup()", 1000 );
+}
+
+// callback to allow refresh
+wgAjaxShowEditors.allowRefresh = function() {
+       canRefresh = true;
+}
+
+// Register our initialization function.
+hookEvent( "load", wgAjaxShowEditors.onLoad);
index 16e4fdc..16a5c2b 100644 (file)
@@ -124,4 +124,4 @@ function wfSupportsAjax() {
        var supportsAjax = request ? true : false;
        delete request;
        return supportsAjax;
-}
\ No newline at end of file
+}
index 517765c..5d08db7 100644 (file)
@@ -1574,6 +1574,27 @@ tr.sv-space{
 }
 tr.sv-space td { display: none; }
 
+
+/* wgAjaxShowEditors */
+
+#ajax-se {
+       border:1px solid #aaaaaa;
+       margin: 0 0 1em 0;
+       padding:0.15em;
+       color : #000000;
+       background-color: #F0F0F0;
+}
+
+#ajax-se-title {
+       font-size:small;
+       display:inline;
+       margin-right:1em;
+}
+
+#ajax-se-editors {
+       display:inline;
+}
+
 /*
   Table pager (e.g. Special:Imagelist)
   - remove underlines from the navigation link
@@ -1615,4 +1636,4 @@ p.mw-ipb-conveniencelinks {
  */
 #toolbar { clear: both; }
 .mw-plusminus-null { color: #aaa; }
-.texvc { direction: ltr; unicode-bidi: embed; }
\ No newline at end of file
+.texvc { direction: ltr; unicode-bidi: embed; }