diff --git a/CategoryPageSubclass.php b/CategoryPageSubclass.php index dbd3b41e..acc5c068 100644 --- a/CategoryPageSubclass.php +++ b/CategoryPageSubclass.php @@ -14,11 +14,23 @@ class CategoryTreeCategoryPage extends CategoryPage { class CategoryTreeCategoryViewer extends CategoryViewer { var $child_titles; + function getCategoryTree() { + global $wgOut, $wgCategoryTreeCategoryPageOptions; + + if ( ! isset($this->categorytree) ) { + CategoryTree::setHeaders( $wgOut ); + + $this->categorytree = new CategoryTree( $wgCategoryTreeCategoryPageOptions ); + } + + return $this->categorytree; + } + /** * Add a subcategory to the internal lists */ function addSubcategory( $title, $sortkey, $pageLength ) { - global $wgContLang, $wgOut, $wgRequest, $wgCategoryTreeCategoryPageMode; + global $wgContLang, $wgOut, $wgRequest; if ( $wgRequest->getCheck( 'notree' ) ) { return parent::addSubcategory( $title, $sortkey, $pageLength ); @@ -29,18 +41,15 @@ class CategoryTreeCategoryViewer extends CategoryViewer { return parent::addSubcategory( $title, $sortkey, $pageLength ); } - if ( ! isset($this->categorytree) ) { - CategoryTree::setHeaders( $wgOut ); - $this->categorytree = new CategoryTree; - } + $tree = $this->getCategoryTree(); - $this->children[] = $this->categorytree->renderNode( $title, $wgCategoryTreeCategoryPageMode ); + $this->children[] = $tree->renderNode( $title ); $this->children_start_char[] = $this->getSubcategorySortChar( $title, $sortkey ); } function getSubcategorySection() { - global $wgOut, $wgRequest, $wgCookiePrefix, $wgCategoryTreeCategoryPageMode; + global $wgOut, $wgRequest, $wgCookiePrefix; if ( $wgRequest->getCheck( 'notree' ) ) { return parent::getSubcategorySection(); @@ -98,11 +107,10 @@ class CategoryTreeCategoryViewer extends CategoryViewer { if ( $showAs == 'list' ) { $r .= $this->formatList( $this->children, $this->children_start_char ); } else { - CategoryTree::setHeaders( $wgOut ); - $ct = new CategoryTree; + $ct = getCategoryTree(); foreach ( $this->child_titles as $title ) { - $r .= $ct->renderNode( $title, $wgCategoryTreeCategoryPageMode ); + $r .= $ct->renderNode( $title ); } } return $r; diff --git a/CategoryTree.css b/CategoryTree.css index 138535ec..254190d8 100644 --- a/CategoryTree.css +++ b/CategoryTree.css @@ -16,11 +16,13 @@ margin-left: 1.5ex; } -.CategoryTreeBullet a:link { +.CategoryTreeBullet a, +.CategoryTreeBullet a:link, +.CategoryTreeBullet a:active, +.CategoryTreeBullet a:visited { text-decoration: none; color: inherit; - font-weight: bold; - speak: none; + speak: none; } .CategoryTreeLabelPage { diff --git a/CategoryTree.i18n.php b/CategoryTree.i18n.php index c19f5d77..3000a2b6 100644 --- a/CategoryTree.i18n.php +++ b/CategoryTree.i18n.php @@ -17,7 +17,7 @@ $messages['en'] = array( 'categorytree' => 'CategoryTree', 'categorytree-tab' => 'Tree', 'categorytree-legend' => 'Show category tree', - 'categorytree-desc' => 'AJAX based gadget to display the [[Special:CategoryTree|category structure]] of a wiki', + 'categorytree-desc' => 'Dynamically navigate the [[Special:CategoryTree|category structure]]', 'categorytree-header' => 'Enter a category name to see its contents as a tree structure. Note that this requires advanced JavaScript functionality known as AJAX. If you have a very old browser, or have JavaScript disabled, it will not work.', @@ -32,6 +32,10 @@ If you have a very old browser, or have JavaScript disabled, it will not work.', 'categorytree-collapse' => 'collapse', 'categorytree-expand' => 'expand', + 'categorytree-collapse-bullet' => '[]', + 'categorytree-expand-bullet' => '[+]', + 'categorytree-page-bullet' => ' ', + 'categorytree-load' => 'load', 'categorytree-loading' => 'loading…', 'categorytree-nothing-found' => 'nothing found', @@ -586,7 +590,7 @@ $messages['de'] = array( 'categorytree' => 'Kategorienbaum', 'categorytree-tab' => 'Baum', 'categorytree-legend' => 'Zeige Kategorienbaum', - 'categorytree-desc' => 'Ajax-basiertes Gadget, um die [[Special:CategoryTree|Kategorien-Struktur]] eines Wikis anzuzeigen', + 'categorytree-desc' => 'Dynamische Navigation für die [[Special:CategoryTree|Kategorien-Struktur]]', 'categorytree-header' => 'Zeigt für die angegebene Kategorie die Unterkategorien in einer Baumstruktur. Diese Seite benötigt bestimmte JavaScript-Funktionen (Ajax) und funktioniert möglicherweise nicht, wenn JavaScript ausgeschaltet ist oder ein sehr alter Browser verwendet wird.', 'categorytree-category' => 'Kategorie:', diff --git a/CategoryTree.js b/CategoryTree.js index 41d01e20..5803501b 100644 --- a/CategoryTree.js +++ b/CategoryTree.js @@ -26,48 +26,88 @@ var categoryTreeRetryMsg = "Please wait a moment and try again."; return n; } - function categoryTreeExpandNode(cat, mode, lnk) { + function categoryTreeExpandNode(cat, options, lnk) { var div= categoryTreeNextDiv( lnk.parentNode.parentNode ); div.style.display= 'block'; - lnk.innerHTML= '–'; + lnk.innerHTML= categoryTreeCollapseBulletMsg; lnk.title= categoryTreeCollapseMsg; - lnk.onclick= function() { categoryTreeCollapseNode(cat, mode, lnk) } + lnk.onclick= function() { categoryTreeCollapseNode(cat, options, lnk) } if (lnk.className != "CategoryTreeLoaded") { - categoryTreeLoadNode(cat, mode, lnk, div); + categoryTreeLoadNode(cat, options, lnk, div); } } - function categoryTreeCollapseNode(cat, mode, lnk) { + function categoryTreeCollapseNode(cat, options, lnk) { var div= categoryTreeNextDiv( lnk.parentNode.parentNode ); div.style.display= 'none'; - lnk.innerHTML= '+'; + lnk.innerHTML= categoryTreeExpandBulletMsg; lnk.title= categoryTreeExpandMsg; - lnk.onclick= function() { categoryTreeExpandNode(cat, mode, lnk) } + lnk.onclick= function() { categoryTreeExpandNode(cat, options, lnk) } } - function categoryTreeLoadNode(cat, mode, lnk, div) { + function categoryTreeLoadNode(cat, options, lnk, div) { div.style.display= 'block'; lnk.className= 'CategoryTreeLoaded'; - lnk.innerHTML= '–'; + lnk.innerHTML= categoryTreeCollapseBulletMsg; lnk.title= categoryTreeCollapseMsg; - lnk.onclick= function() { categoryTreeCollapseNode(cat, mode, lnk) } + lnk.onclick= function() { categoryTreeCollapseNode(cat, options, lnk) } - categoryTreeLoadChildren(cat, mode, div) + categoryTreeLoadChildren(cat, options, div) } - function categoryTreeLoadChildren(cat, mode, div) { + function categoryTreeEncodeOptions(options) { + var opt = ""; + + for (k in options) { + v = options[k]; + + switch (typeof v) { + case 'string': + v = '"' + v.replace(/([\\"'])/g, "\\$1") + '"'; + break; + + case 'number': + case 'boolean': + case 'null': + v = String(v); + break; + + case 'object': + if ( !v ) v = 'null'; + else throw new Error("categoryTreeLoadChildren can not encode complex types"); + break; + + default: + throw new Error("categoryTreeLoadChildren encountered strange variable type " + (typeof v)); + } + + if ( opt != "" ) opt += ", "; + opt += k; + opt += ":"; + opt += v; + } + + opt = "{"+opt+"}"; + return opt; + } + + function categoryTreeLoadChildren(cat, options, div) { div.innerHTML= '' + categoryTreeLoadingMsg + ''; + if ( typeof options == "string" ) { //hack for backward compatibility + options = { mode : options }; + } + function f( request ) { if (request.status != 200) { div.innerHTML = '' + categoryTreeErrorMsg + ' '; var retryLink = document.createElement('a'); retryLink.innerHTML = categoryTreeRetryMsg; retryLink.onclick = function() { - categoryTreeLoadChildren(cat, mode, div); + categoryTreeLoadChildren(cat, options, div, enc); } div.appendChild(retryLink); return; @@ -79,8 +119,8 @@ var categoryTreeRetryMsg = "Please wait a moment and try again."; if ( result == '' ) { result= ''; - if ( mode == 0 ) result= categoryTreeNoSubcategoriesMsg; - else if ( mode == 10 ) result= categoryTreeNoPagesMsg; + if ( options.mode == 0 ) result= categoryTreeNoSubcategoriesMsg; + else if ( options.mode == 10 ) result= categoryTreeNoPagesMsg; else result= categoryTreeNothingFoundMsg; result+= ''; @@ -90,5 +130,6 @@ var categoryTreeRetryMsg = "Please wait a moment and try again."; div.innerHTML= result; } - sajax_do_call( "efCategoryTreeAjaxWrapper", [cat, mode] , f ); + var opt = categoryTreeEncodeOptions(options); + sajax_do_call( "efCategoryTreeAjaxWrapper", [cat, opt, 'json'] , f ); } diff --git a/CategoryTree.php b/CategoryTree.php index 5b9941e7..ea9c4e39 100644 --- a/CategoryTree.php +++ b/CategoryTree.php @@ -6,7 +6,7 @@ * * @addtogroup Extensions * @author Daniel Kinzler, brightbyte.de - * @copyright © 2006-2007 Daniel Kinzler + * @copyright © 2006-2008 Daniel Kinzler and others * @license GNU General Public Licence 2.0 or later */ @@ -46,12 +46,21 @@ $wgCategoryTreeDisableCache = true; $wgCategoryTreeDynamicTag = false; $wgCategoryTreeHTTPCache = false; $wgCategoryTreeUnifiedView = true; -$wgCategoryTreeOmitNamespace = false; $wgCategoryTreeMaxDepth = array(CT_MODE_PAGES => 1, CT_MODE_ALL => 1, CT_MODE_CATEGORIES => 2); + $wgCategoryTreeExtPath = '/extensions/CategoryTree'; +$wgCategoryTreeVersion = '2'; #NOTE: bump this when you change the CSS or JS files! + +$wgCategoryTreeOmitNamespace = false; $wgCategoryTreeDefaultMode = CT_MODE_CATEGORIES; +$wgCategoryTreeDefaultOptions = array(); #Default values for most options. ADD NEW OPTIONS HERE! +$wgCategoryTreeDefaultOptions['mode'] = NULL; # will be set to $wgCategoryTreeDefaultMode in efCategoryTree(); compatibility quirk +$wgCategoryTreeDefaultOptions['hideprefix'] = NULL; # will be set to $wgCategoryTreeDefaultMode in efCategoryTree(); compatibility quirk +#TODO: hideprefix: always, never, catonly, catonly_if_onlycat + $wgCategoryTreeCategoryPageMode = CT_MODE_CATEGORIES; -$wgCategoryTreeVersion = '1'; #NOTE: bump this when you change the CSS or JS files! +$wgCategoryTreeCategoryPageOptions = array(); #Options to be used for category pages +$wgCategoryTreeCategoryPageOptions['mode'] = NULL; # will be set to $wgCategoryTreeDefaultMode in efCategoryTree(); compatibility quirk /** * Register extension setup hook and credits @@ -63,7 +72,7 @@ $wgExtensionCredits['specialpage'][] = array( 'svn-revision' => '$LastChangedRevision$', 'author' => 'Daniel Kinzler', 'url' => 'http://www.mediawiki.org/wiki/Extension:CategoryTree', - 'description' => 'AJAX based gadget to display the category structure of a wiki', + 'description' => 'Dynamically navigate the category structure', 'descriptionmsg' => 'categorytree-desc', ); $wgExtensionCredits['parserhook'][] = array( @@ -72,7 +81,7 @@ $wgExtensionCredits['parserhook'][] = array( 'svn-revision' => '$LastChangedRevision$', 'author' => 'Daniel Kinzler', 'url' => 'http://www.mediawiki.org/wiki/Extension:CategoryTree', - 'description' => 'AJAX based gadget to display the category structure of a wiki', + 'description' => 'Dynamically navigate the category structure', 'descriptionmsg' => 'categorytree-desc', ); @@ -90,6 +99,8 @@ $wgSpecialPageGroups['CategoryTree'] = 'pages'; $wgHooks['OutputPageParserOutput'][] = 'efCategoryTreeParserOutput'; $wgHooks['ArticleFromTitle'][] = 'efCategoryTreeArticleFromTitle'; $wgHooks['LanguageGetMagic'][] = 'efCategoryTreeGetMagic'; + + /** * register Ajax function */ @@ -100,6 +111,8 @@ $wgAjaxExportList[] = 'efCategoryTreeAjaxWrapper'; */ function efCategoryTree() { global $wgUseAjax, $wgHooks; + global $wgCategoryTreeDefaultOptions, $wgCategoryTreeDefaultMode, $wgCategoryTreeOmitNamespace; + global $wgCategoryTreeCategoryPageOptions, $wgCategoryTreeCategoryPageMode; # Abort if AJAX is not enabled if ( !$wgUseAjax ) { @@ -113,6 +126,17 @@ function efCategoryTree() { efCategoryTreeSetHooks(); } + if ( !isset( $wgCategoryTreeDefaultOptions['mode'] ) || is_null( $wgCategoryTreeDefaultOptions['mode'] ) ) { + $wgCategoryTreeDefaultOptions['mode'] = $wgCategoryTreeDefaultMode; + } + + if ( !isset( $wgCategoryTreeDefaultOptions['hideprefix'] ) || is_null( $wgCategoryTreeDefaultOptions['hideprefix'] ) ) { + $wgCategoryTreeDefaultOptions['hideprefix'] = $wgCategoryTreeOmitNamespace; + } + + if ( !isset( $wgCategoryTreeCategoryPageOptions['mode'] ) || is_null( $wgCategoryTreeCategoryPageOptions['mode'] ) ) { + $wgCategoryTreeCategoryPageOptions['mode'] = $wgCategoryTreeCategoryPageMode; + } } function efCategoryTreeSetHooks() { @@ -140,13 +164,23 @@ function efCategoryTreeGetMagic( &$magicWords, $langCode ) { /** * Entry point for Ajax, registered in $wgAjaxExportList. + * The $enc parameter determins how the $options is decoded into a PHP array. + * If $enc is not given, '' is asumed, which simulates the old call interface, + * namely, only providing the mode name or number. * This loads CategoryTreeFunctions.php and calls CategoryTree::ajax() */ -function efCategoryTreeAjaxWrapper( $category, $mode ) { +function efCategoryTreeAjaxWrapper( $category, $options, $enc = '' ) { global $wgCategoryTreeHTTPCache, $wgSquidMaxAge, $wgUseSquid; - $ct = new CategoryTree; - $response = $ct->ajax( $category, $mode ); //FIXME: would need to pass on depth parameter here. + if ( is_string( $options ) ) { + $options = CategoryTree::decodeOptions( $options, $enc ); + } + + $depth = isset( $options['depth'] ) ? (int)$options['depth'] : 1; + + $ct = new CategoryTree( $options, true ); + $depth = efCategoryTreeCapDepth( $ct->getOption('mode'), $depth ); + $response = $ct->ajax( $category, $depth ); if ( $wgCategoryTreeHTTPCache && $wgSquidMaxAge && $wgUseSquid ) { $response->setCacheDuration( $wgSquidMaxAge ); @@ -180,25 +214,6 @@ function efCategoryTreeCapDepth( $mode, $depth ) { return min($depth, $max); } -/** -* Helper function to convert a string to a boolean value. -* Perhaps make this a global function in MediaWiki proper -*/ -function efCategoryTreeAsBool( $s ) { - if ( is_null( $s ) || is_bool( $s ) ) return $s; - $s = trim( strtolower( $s ) ); - - if ( $s === '1' || $s === 'yes' || $s === 'on' || $s === 'true' ) { - return true; - } - else if ( $s === '0' || $s === 'no' || $s === 'off' || $s === 'false' ) { - return false; - } - else { - return NULL; - } -} - /** * Entry point for the {{#categorytree}} tag parser function. * This is a wrapper around efCategoryTreeParserHook @@ -242,51 +257,21 @@ function efCategoryTreeParserHook( $cat, $argv, &$parser ) { static $initialized = false; + $ct = new CategoryTree( $argv ); + $divAttribs = Sanitizer::validateTagAttributes( $argv, 'div' ); $style = isset( $divAttribs['style'] ) ? $divAttribs['style'] : null; - $mode = isset( $argv[ 'mode' ] ) ? $argv[ 'mode' ] : null; - if ( $mode !== NULL ) { - $mode= trim( strtolower( $mode ) ); - - if ( $mode == 'all' ) $mode = CT_MODE_ALL; - else if ( $mode == 'pages' ) $mode = CT_MODE_PAGES; - else if ( $mode == 'categories' ) $mode = CT_MODE_CATEGORIES; - } - else { - $mode = $wgCategoryTreeDefaultMode; - } - - $hideroot = isset( $argv[ 'hideroot' ] ) ? efCategoryTreeAsBool( $argv[ 'hideroot' ] ) : null; - $onlyroot = isset( $argv[ 'onlyroot' ] ) ? efCategoryTreeAsBool( $argv[ 'onlyroot' ] ) : null; + $hideroot = isset( $argv[ 'hideroot' ] ) ? CategoryTree::decodeBoolean( $argv[ 'hideroot' ] ) : null; + $onlyroot = isset( $argv[ 'onlyroot' ] ) ? CategoryTree::decodeBoolean( $argv[ 'onlyroot' ] ) : null; $depthArg = isset( $argv[ 'depth' ] ) ? $argv[ 'depth' ] : null; - $depth = efCategoryTreeCapDepth($mode, $depthArg); - + $depth = efCategoryTreeCapDepth( $ct->getOption( 'mode' ), $depthArg ); if ( $onlyroot ) $depth = 0; - $ct = new CategoryTree; - return $ct->getTag( $parser, $cat, $mode, $hideroot, $style, $depth ); + return $ct->getTag( $parser, $cat, $hideroot, $style, $depth ); } -/** -* Hook callback that installs a tab for CategoryTree on Category pages - */ -/* -function efCategoryTreeInstallTabs( &$skin, &$content_actions ) { - global $wgTitle; - - if ( $wgTitle->getNamespace() != NS_CATEGORY ) return true; - - $special = Title::makeTitle( NS_SPECIAL, 'CategoryTree' ); - - $content_actions['categorytree'] = array( - 'class' => false, - 'text' => htmlspecialchars( CategoryTree::msg( 'tab' ) ), - 'href' => $special->getLocalUrl() . '/' . $wgTitle->getPartialURL() ); - return true; -}*/ - /** * Hook callback that injects messages and things into the tag * Does nothing if $parserOutput->mCategoryTreeTag is not set diff --git a/CategoryTreeFunctions.php b/CategoryTreeFunctions.php index 34a939ae..6366bd26 100644 --- a/CategoryTreeFunctions.php +++ b/CategoryTreeFunctions.php @@ -17,6 +17,62 @@ if( !defined( 'MEDIAWIKI' ) ) { class CategoryTree { var $mIsAjaxRequest = false; + var $mOptions = array(); + + function __construct( $options, $ajax = false ) { + global $wgCategoryTreeDefaultOptions; + + $this->mIsAjaxRequest = $ajax; + + #ensure default values and order of options. Order may become important, it may influence the cache key! + foreach ( $wgCategoryTreeDefaultOptions as $option => $default ) { + if ( isset( $options[$option] ) && !is_null( $options[$option] ) ) $this->mOptions[$option] = $options[$option]; + else $this->mOptions[$option] = $default; + } + + $this->mOptions['mode'] = self::decodeMode( $this->mOptions['mode'] ); + $this->mOptions['hideprefix'] = self::decodeBoolean( $this->mOptions['hideprefix'] ); + } + + function getOption( $name ) { + return $this->mOptions[$name]; + } + + static function decodeMode( $mode ) { + global $wgCategoryTreeDefaultOptions; + + if ( is_null( $mode ) ) return $wgCategoryTreeDefaultOptions['mode']; + if ( is_int( $mode ) ) return $mode; + + $mode = trim( strtolower( $mode ) ); + + if ( is_numeric( $mode ) ) return (int)$mode; + + if ( $mode == 'all' ) $mode = CT_MODE_ALL; + else if ( $mode == 'pages' ) $mode = CT_MODE_PAGES; + else if ( $mode == 'categories' ) $mode = CT_MODE_CATEGORIES; + else if ( $mode == 'default' ) $mode = $wgCategoryTreeDefaultOptions['mode']; + + return (int)$mode; + } + + /** + * Helper function to convert a string to a boolean value. + * Perhaps make this a global function in MediaWiki proper + */ + static function decodeBoolean( $value ) { + if ( is_null( $value ) ) return NULL; + if ( is_bool( $value ) ) return $value; + if ( is_int( $value ) ) return ( $value > 0 ); + + $value = trim( strtolower( $value ) ); + if ( is_numeric( $value ) ) return ( (int)$mode > 0 ); + + if ( $value == 'yes' || $value == 'y' || $value == 'true' || $value == 't' || $value == 'on' ) return true; + else if ( $value == 'no' || $value == 'n' || $value == 'false' || $value == 'f' || $value == 'off' ) return false; + else if ( $value == 'null' || $value == 'default' || $value == 'none' || $value == 'x' ) return NULL; + else return false; + } /** * Set the script tags in an OutputPage object @@ -57,6 +113,8 @@ class CategoryTree { "