Difference between revisions of "Extension:TreeAndMenu"
m |
(change render code to handle trees or menus) |
||
Line 88: | Line 88: | ||
* Expand #tree parser-functions | * Expand #tree parser-functions | ||
*/ | */ | ||
− | public function expandTree( | + | public function expandTree() { |
+ | $args = func_get_args(); | ||
+ | return $this->expandTreeAndMenu('tree',$args); | ||
+ | } | ||
/** | /** | ||
* Expand #menu parser-functions | * Expand #menu parser-functions | ||
*/ | */ | ||
− | public function expandMenu( | + | public function expandMenu() { |
+ | $args = func_get_args(); | ||
+ | return $this->expandTreeAndMenu('menu',$args); | ||
+ | } | ||
/** | /** | ||
* Expand either kind of parser-function (reformats tree rows for matching later) and store args | * Expand either kind of parser-function (reformats tree rows for matching later) and store args | ||
*/ | */ | ||
− | private function expandTreeAndMenu( | + | private function expandTreeAndMenu($magic,$args) { |
+ | $parser = array_shift($args); | ||
# Store args for this tree for later use | # Store args for this tree for later use | ||
− | $args | + | foreach ($args as $arg) if (preg_match('/^(\\w+?)\\s*=\\s*(.+)$/s',$arg,$m)) $args[$m[1]] = $m[2]; else $text = $arg; |
− | |||
− | |||
− | |||
# Create a unique id for this tree or use id supplied in args and store args wrt id | # Create a unique id for this tree or use id supplied in args and store args wrt id | ||
$this->id = isset($args['id']) ? $args['id'] : uniqid(''); | $this->id = isset($args['id']) ? $args['id'] : uniqid(''); | ||
+ | $args['type'] = $magic; | ||
$this->args[$this->id] = $args; | $this->args[$this->id] = $args; | ||
Line 113: | Line 118: | ||
$text = preg_replace('/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/',"{$this->uniq}3$1{$this->uniq}4",$text); | $text = preg_replace('/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/',"{$this->uniq}3$1{$this->uniq}4",$text); | ||
$text = preg_replace_callback('/^(\\*+)(.*?)$/m',array($this,'formatRow'),$text); | $text = preg_replace_callback('/^(\\*+)(.*?)$/m',array($this,'formatRow'),$text); | ||
− | + | ||
return $text; | return $text; | ||
} | } | ||
Line 133: | Line 138: | ||
* Called after parser has finished (ParserAfterTidy) so all transcluded parts can be assembled into final trees | * Called after parser has finished (ParserAfterTidy) so all transcluded parts can be assembled into final trees | ||
*/ | */ | ||
− | public function renderTreeAndMenu(&$parser, &$text) { | + | public function renderTreeAndMenu(&$parser,&$text) { |
global $wgJsMimeType; | global $wgJsMimeType; | ||
$u = $this->uniq; | $u = $this->uniq; | ||
Line 148: | Line 153: | ||
$rows = array(); | $rows = array(); | ||
$depths = array('' => 0); # depth of each tree root | $depths = array('' => 0); # depth of each tree root | ||
− | $rootId = ''; | + | $rootId = ''; # the id of the current root-tree (used as tree id in PASS2) |
$lastId = ''; | $lastId = ''; | ||
$lastDepth = 0; | $lastDepth = 0; | ||
Line 170: | Line 175: | ||
$node++; | $node++; | ||
list($id,$depth,$icon,$item,$start) = $info; | list($id,$depth,$icon,$item,$start) = $info; | ||
− | $args = $this->args[$id]; | + | $args = $this->args[$id]; |
+ | $type = $args['type']; | ||
if (!isset($args['root'])) $args['root'] = ''; # tmp - need to handle rootless trees | if (!isset($args['root'])) $args['root'] = ''; # tmp - need to handle rootless trees | ||
− | $end | + | $end = $node == count($rows) || $rows[$node][4]; |
− | + | ||
− | + | # Append node script for this row | |
− | # Append | + | if ($depth > $last) { |
− | if ($depth > $last) $parents[$depth] = $node-1; | + | $parents[$depth] = $node-1; |
+ | if ($type == 'menu') $nodes .= "<ul>\n"; | ||
+ | } | ||
+ | elseif ($depth < $last && $type == 'menu' && !$start) $nodes .= str_repeat('</ul>',$last-$depth); | ||
$parent = $parents[$depth]; | $parent = $parents[$depth]; | ||
− | $ | + | $nodes .= $type == 'tree' ? "{$this->uniqname}$id.add($node,$parent,'$item');\n" : "<li>$item</li>\n"; |
− | + | $last = $depth; | |
− | # Last row of current root | + | # Last row of current root, surround nodes dtree or menu script and div etc |
if ($end) { | if ($end) { | ||
− | + | $class = "{$this->uniqname}-$type"; | |
− | + | if ($type == 'tree') { | |
− | $ | ||
− | |||
− | |||
− | |||
− | |||
− | + | # Finalise a tree | |
− | + | $add = isset($args['root']) ? "tree.add(0,-1,'".$args['root']."');" : ''; | |
− | + | $top = $bottom = $root = ''; | |
+ | foreach ($args as $arg => $pos) | ||
+ | if (($pos == 'top' || $pos == 'bottom' || $pos == 'root') && ($arg == 'open' || $arg == 'close')) | ||
+ | $$pos .= "<a href=\"javascript: {$this->uniqname}$id.{$arg}All();\"> {$arg} all</a> "; | ||
+ | if ($top) $top = "<p> $top</p>"; | ||
+ | if ($bottom) $bottom = "<p> $bottom</p>"; | ||
+ | if ($root) $add = "tree.add(0,-1,'$root');"; | ||
+ | $html = "$top<div class='$class' id='$id'> | ||
+ | <script type=\"$wgJsMimeType\"> | ||
+ | // TreeAndMenu{$this->version} | ||
+ | tree = new dTree('{$this->uniqname}$id'); | ||
+ | for (i in tree.icon) tree.icon[i] = '{$this->baseUrl}/'+tree.icon[i];{$this->images} | ||
+ | tree.config.useLines = {$this->useLines}; | ||
+ | $add | ||
+ | {$this->uniqname}$id = tree; | ||
+ | $nodes | ||
+ | document.getElementById('$id').innerHTML = {$this->uniqname}$id.toString(); | ||
+ | </script> | ||
+ | </div>$bottom"; | ||
+ | } | ||
+ | else { | ||
+ | if ($depth > 0) $nodes .= str_repeat('</ul>',$depth); | ||
+ | $html = "<ul class='$class' id='$id'>$nodes</ul>\n"; # Finalise a menu | ||
+ | } | ||
− | + | $text = preg_replace("/\x7f1$u\x7f$id\x7f.+?$/m",$html,$text,1); # replace first occurence of this trees root-id | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | $text = preg_replace("/\x7f1$u\x7f$id\x7f.+?$/m",$ | ||
$nodes = ''; | $nodes = ''; | ||
} | } |
Revision as of 03:08, 17 April 2008
<?php /**
- MediaWiki TreeAndMenu ExtensionTemplate:Php
- - See http://www.mediawiki.org/wiki/Extension:TreeAndMenu for installation and usage details
- - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
- - Author: http://www.organicdesign.co.nz/nad
- - Started: 2008-04-17 (This code is Extension:Treeview5.php with Son of Suckerfish dropdown menus added)
- /
if (!defined('MEDIAWIKI')) die('Not an entry point.');
define('TREEANDMENU_VERSION','1.0.0, 2008-04-17');
- Set any unset images to default titles
if (!isset($wgTreeViewImages) || !is_array($wgTreeViewImages)) $wgTreeViewImages = array();
$wgTreeMagic = "tree"; # the parser-function name for trees $wgMenuMagic = "menu"; # the parser-function name for dropdown menus $wgTreeViewShowLines = false; # whether to render the dotted lines joining nodes $wgExtensionFunctions[] = 'wfSetupTreeAndMenu'; $wgHooks['LanguageGetMagic'][] = 'wfTreeAndMenuLanguageGetMagic';
$wgExtensionCredits['parserhook'][] = array( 'name' => 'TreeAndMenu', 'author' => 'Nad, Sven', 'url' => 'http://www.mediawiki.org/wiki/Extension:Treeview', 'description' => 'Adds #tree and #menu parser functions which contain bullet-lists to be rendered as collapsible treeview\'s or dropdown menus. The treeview\'s use the dTree JavaScript tree menu, and the dropdown menu\'s use Son of Suckerfish', 'version' => TREEANDMENU_VERSION );
class TreeAndMenu {
var $version = TREEANDMENU_VERSION; var $uniq = ; # uniq part of all tree id's var $uniqname = 'tam'; # input name for uniqid var $id = ; # id for specific tree var $baseDir = ; # internal absolute path to treeview directory var $baseUrl = ; # external URL to treeview directory (relative to domain) var $images = ; # internal JS to update dTree images var $useLines = true; # internal variable determining whether to render connector lines var $args = array(); # args for each tree
/**
* Constructor
*/
function __construct() {
global $wgOut,$wgHooks,$wgParser,$wgScriptPath,$wgJsMimeType,
$wgTreeMagic,$wgMenuMagic,$wgTreeViewImages,$wgTreeViewShowLines;
# Add hooks $wgParser->setFunctionHook($wgTreeMagic,array($this,'expandTree')); $wgParser->setFunctionHook($wgMenuMagic,array($this,'expandMenu')); $wgHooks['ParserAfterTidy'][] = array($this,'renderTreeAndMenu');
# Update general tree paths and properties $this->baseDir = dirname(__FILE__); $this->baseUrl = preg_replace('|^.+(?=[/\\\\]extensions)|',$wgScriptPath,$this->baseDir); $this->useLines = $wgTreeViewShowLines ? 'true' : 'false'; $this->uniq = uniqid($this->uniqname);
# Convert image titles to file paths and store as JS to update dTree foreach ($wgTreeViewImages as $k => $v) { $title = Title::newFromText($v,NS_IMAGE); $image = Image::newFromTitle($title); $v = $image && $image->exists() ? $image->getURL() : $wgTreeViewImages[$k]; $this->images .= "tree.icon['$k'] = '$v';"; }
# Add link to output to load dtree.js script $wgOut->addScript("<script type=\"$wgJsMimeType\" src=\"{$this->baseUrl}/dtree.js\"></script>\n"); $wgOut->addScript("<script type=\"$wgJsMimeType\"><![CDATA[//><"."!-- sfHover = function() { var sfEls = document.getElementById('nav').getElementsByTagName('li'); for (var i=0; i<sfEls.length; i++) { sfEls[i].onmouseover=function() { this.className+=' sfhover'; } sfEls[i].onmouseout=function() { this.className=this.className.replace(new RegExp(' sfhover\\b'),); } } } if (window.attachEvent) window.attachEvent('onload',sfHover); //--><!]]></script>"); }
/**
* Expand #tree parser-functions
*/
public function expandTree() {
$args = func_get_args();
return $this->expandTreeAndMenu('tree',$args);
}
/** * Expand #menu parser-functions */ public function expandMenu() { $args = func_get_args(); return $this->expandTreeAndMenu('menu',$args); }
/** * Expand either kind of parser-function (reformats tree rows for matching later) and store args */ private function expandTreeAndMenu($magic,$args) { $parser = array_shift($args);
# Store args for this tree for later use foreach ($args as $arg) if (preg_match('/^(\\w+?)\\s*=\\s*(.+)$/s',$arg,$m)) $args[$m[1]] = $m[2]; else $text = $arg;
# Create a unique id for this tree or use id supplied in args and store args wrt id $this->id = isset($args['id']) ? $args['id'] : uniqid(); $args['type'] = $magic; $this->args[$this->id] = $args;
# Reformat tree rows for matching in ParserAfterStrip $text = preg_replace('/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/',"{$this->uniq}3$1{$this->uniq}4",$text); $text = preg_replace_callback('/^(\\*+)(.*?)$/m',array($this,'formatRow'),$text);
return $text; }
/**
* Reformat tree bullet structure recording row, depth and id in a format which is not altered by wiki-parsing
* - format is: 1{uniq}-{id}-{depth}-{item}-2{uniq}
* - sequences of this format will be matched in ParserAfterTidy and converted into dTree JavaScript
* - NOTE: we can't encode a unique row-id because if the same tree instranscluded twice a cached version
* may be used (even if parser-cache disabled) this also means that tree id's may be repeated
*/
private function formatRow($m) {
return "\x7f1{$this->uniq}\x7f{$this->id}\x7f".(strlen($m[1])-1)."\x7f$m[2]\x7f2{$this->uniq}";
}
/**
* Called after parser has finished (ParserAfterTidy) so all transcluded parts can be assembled into final trees
*/
public function renderTreeAndMenu(&$parser,&$text) {
global $wgJsMimeType;
$u = $this->uniq;
# Determine which trees are sub trees # - there should be a more robust way to do this, # it's just based on the fact that all sub-tree's have a minus preceding their row data if (!preg_match_all("/\x7f\x7f1$u\x7f(.+?)\x7f/",$text,$subs)) $subs = array(1 => array());
# Extract all the formatted tree rows in the page and if any, replace with dTree JavaScript if (preg_match_all("/\x7f1$u\x7f(.+?)\x7f([0-9]+)\x7f({$u}3(.+?){$u}4)?(.*?)(?=\x7f[12]$u)/",$text,$matches,PREG_SET_ORDER)) {
# PASS-1: build $rows array containing depth, and tree start/end information $rows = array(); $depths = array( => 0); # depth of each tree root $rootId = ; # the id of the current root-tree (used as tree id in PASS2) $lastId = ; $lastDepth = 0; foreach ($matches as $match) { list(,$id,$depth,,$icon,$item) = $match; $start = false; if ($id != $lastId) { if (!isset($depths[$id])) $depths[$id] = $depths[$lastId]+$lastDepth; if ($start = $rootId != $id && !in_array($id,$subs[1])) $depths[$rootId = $id] = 0; } if ($item) $rows[] = array($rootId,$depth+$depths[$id],$icon,addslashes($item),$start); $lastId = $id; $lastDepth = $depth; }
# PASS-2: build the JavaScript and replace into $text $parents = array(); # parent node for each depth $last = -1; $nodes = ; foreach ($rows as $node => $info) { $node++; list($id,$depth,$icon,$item,$start) = $info; $args = $this->args[$id]; $type = $args['type']; if (!isset($args['root'])) $args['root'] = ; # tmp - need to handle rootless trees $end = $node == count($rows) || $rows[$node][4];
# Append node script for this row if ($depth > $last) { $parents[$depth] = $node-1;
if ($type == 'menu') $nodes .= "
- \n";
}
elseif ($depth < $last && $type == 'menu' && !$start) $nodes .= str_repeat('
',$last-$depth);
$parent = $parents[$depth];
$nodes .= $type == 'tree' ? "{$this->uniqname}$id.add($node,$parent,'$item');\n" : "
\n";
$last = $depth;
# Last row of current root, surround nodes dtree or menu script and div etc if ($end) { $class = "{$this->uniqname}-$type"; if ($type == 'tree') {
# Finalise a tree $add = isset($args['root']) ? "tree.add(0,-1,'".$args['root']."');" : ; $top = $bottom = $root = ; foreach ($args as $arg => $pos) if (($pos == 'top' || $pos == 'bottom' || $pos == 'root') && ($arg == 'open' || $arg == 'close')) $$pos .= "<a href=\"javascript: {$this->uniqname}$id.{$arg}All();\"> {$arg} all</a> ";
if ($top) $top = "
$top
"; if ($bottom) $bottom = "
$bottom
";
if ($root) $add = "tree.add(0,-1,'$root');";
$html = "$top
<script type=\"$wgJsMimeType\"> // TreeAndMenu{$this->version} tree = new dTree('{$this->uniqname}$id'); for (i in tree.icon) tree.icon[i] = '{$this->baseUrl}/'+tree.icon[i];{$this->images} tree.config.useLines = {$this->useLines}; $add {$this->uniqname}$id = tree; $nodes document.getElementById('$id').innerHTML = {$this->uniqname}$id.toString(); </script>
$bottom";
} else {
if ($depth > 0) $nodes .= str_repeat('',$depth); $html = "
- $nodes
\n"; # Finalise a menu
}
$text = preg_replace("/\x7f1$u\x7f$id\x7f.+?$/m",$html,$text,1); # replace first occurence of this trees root-id $nodes = ; } } }
$text = preg_replace("/\x7f1$u\x7f.+?[\\r\\n]+/m",,$text); # Remove all unreplaced row information return true; }
}
/**
* Called from $wgExtensionFunctions array when initialising extensions */
function wfSetupTreeAndMenu() { global $wgTreeAndMenu; $wgTreeAndMenu = new TreeAndMenu(); }
/**
* Needed in MediaWiki >1.8.0 for magic word hooks to work properly */
function wfTreeAndMenuLanguageGetMagic(&$magicWords,$langCode = 0) { global $wgTreeMagic,$wgMenuMagic; $magicWords[$wgTreeMagic] = array($langCode,$wgTreeMagic); $magicWords[$wgMenuMagic] = array($langCode,$wgMenuMagic); return true; }