Index: trunk/phase3/maintenance/archives/patch-editings.sql |
— | — | @@ -0,0 +1,14 @@ |
| 2 | +-- |
| 3 | +-- Tracks people currently editing an article |
| 4 | +-- Enabled with $wgAjaxShowEditors = true; |
| 5 | +-- |
| 6 | + |
| 7 | +CREATE TABLE /*$wgDBprefix*/editings ( |
| 8 | + `editings_page` int(8) NOT NULL, |
| 9 | + `editings_user` varchar(255) NOT NULL, |
| 10 | + `editings_started` char(14) NOT NULL, |
| 11 | + `editings_touched` char(14) NOT NULL, |
| 12 | + PRIMARY KEY (`editings_page`,`editings_user`), |
| 13 | + KEY `editings_page` (`editings_page`) |
| 14 | + KEY `editings_page_started` (`editings_page`,`editings_user`,`editings_started`) |
| 15 | +) TYPE=InnoDB; |
Property changes on: trunk/phase3/maintenance/archives/patch-editings.sql |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 16 | + native |
Index: trunk/phase3/maintenance/updaters.inc |
— | — | @@ -30,6 +30,7 @@ |
31 | 31 | array( 'transcache', 'patch-transcache.sql' ), |
32 | 32 | array( 'trackbacks', 'patch-trackbacks.sql' ), |
33 | 33 | array( 'externallinks', 'patch-externallinks.sql' ), |
| 34 | + array( 'editings', 'patch-editings.sql' ), |
34 | 35 | array( 'job', 'patch-job.sql' ), |
35 | 36 | array( 'langlinks', 'patch-langlinks.sql' ), |
36 | 37 | array( 'querycache_info', 'patch-querycacheinfo.sql' ), |
Index: trunk/phase3/skins/monobook/main.css |
— | — | @@ -1574,6 +1574,27 @@ |
1575 | 1575 | } |
1576 | 1576 | tr.sv-space td { display: none; } |
1577 | 1577 | |
| 1578 | + |
| 1579 | +/* wgAjaxShowEditors */ |
| 1580 | + |
| 1581 | +#ajax-se { |
| 1582 | + border:1px solid #aaaaaa; |
| 1583 | + margin: 0 0 1em 0; |
| 1584 | + padding:0.15em; |
| 1585 | + color : #000000; |
| 1586 | + background-color: #F0F0F0; |
| 1587 | +} |
| 1588 | + |
| 1589 | +#ajax-se-title { |
| 1590 | + font-size:small; |
| 1591 | + display:inline; |
| 1592 | + margin-right:1em; |
| 1593 | +} |
| 1594 | + |
| 1595 | +#ajax-se-editors { |
| 1596 | + display:inline; |
| 1597 | +} |
| 1598 | + |
1578 | 1599 | /* |
1579 | 1600 | Table pager (e.g. Special:Imagelist) |
1580 | 1601 | - remove underlines from the navigation link |
— | — | @@ -1615,4 +1636,4 @@ |
1616 | 1637 | */ |
1617 | 1638 | #toolbar { clear: both; } |
1618 | 1639 | .mw-plusminus-null { color: #aaa; } |
1619 | | -.texvc { direction: ltr; unicode-bidi: embed; } |
\ No newline at end of file |
| 1640 | +.texvc { direction: ltr; unicode-bidi: embed; } |
Index: trunk/phase3/skins/common/ajaxwatch.js |
— | — | @@ -124,4 +124,4 @@ |
125 | 125 | var supportsAjax = request ? true : false; |
126 | 126 | delete request; |
127 | 127 | return supportsAjax; |
128 | | -} |
\ No newline at end of file |
| 128 | +} |
Index: trunk/phase3/skins/common/ajaxshoweditors.js |
— | — | @@ -0,0 +1,62 @@ |
| 2 | +var sajax_debug_mode = false; |
| 3 | +var canRefresh = null; |
| 4 | +var ShowEditorsCounting = false; |
| 5 | +var wgAjaxShowEditors = {} ; |
| 6 | + |
| 7 | +// The loader. Look at bottom for the sajax hook registration |
| 8 | +wgAjaxShowEditors.onLoad = function() { |
| 9 | + var elEditors = document.getElementById( 'ajax-se' ); |
| 10 | + // wgAjaxShowEditors.refresh(); |
| 11 | + elEditors.onclick = function() { wgAjaxShowEditors.refresh(); } ; |
| 12 | + |
| 13 | + var elTextArea = document.getElementById( 'wpTextbox1' ); |
| 14 | + elTextArea.onkeypress = function() { wgAjaxShowEditors.refresh(); } ; |
| 15 | + |
| 16 | + wgAjaxShowEditors.allowRefresh(); |
| 17 | +} |
| 18 | + |
| 19 | + |
| 20 | +// Ask for new data & update UI |
| 21 | +wgAjaxShowEditors.refresh = function() { |
| 22 | + if( !canRefresh ) { return; } |
| 23 | + |
| 24 | + // Disable new requests for 5 seconds |
| 25 | + canRefresh = false; |
| 26 | + setTimeout( 'wgAjaxShowEditors.allowRefresh()', 5000 ); |
| 27 | + |
| 28 | + // Load the editors list element, it will get rewrote |
| 29 | + var elEditorsList = document.getElementById( 'ajax-se-editors' ); |
| 30 | + |
| 31 | + if( wgUserName == null ) { |
| 32 | + wgUserName = ''; |
| 33 | + } |
| 34 | + |
| 35 | + // Do the ajax call to the server |
| 36 | + sajax_do_call( "wfAjaxShowEditors", [ wgArticleId, wgUserName ], elEditorsList ); |
| 37 | + if(!ShowEditorsCounting) { |
| 38 | + wgAjaxShowEditors.countup(); |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +wgAjaxShowEditors.countup = function() { |
| 43 | + ShowEditorsCounting = true; |
| 44 | + |
| 45 | + var elEditorsList = document.getElementById( 'ajax-se-editors' ); |
| 46 | + for(var i=0;i<elEditorsList.childNodes.length;i++) { |
| 47 | + var item = elEditorsList.childNodes[i]; |
| 48 | + if (item.nodeName == 'SPAN') { |
| 49 | + var value = parseInt( item.innerHTML ); |
| 50 | + value++; |
| 51 | + item.innerHTML = value ; |
| 52 | + } |
| 53 | + } |
| 54 | + setTimeout( "wgAjaxShowEditors.countup()", 1000 ); |
| 55 | +} |
| 56 | + |
| 57 | +// callback to allow refresh |
| 58 | +wgAjaxShowEditors.allowRefresh = function() { |
| 59 | + canRefresh = true; |
| 60 | +} |
| 61 | + |
| 62 | +// Register our initialization function. |
| 63 | +hookEvent( "load", wgAjaxShowEditors.onLoad); |
Property changes on: trunk/phase3/skins/common/ajaxshoweditors.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 64 | + native |
Index: trunk/phase3/includes/Setup.php |
— | — | @@ -198,6 +198,7 @@ |
199 | 199 | |
200 | 200 | if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch'; |
201 | 201 | if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch'; |
| 202 | +if ( $wgAjaxShowEditors ) $wgAjaxExportList[] = 'wfAjaxShowEditors'; |
202 | 203 | |
203 | 204 | wfSeedRandom(); |
204 | 205 | |
Index: trunk/phase3/includes/EditPage.php |
— | — | @@ -1113,6 +1113,19 @@ |
1114 | 1114 | $templates = ($this->preview || $this->section) ? $this->mPreviewTemplates : $this->mArticle->getUsedTemplates(); |
1115 | 1115 | $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != ''); |
1116 | 1116 | |
| 1117 | + |
| 1118 | + global $wgAjaxShowEditors ; |
| 1119 | + if ( $wgAjaxShowEditors && isset( $this->mArticle ) |
| 1120 | + && isset( $this->mArticle->mRevision ) ) { |
| 1121 | + global $wgJsMimeType, $wgStylePath, $wgStyleVersion; |
| 1122 | + $wgOut->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxshoweditors.js?$wgStyleVersion\"></script>\n" ); |
| 1123 | + $wgOut->addWikiText( |
| 1124 | + '<div id="ajax-se"><p id="ajax-se-title">'.wfMsg('ajax-se-title').'</p>' |
| 1125 | + . '<p id="ajax-se-editors">'. wfMsg('ajax-se-pending') . '</p>' |
| 1126 | + . '</div>' |
| 1127 | + ); |
| 1128 | + } |
| 1129 | + |
1117 | 1130 | global $wgUseMetadataEdit ; |
1118 | 1131 | if ( $wgUseMetadataEdit ) { |
1119 | 1132 | $metadata = $this->mMetaData ; |
Index: trunk/phase3/includes/AjaxHooks.php |
— | — | @@ -0,0 +1,29 @@ |
| 2 | +<?php |
| 3 | +if( !defined( 'MEDIAWIKI' ) ) |
| 4 | + die( 1 ); |
| 5 | + |
| 6 | +/** |
| 7 | + $article: the article (object) saved |
| 8 | + $user: the user (object) who saved the article |
| 9 | + $text: the new article text |
| 10 | + $summary: the article summary (comment) |
| 11 | + $isminor: minor flag |
| 12 | + $iswatch: watch flag |
| 13 | + $section: section # |
| 14 | +*/ |
| 15 | +function wfAjaxShowEditorsCleanup( $article, $user ) { |
| 16 | + $articleId = $article->getID(); |
| 17 | + $userId = $user->getName(); |
| 18 | + |
| 19 | + $dbw =& wfGetDB(DB_MASTER); |
| 20 | + $dbw->delete('editings', |
| 21 | + array( |
| 22 | + 'editings_page' => $articleId, |
| 23 | + 'editings_user' => $userId, |
| 24 | + ), |
| 25 | + __METHOD__ |
| 26 | + ); |
| 27 | +} |
| 28 | + |
| 29 | +$wgHooks['ArticleSaveComplete'][] = 'wfAjaxShowEditorsCleanup'; |
| 30 | +?> |
Property changes on: trunk/phase3/includes/AjaxHooks.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 31 | + native |
Index: trunk/phase3/includes/AjaxFunctions.php |
— | — | @@ -168,4 +168,102 @@ |
169 | 169 | |
170 | 170 | return $watch ? '<w#>' : '<u#>'; |
171 | 171 | } |
| 172 | + |
| 173 | +/** |
| 174 | + * Return a list of Editors currently editing the article. |
| 175 | + * Based on an idea by Tim Starling. |
| 176 | + * |
| 177 | + * @author Ashar Voultoiz <hashar@altern.org> |
| 178 | + * @author Tim Starling |
| 179 | + */ |
| 180 | +function wfAjaxShowEditors( $articleId, $username ) { |
| 181 | + global $wgOut; |
| 182 | + $articleId = intval($articleId); |
| 183 | + |
| 184 | + // Validate request |
| 185 | + $title = Title::newFromID( $articleId ); |
| 186 | + if( !($title) ) { return 'ERR: page id invalid'; } |
| 187 | + |
| 188 | + $user = User::newFromSession() ; |
| 189 | + if( !$user ) { return 'ERR: user invalid'; } |
| 190 | + |
| 191 | + $username = $user->getName(); |
| 192 | + if( !( $user->isLoggedIn() or User::isIP( $username ) ) ) { return 'ERR: user not found'; } |
| 193 | + |
| 194 | + |
| 195 | + // When did the user started editing ? |
| 196 | + $dbr =& wfGetDB(DB_SLAVE); |
| 197 | + $userStarted = $dbr->selectField( 'editings', |
| 198 | + 'editings_started', |
| 199 | + array( |
| 200 | + 'editings_user' => $username, |
| 201 | + 'editings_page' => $title->getArticleID(), |
| 202 | + ), |
| 203 | + __METHOD__ |
| 204 | + ); |
| 205 | + |
| 206 | + // He just started editing, assume NOW |
| 207 | + if(!$userStarted) { $userStarted = $dbr->timestamp(); } |
| 208 | + |
| 209 | + # Either create a new entry or update the touched timestamp. |
| 210 | + # This is done using a unique index on the database : |
| 211 | + # `editings_page_started` (`editings_page`,`editings_user`,`editings_started`) |
| 212 | + |
| 213 | + $dbw =& wfGetDB(DB_MASTER); |
| 214 | + $dbw->replace( 'editings', |
| 215 | + array( 'editings_page', 'editings_user', 'editings_started' ), |
| 216 | + array( |
| 217 | + 'editings_page' => $title->getArticleID() , |
| 218 | + 'editings_user' => $username, |
| 219 | + 'editings_started' => $userStarted , |
| 220 | + 'editings_touched' => $dbw->timestamp(), |
| 221 | + ), __METHOD__ |
| 222 | + ); |
| 223 | + |
| 224 | + // Now we get the list of all watching users |
| 225 | + $dbr = & wfGetDB(DB_SLAVE); |
| 226 | + $res = $dbr->select( 'editings', |
| 227 | + array( 'editings_user','editings_started','editings_touched' ), |
| 228 | + array( 'editings_page' => $title->getArticleID() ), |
| 229 | + __METHOD__ |
| 230 | + ); |
| 231 | + |
| 232 | + $l = new Linker(); |
| 233 | + |
| 234 | + $wikitext = ''; |
| 235 | + $unix_now = wfTimestamp(TS_UNIX); |
| 236 | + $first = 1; |
| 237 | + while( $editor = $dbr->fetchObject( $res ) ) { |
| 238 | + |
| 239 | + // Check idling time |
| 240 | + $idle = $unix_now - wfTimestamp( TS_UNIX, $editor->editings_touched ); |
| 241 | + |
| 242 | + global $wgAjaxShowEditorsTimeout ; |
| 243 | + if( $idle >= $wgAjaxShowEditorsTimeout ) { |
| 244 | + $dbw->delete('editings', |
| 245 | + array( |
| 246 | + 'editings_page' => $title->getArticleID(), |
| 247 | + 'editings_user' => $editor->editings_user, |
| 248 | + ), |
| 249 | + __METHOD__ |
| 250 | + ); |
| 251 | + continue; // we will not show the user |
| 252 | + } |
| 253 | + |
| 254 | + if( $first ) { $first = 0; } |
| 255 | + else { $wikitext .= ' ~ '; } |
| 256 | + |
| 257 | + $since = wfTimestamp( TS_DB, $editor->editings_started ); |
| 258 | + $wikitext .= $since; |
| 259 | + |
| 260 | + $wikitext .= ' ' . $l->makeLinkObj( |
| 261 | + Title::makeTitle( NS_USER, $editor->editings_user ), |
| 262 | + $editor->editings_user |
| 263 | + ); |
| 264 | + |
| 265 | + |
| 266 | + $wikitext .= ' ' . wfMsg( 'ajax-se-idling', '<span>'.$idle.'</span>' ); |
| 267 | + } |
| 268 | + return $wikitext ; |
| 269 | +} |
172 | 270 | ?> |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -1109,7 +1109,7 @@ |
1110 | 1110 | * to ensure that client-side caches don't keep obsolete copies of global |
1111 | 1111 | * styles. |
1112 | 1112 | */ |
1113 | | -$wgStyleVersion = '51'; |
| 1113 | +$wgStyleVersion = '52'; |
1114 | 1114 | |
1115 | 1115 | |
1116 | 1116 | # Server-side caching: |
— | — | @@ -2351,6 +2351,15 @@ |
2352 | 2352 | $wgAjaxWatch = false; |
2353 | 2353 | |
2354 | 2354 | /** |
| 2355 | + * Let you show other peoples editing an article. |
| 2356 | + */ |
| 2357 | +$wgAjaxShowEditors = false; |
| 2358 | +/** |
| 2359 | + * Number of seconds before an user is considered as no more editing |
| 2360 | + */ |
| 2361 | +$wgAjaxShowEditorsTimeout = 60; |
| 2362 | + |
| 2363 | +/** |
2355 | 2364 | * Allow DISPLAYTITLE to change title display |
2356 | 2365 | */ |
2357 | 2366 | $wgAllowDisplayTitle = false ; |
Index: trunk/phase3/index.php |
— | — | @@ -22,13 +22,17 @@ |
23 | 23 | # |
24 | 24 | # Send Ajax requests to the Ajax dispatcher. |
25 | 25 | # |
26 | | -if ( $wgUseAjax && $action == 'ajax' ) { |
27 | | - require_once( $IP . '/includes/AjaxDispatcher.php' ); |
| 26 | +if ( $wgUseAjax ) { |
| 27 | + if( $action == 'ajax' ) { |
| 28 | + require_once( $IP . '/includes/AjaxDispatcher.php' ); |
28 | 29 | |
29 | | - $dispatcher = new AjaxDispatcher(); |
30 | | - $dispatcher->performAction(); |
31 | | - $mediaWiki->restInPeace( $wgLoadBalancer ); |
32 | | - exit; |
| 30 | + $dispatcher = new AjaxDispatcher(); |
| 31 | + $dispatcher->performAction(); |
| 32 | + $mediaWiki->restInPeace( $wgLoadBalancer ); |
| 33 | + exit; |
| 34 | + } else { |
| 35 | + require_once( $IP . '/includes/AjaxHooks.php' ); |
| 36 | + } |
33 | 37 | } |
34 | 38 | |
35 | 39 | |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -2759,6 +2759,11 @@ |
2760 | 2760 | 'size-megabytes' => '$1 MB', |
2761 | 2761 | 'size-gigabytes' => '$1 GB', |
2762 | 2762 | |
| 2763 | +# Ajax show editors |
| 2764 | +'ajax-se-title' => 'Currently editing:', |
| 2765 | +'ajax-se-pending' => 'pending refresh ... (click this box or start editing)', |
| 2766 | +'ajax-se-idling' => '($1s ago)', |
| 2767 | + |
2763 | 2768 | ); |
2764 | 2769 | |
2765 | 2770 | ?> |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -28,6 +28,9 @@ |
29 | 29 | |
30 | 30 | == Major new features == |
31 | 31 | |
| 32 | +$wgAjaxShowEditors will let your users see who else is editing the page |
| 33 | +they are currently editing. |
| 34 | + |
32 | 35 | == Changes since 1.9 == |
33 | 36 | |
34 | 37 | * (bug 7292) Fix site statistics when moving pages in/out of content namespaces |