From: Happy-melon Date: Thu, 23 Dec 2010 00:06:58 +0000 (+0000) Subject: (bug 17456) implement CollapsibleTables in core javascript. Using ResourceLoader... X-Git-Tag: 1.31.0-rc.0~33157 X-Git-Url: http://git.cyclocoop.org/%40spipnet%40?a=commitdiff_plain;h=023debde1627723465b846566d3ff77da699dbd4;p=lhc%2Fweb%2Fwiklou.git (bug 17456) implement CollapsibleTables in core javascript. Using ResourceLoader/jQuery, this is now able to replace not only the ubiquitous collapsible tables in articles ([[mw:Manual:Collapsible tables]]), but also most of the other places we collapse things as well, like the enhanced Watchlist/RecentChanges feed. --- diff --git a/includes/ChangesList.php b/includes/ChangesList.php index 5de3b06d41..4597529fdb 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -30,7 +30,7 @@ class RCCacheEntry extends RecentChange { class ChangesList { public $skin; protected $watchlist = false; - + /** * Changeslist contructor * @param $skin Skin @@ -81,21 +81,19 @@ class ChangesList { } } - /** * Returns the appropriate flags for new page, minor change and patrolling - * @param $new Boolean - * @param $minor Boolean - * @param $patrolled Boolean + * @param $flags Associative array of 'flag' => Bool * @param $nothing String to use for empty space - * @param $bot Boolean * @return String */ - protected function recentChangesFlags( $new, $minor, $patrolled, $nothing = ' ', $bot = false ) { - $f = $new ? self::flag( 'newpage' ) : $nothing; - $f .= $minor ? self::flag( 'minor' ) : $nothing; - $f .= $bot ? self::flag( 'bot' ) : $nothing; - $f .= $patrolled ? self::flag( 'unpatrolled' ) : $nothing; + protected function recentChangesFlags( $flags, $nothing = ' ' ) { + $f = ''; + foreach( array( 'newpage', 'minor', 'bot', 'unpatrolled' ) as $flag ){ + $f .= isset( $flags[$flag] ) && $flags[$flag] + ? self::flag( $flag ) + : $nothing; + } return $f; } @@ -111,22 +109,18 @@ class ChangesList { public static function flag( $key ) { static $messages = null; if ( is_null( $messages ) ) { - foreach ( explode( ' ', 'minoreditletter boteditletter newpageletter ' . - 'unpatrolledletter recentchanges-label-minor recentchanges-label-bot ' . - 'recentchanges-label-newpage recentchanges-label-unpatrolled' ) as $msg ) { - $messages[$msg] = wfMsgExt( $msg, 'escapenoentities' ); + $messages = array( + 'newpage' => array( 'newpageletter', 'recentchanges-label-newpage' ), + 'minor' => array( 'minoreditletter', 'recentchanges-label-minor' ), + 'bot' => array( 'boteditletter', 'recentchanges-label-bot' ), + 'unpatrolled' => array( 'unpatrolledletter', 'recentchanges-label-unpatrolled' ), + ); + foreach( $messages as $key => &$value ) { + $value[0] = wfMsgExt( $value[0], 'escapenoentities' ); + $value[1] = wfMsgExt( $value[1], 'escapenoentities' ); } } - # Inconsistent naming, bleh - if ( $key == 'newpage' || $key == 'unpatrolled' ) { - $key2 = $key; - } else { - $key2 = $key . 'edit'; - } - return "" - . $messages["${key2}letter"] - . ''; + return "" . $messages[$key][0] . ''; } /** @@ -507,8 +501,15 @@ class OldChangesList extends ChangesList { } else { $this->insertDiffHist( $s, $rc, $unpatrolled ); # M, N, b and ! (minor, new, bot and unpatrolled) - $s .= $this->recentChangesFlags( $rc->mAttribs['rc_new'], $rc->mAttribs['rc_minor'], - $unpatrolled, '', $rc->mAttribs['rc_bot'] ); + $s .= $this->recentChangesFlags( + array( + 'new' => $rc->mAttribs['rc_new'], + 'minor' => $rc->mAttribs['rc_minor'], + 'unpatrolled' => $unpatrolled, + 'bot' => $rc->mAttribs['rc_bot'] + ), + '' + ); $this->insertArticleLink( $s, $rc, $unpatrolled, $watched ); } # Edit/log timestamp @@ -566,7 +567,7 @@ class EnhancedChangesList extends ChangesList { $this->rcCacheIndex = 0; $this->lastdate = ''; $this->rclistOpen = false; - $wgOut->addModules( 'mediawiki.legacy.enhancedchanges' ); + $wgOut->addModuleStyles( 'mediawiki.special.changeslist' ); return ''; } /** @@ -739,9 +740,9 @@ class EnhancedChangesList extends ChangesList { # Add the namespace and title of the block as part of the class if ( $block[0]->mAttribs['rc_log_type'] ) { # Log entry - $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $block[0]->mAttribs['rc_log_type'] . '-' . $block[0]->mAttribs['rc_title'] ); + $classes = 'collapsible collapsed mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $block[0]->mAttribs['rc_log_type'] . '-' . $block[0]->mAttribs['rc_title'] ); } else { - $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-ns' . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); + $classes = 'collapsible collapsed mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-ns' . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); } $r = Html::openElement( 'table', array( 'class' => $classes ) ) . Html::openElement( 'tr' ); @@ -804,23 +805,28 @@ class EnhancedChangesList extends ChangesList { $users = ' [' . implode( $this->message['semicolon-separator'], $users ) . ']'; - # ID for JS visibility toggle - $jsid = $this->rcCacheIndex; - # onclick handler to toggle hidden/expanded - $toggleLink = "onclick='toggleVisibility($jsid); return false'"; # Title for tags $expandTitle = htmlspecialchars( wfMsg( 'rc-enhanced-expand' ) ); $closeTitle = htmlspecialchars( wfMsg( 'rc-enhanced-hide' ) ); - $tl = "" . $this->sideArrow() . ""; - $tl .= ""; - $r .= ''.$tl.' '; + $tl = "" + . "" + . "{$this->arrow( 'd', '-', wfMsg( 'rc-enhanced-hide' ) )}" + . "" + . "{$this->arrow( 'd', '-', wfMsg( 'rc-enhanced-hide' ) )}" + . ""; + $r .= "$tl"; # Main line - $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, ' ', $bot ); + $r .= '' . $this->recentChangesFlags( array( + 'new' => $isnew, + 'minor' => false, + 'unpatrolled' => $unpatrolled, + 'bot' => $bot , + ) ); # Timestamp - $r .= ' '.$block[0]->timestamp.' '; + $r .= ' '.$block[0]->timestamp.' '; # Article link if( $namehidden ) { @@ -908,11 +914,7 @@ class EnhancedChangesList extends ChangesList { $r .= $users; $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers); - $r .= "\n"; - # Sub-entries - $r .= '
'; - $r .= ''; foreach( $block as $rcObj ) { # Extract fields from DB into the function scope (rc_xxxx variables) // FIXME: Would be good to replace this extract() call with something @@ -922,10 +924,14 @@ class EnhancedChangesList extends ChangesList { extract( $rcObj->mAttribs ); #$r .= '\n"; } - $r .= "
'.$this->spacerArrow(); - $r .= '
'; - $r .= $this->spacerIndent() . $this->spacerIndent(); - $r .= $this->recentChangesFlags( $rc_new, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot ); - $r .= ' '; + $r .= '
'; + $r .= $this->recentChangesFlags( array( + 'new' => $rc_new, + 'minor' => $rc_minor, + 'unpatrolled' => $rcObj->unpatrolled, + 'bot' => $rc_bot, + ) ); + $r .= ' '; $params = $queryParams; @@ -983,7 +989,7 @@ class EnhancedChangesList extends ChangesList { $r .= "
\n"; + $r .= "\n"; $this->rcCacheIndex++; @@ -1007,42 +1013,6 @@ class EnhancedChangesList extends ChangesList { return "\"$encAlt\""; } - /** - * Generate HTML for a right- or left-facing arrow, - * depending on language direction. - * @return String: HTML tag - */ - protected function sideArrow() { - global $wgContLang; - $dir = $wgContLang->isRTL() ? 'l' : 'r'; - return $this->arrow( $dir, '+', wfMsg( 'rc-enhanced-expand' ) ); - } - - /** - * Generate HTML for a down-facing arrow - * depending on language direction. - * @return String: HTML tag - */ - protected function downArrow() { - return $this->arrow( 'd', '-', wfMsg( 'rc-enhanced-hide' ) ); - } - - /** - * Generate HTML for a spacer image - * @return String: HTML tag - */ - protected function spacerArrow() { - return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space - } - - /** - * Add a set of spaces - * @return String: HTML tag - */ - protected function spacerIndent() { - return '     '; - } - /** * Enhanced RC ungrouped line. * @return String: a HTML formated line (generated using $r) @@ -1068,14 +1038,19 @@ class EnhancedChangesList extends ChangesList { $r = Html::openElement( 'table', array( 'class' => $classes ) ) . Html::openElement( 'tr' ); - $r .= '' . $this->spacerArrow() . ' '; + $r .= '' . $this->arrow( '', codepointToUtf8( 0xa0 ) ); # Flag and Timestamp if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { $r .= '    '; // 4 flags -> 4 spaces } else { - $r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot ); + $r .= $this->recentChangesFlags( array( + 'new' => $rc_type == RC_NEW, + 'minor' => $rc_minor, + 'unpatrolled' => $rcObj->unpatrolled, + 'bot' => $rc_bot + ) ); } - $r .= ' '.$rcObj->timestamp.' '; + $r .= ' '.$rcObj->timestamp.' '; # Article or log link if( $rc_log_type ) { $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 8fb8be159b..b9653b1ba5 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -1617,7 +1617,7 @@ class OutputPage { $sk = $wgUser->getSkin(); // Add base resources - $this->addModules( array( 'mediawiki.legacy.wikibits', 'mediawiki.util' ) ); + $this->addModules( array( 'mediawiki.legacy.wikibits', 'mediawiki.util', 'skins.common' ) ); // Add various resources if required if ( $wgUseAjax ) { diff --git a/resources/Resources.php b/resources/Resources.php index eff804aed0..6f5c2f1c0d 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -10,6 +10,16 @@ return array( 'user.options' => array( 'class' => 'ResourceLoaderUserOptionsModule' ), /* Skins */ + + 'skins.common' => array( + 'scripts' => 'resources/skins.common/skins.common.js', + 'styles' => array( + 'resources/skins.common/skins.common.css', + 'resources/skins.common/skins.common.print.css' => array( 'media' => 'print' ) + ), + 'messages' => array( 'hide', 'show' ), + 'dependencies' => array( 'jquery.effects.fade' ), + ), 'skins.vector' => array( 'styles' => array( 'skins/vector/screen.css' => array( 'media' => 'screen' ) ) @@ -292,6 +302,10 @@ return array( 'scripts' => 'resources/jquery.effects/jquery.effects.explode.js', 'dependencies' => 'jquery.effects.core', ), + 'jquery.effects.fade' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.fade.js', + 'dependencies' => 'jquery.effects.core', + ), 'jquery.effects.fold' => array( 'scripts' => 'resources/jquery.effects/jquery.effects.fold.js', 'dependencies' => 'jquery.effects.core', @@ -348,6 +362,9 @@ return array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.preferences.js', 'styles' => 'resources/mediawiki.special/mediawiki.special.preferences.css', ), + 'mediawiki.special.changeslist' => array( + 'styles' => 'resources/mediawiki.special/mediawiki.special.changeslist.css', + ), 'mediawiki.special.search' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.search.js', ), @@ -433,10 +450,6 @@ return array( 'scripts' => 'skins/common/edit.js', 'dependencies' => 'mediawiki.legacy.wikibits', ), - 'mediawiki.legacy.enhancedchanges' => array( - 'scripts' => 'skins/common/enhancedchanges.js', - 'dependencies' => 'mediawiki.legacy.wikibits', - ), 'mediawiki.legacy.history' => array( 'scripts' => 'skins/common/history.js', 'dependencies' => 'mediawiki.legacy.wikibits', diff --git a/resources/jquery.effects/jquery.effects.fade.js b/resources/jquery.effects/jquery.effects.fade.js new file mode 100644 index 0000000000..38bff0ba05 --- /dev/null +++ b/resources/jquery.effects/jquery.effects.fade.js @@ -0,0 +1,32 @@ +/* +* jQuery UI Effects Fade @VERSION +* +* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT or GPL Version 2 licenses. +* http://jquery.org/license +* +* http://docs.jquery.com/UI/Effects/Fade +* +* Depends: +* jquery.effects.core.js +*/ +(function( $, undefined ) { + + $.effects.fade = function(o) { + return this.queue(function() { + var elem = $(this), + mode = $.effects.setMode(elem, o.options.mode || 'hide'); + + elem.animate({ opacity: mode }, { + queue: false, + duration: o.duration, + easing: o.options.easing, + complete: function() { + (o.callback && o.callback.apply(this, arguments)); + elem.dequeue(); + } + }); + }); + }; + +})(jQuery); \ No newline at end of file diff --git a/resources/mediawiki.special/mediawiki.special.changeslist.css b/resources/mediawiki.special/mediawiki.special.changeslist.css new file mode 100644 index 0000000000..2383b88f5e --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.changeslist.css @@ -0,0 +1,40 @@ +/** + * Styling for Special:Watchlist and Special:RecentChanges + */ + +table.mw-enhanced-rc { + background: none; + border:0; + border-spacing:0; +} + +table.mw-enhanced-rc th, table.mw-enhanced-rc td { + padding:0; + vertical-align:top; +} + +td.mw-enhanced-rc { + white-space:nowrap; + font-family:monospace; +} + +.mw-enhanced-rc-time { + font-family: monospace; +} + +table.mw-enhanced-rc td.mw-enhanced-rc-nested { + padding-left: 1em; +} + +/* Show/hide arrows in enhanced changeslist */ +.mw-enhanced-rc .collapsible-expander { + float: none; +} + +.mw-rc-openarrow, +.show .mw-rc-closearrow { + display: none; +} +.show .mw-rc-openarrow { + display: inline; +} diff --git a/resources/skins.common/skins.common.css b/resources/skins.common/skins.common.css new file mode 100644 index 0000000000..cd422f6e3a --- /dev/null +++ b/resources/skins.common/skins.common.css @@ -0,0 +1,24 @@ +/** + * 'show'/'hide' buttons created dynamically + * by the CollapsibleTables javascript + */ +.collapsible-expander { + float: right; + font-weight: normal; + text-align: right; + width: auto; +} + +/** + * Note that IE<7 sees the second selector here as + * just ".collapsible-expander", and doesn't accept + * the multiple classes. Fortunately these two cursors + * are generally the same, a pair of up-and-down arrows. + */ + .collapsible-expander { + cursor: n-resize; +} +.show.collapsible-expander { + cursor: s-resize; +} + diff --git a/resources/skins.common/skins.common.js b/resources/skins.common/skins.common.js new file mode 100644 index 0000000000..8162f3d568 --- /dev/null +++ b/resources/skins.common/skins.common.js @@ -0,0 +1,99 @@ +/* + * Scripts common to all skins + */ + +/** + * Collapsible tables and divs. + * + * Users can create tables and nested divs which collapse either on-click or on-load, + * to save space on a page, or to conceal information at first sight. Eg: + * + * + * + * + * + * + * + * + * + * If the user does not create a toggle-button manually, one will be created, + * in the top-right header cell for tables, and at the beginning of the outer + * div's content for a collapsible div. + * @author Happy-melon + */ +$('.collapsible').each( function(){ + var $x = $j(this); + if( $('.collapsible-expander', $x).size() ){ + $('.collapsible-expander', $x) + .click(function(e, rmClass){ + e.preventDefault(); + rmClass = !(rmClass == false); + $(this) + .toggleClass('show') + .trigger('mw-toggle-collapse', [rmClass]); + return false; + }); + } else { + $expander = $j('
') + .text( '[' + mediaWiki.msg( 'hide' ) + ']' ) + .click(function(e, rmClass){ + rmClass = !(rmClass == false); + e.preventDefault(); + $(this) + .toggleClass('show') + .trigger('mw-toggle-collapse', [rmClass]) + .text( + '[' + + ($(this).is('.collapsible.collapsed .collapsible-expander') + ? mediaWiki.msg( 'show' ) + : mediaWiki.msg( 'hide' )) + + ']' + ); + return true; + }) + .before('[').after(']'); + if( $x.is('div.collapsible')){ + $x.prepend($expander); + } else { + $('tr:first th:last',$x).append($expander); + } + } + +}); + +/** + * This is a bit nasty because it also toggles any nested + * collapsible objects. But it would be a nightmare to only + * select the outer collapser without adding ids everywhere. + */ +$('table.collapsible').live( 'mw-toggle-collapse', function(e, rmClass){ + e.stopPropagation(); + var time = rmClass ? 500 : 0; + $('tr:gt(0)',$(this)) + // We'd like to use slideToggle() like for divs, but the jQuery + // slide animation for table rows is just *pig ugly*... + .toggle("fade", { direction: "vertical" }, time); + if( rmClass ){ + $('table.collapsible',$(this)).andSelf().toggleClass('collapsed'); + } + return true; +}); + +$('div.collapsible').live( 'mw-toggle-collapse', function(e, rmClass){ + e.stopPropagation(); + var time = rmClass ? 500 : 0; + $(this).children(':not(.collapsible-expander)') + .slideToggle(time); + if( rmClass ){ + $('div.collapsible',$(this)).andSelf().toggleClass('collapsed'); + } + return true; +}); + +$('.collapsible.collapsed .collapsible-expander').trigger( 'click', [false] ); \ No newline at end of file diff --git a/resources/skins.common/skins.common.print.css b/resources/skins.common/skins.common.print.css new file mode 100644 index 0000000000..336b879046 --- /dev/null +++ b/resources/skins.common/skins.common.print.css @@ -0,0 +1,19 @@ +/** + * Uncollapse collapsible tables/divs. The proper way to do this + * for tables is to use display:table-row, but this is not supported + * by all browsers, so use display:block as fallback. Equally, the > + * selector is not supported by IE<7, so the fourth rule is recognised + * only by IE6 and below. + */ +table.collapsible tr { + display: block !important; +} +table.collapsible tr { + display: table-row !important; +} +div.collapsible > * { + display: auto !important; +} +* html div.collapsible * { + display: auto !important; +} \ No newline at end of file diff --git a/skins/common/enhancedchanges.js b/skins/common/enhancedchanges.js deleted file mode 100644 index bcc2cc88a6..0000000000 --- a/skins/common/enhancedchanges.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - JavaScript file for enhanced recentchanges - */ - -/* - * Add the CSS to hide parts that should be collapsed - * - * We do this with JS so everything will be expanded by default - * if JS is disabled - */ -appendCSS('.mw-changeslist-hidden {'+ - ' display:none;'+ - '}'+ - 'div.mw-changeslist-expanded {'+ - ' display:block;'+ - '}'+ - 'span.mw-changeslist-expanded {'+ - ' display:inline !important;'+ - ' visibility:visible !important;'+ - '}' -); - -/* - * Switch an RC line between hidden/shown - * @param int idNumber : the id number of the RC group -*/ -window.toggleVisibility = function(idNumber) { - var openarrow = document.getElementById("mw-rc-openarrow-"+idNumber); - var closearrow = document.getElementById("mw-rc-closearrow-"+idNumber); - var subentries = document.getElementById("mw-rc-subentries-"+idNumber); - if (openarrow.className == 'mw-changeslist-expanded') { - openarrow.className = 'mw-changeslist-hidden'; - closearrow.className = 'mw-changeslist-expanded'; - subentries.className = 'mw-changeslist-expanded'; - } else { - openarrow.className = 'mw-changeslist-expanded'; - closearrow.className = 'mw-changeslist-hidden'; - subentries.className = 'mw-changeslist-hidden'; - } -}; diff --git a/skins/common/shared.css b/skins/common/shared.css index 32d566c20e..31d8eec0fb 100644 --- a/skins/common/shared.css +++ b/skins/common/shared.css @@ -827,18 +827,6 @@ div.gallerytext { word-wrap: break-word; } -table.mw-enhanced-rc { - background: none; - border:0; - border-spacing:0; -} -td.mw-enhanced-rc { - white-space:nowrap; - padding:0; - vertical-align:top; - font-family:monospace -} - #mw-addcategory-prompt { display: inline; margin-left: 1em;